<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
  <channel>
    <title>liangguanhui</title>
    <description>盛年不重来，一日难再晨</description>
    <link>http://liangguanhui.javaeye.com</link>
    <language>UTF-8</language>
    <copyright>Copyright 2003-2008, JavaEye.com</copyright>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>JavaEye - 做最棒的软件开发交流社区</generator>
      <item>
        <title>【原创】Java多线程断点下载理论</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/186577" style="color:red;">http://liangguanhui.javaeye.com/blog/186577</a>&nbsp;
          发表时间: 2008年04月25日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>希望你转载文章的时候，麻烦保留作者信息。<strong><span style="color: #0000ff;">（夏威夷雪人 or 书虫）</span><br /><br />1、断点下载的基本原理<br /></strong>　　<br />　　其实这个是HTTP协议的一部分。在HTTP 1.1，支持断点下载，断点HTTP请求跟一般的HTTP请求基本相同，只有两点不同：<br />　　<br />（1）发起请求是HTTP的版本必须是HTTP/1.1<br />（2）在Header有这样一个节点：Range，格式是<strong>Range: bytes=起始字节-结束字节</strong><br />　　<br />　　如果HTTP返回的状态码是206，则代表对方支持断点下载，否则就是不支持。另外我用HttpURLConnection的时候，找不到设置HTTP版本的方法，虽然在下载的时候很多网站照样支持，但感觉不是很踏实。希望可以找到解决的方法。（当然可以自己用Socket实现一个Http协议，不过重复轮子的事情貌似不好，也不怎么想用HttpClient这个大块头）<br />　　<br /><strong>2、任务的分配算法</strong><br />　　<br />　　如果我们认真看一下FlashGet的下载栏目，一定可以看到它有一堆表示进度的格子，这里，每个格子就代表固定长度的字节，从这里我们可以清晰地看到下载的进度以及每一部分下载的情况。我们这里也使用类似的方法，把整个文件按照固定的长度（4K）分成很多&ldquo;格&rdquo;，然后进行多线程下载。这里就产生一个问题：怎么决定每个格子哪个线程去下载？我们知道，下载的时候，发起连接现对来说是一个比较耗时的操作，所以，我们需要把任务尽可能平均分配，较少连接的次数。<br />　　<br />　　我们根据每一个未下载的格子的&ldquo;相连&rdquo;情况，分成若干&ldquo;条&rdquo;&ldquo;任务链&rdquo;。这里的&ldquo;相连&rdquo;，是指按顺序的每一个未下载的格子都是相连的，没有跳跃。任务刚开始的时候，只有一条&ldquo;任务链&rdquo;，但当开始之后，当某些部分格子下载完毕之后，就被分割成不止一条&ldquo;任务链&rdquo;了。同时因为可能每一个线程的下载速度各不相同，所以，每一条任务链的长度可能都不相同。这就产生一个问题，当某个线程先一步完成了，我们当然是让这个线程继续下载，这时，我们就需要重新分配&ldquo;任务链&rdquo;。（如果我们用FlashGet下载一个文件，观察它的&ldquo;格子&rdquo;，对这个&ldquo;任务链&rdquo;的概念应该会更加清楚）<br />　　<br />　　整个分配算法，抽象出来，其实就是：<strong>有m条绳子，需要对这些绳子合共剪n刀，如何剪法才能使剪出来的绳子的最长跟最短的差距最小？</strong><br />　　<br />　　首先我们需要明确的是，无论是何种剪法，都需要遵守这个原则：<strong>对每条绳子的剪都必须是平均剪。至于原因，读者可以自己思考。</strong><br />　　<br />　　开始的时候，我是打算枚举所有剪法，然后计算每一种的剪法中，最短跟最长的差距。不过发现算法不容易写（粗略想了一下，貌似是排列组合的问题），而且时间复杂度貌似有O（n的平方）这么多。<br />　　<br />　　后来才发现自己原来是绕了个大弯。剪法其实很简单，首先在这m条绳子里找到最长的一条，放下一个剪刀，这是第一轮；然后在这m条绳子里再找到最长的一条，找的时候，如果遇到放有剪刀的绳子，按照剪刀的数目重新计算平均的绳子长度，例如加入绳子有6，放有2把剪刀，我们就算他为6&divide;（2＋1）＝2，然后，又在最长的那里放下一个剪刀；&hellip;&hellip;这样一直循环，直到放完所有剪刀。算法非常简单，而且时间复杂度只有O（n）。<br />　　<br /><strong>3、缓冲写的功能<br /></strong>　　<br />　　一般的下载工具都有缓冲写的功能，这个功能貌似对硬盘保养有一定的帮助。由于我们的下载是多线程下载，一般的方法是每个线程每次下完一个格子之后然后把这个格子的数据放到一个List里面，然后检查这个List里面的数据时候超过一个额度，如果超过，就把这些数据写道文件里面。这里有两个问题：<br />　　<br />（1）当这个线程写文件的时候，这个List我们当然要上锁的，这个时候，其余线程就不能往这个List里面放数据，被阻塞了。（解决方法很简单，把这个List的数据搬到另外一个B List里面，然后清空List的数据，再在这个线程根据B List慢慢写文件，这样其余线程就不会阻塞了）<br />　　<br />（2）这种方法需要浪费一个本来是下载的线程来写文件。<br />　　<br />　　第二个问题解决的方法是另起一个线程来专门写文件。每次每个线程下载完一个格子之后，把这个格子的数据放到一个地方（可以是List，也可以是其它），然后通知那个专门写文件的线程。那个线程检查数据的数量，如果超过一个额度就写文件。<br />　　<br />　　很明显，这是一个典型的生产/消费模型。Java5已经帮我们准备好了一个接口&mdash;&mdash;<strong>BlockingQueue</strong>，就不需要再重复造轮子了。<br />　　<br />　　最后差点忘了说，这里写文件由于需要跳跃写，所以需要使用RandomAccessFile，同时建议一开始就分配空间，减少产生文件碎片的可能。<br />　　<br /><strong>4、使用NIO代替多线程＋BIO<br /></strong>　　<br />　　貌似现在越来越流行NIO了，连Tomcat6都是NIO，我们也把我们的下载工具改造成NIO。具体的NIO原理就不在这里细说了，读者可以上网查查，不过由于HttpURLConnection和HttpClient都是基于BIO的，所以很遗憾，如果你要用NIO，你不得不自己实现Http的协议，虽然不是很难。</p>
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/186577#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 25 Apr 2008 00:42:34 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/186577</link>
        <guid>http://liangguanhui.javaeye.com/blog/186577</guid>
      </item>
      <item>
        <title>【原创】一个简单的多线程、断点下载Java程序</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/185625" style="color:red;">http://liangguanhui.javaeye.com/blog/185625</a>&nbsp;
          发表时间: 2008年04月22日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          <p>因为公司不允许用fg之类的软件，所以就搞了这个东西来下载东西。程序比较简单，尚有多处地方没有优化。其实这种多线程下载的难点主要是下载任务的分配 下，打个比方，一个文件的某个部分应该给哪个线程下载？为了简单（另一方面是我不愿多想），所以分配算法也比较简单，直接分成一块块，然后每个线程下载一块。如果读者有留意Flashget之类的软件下载时的过程图的话，应该会发现它们的算法比这里的好很多。<br />
<br />
这里我用HttpURLConnection下载，你也可以用HttpClient或者自己实现一个Http协议（不过貌似没有必要）<br />
<br />
其次，你可能发现我这里效仿迅雷，一个任务生成两个文件，一个是任务描述文件，一个是真正的下载文件，而Flahget则是只有一个文件。在任务描述文件里，我把前4K用来做一些描述，然后之后的用于记录下载的过程。<br />
<br />
另外，这里写文件没有实现缓存写之类的功能，不过那些功能做起来不难。<br />
<br />
最后，希望你转载文章的时候，麻烦保留作者信息。<span style="color: #0000ff;"><strong>（夏威夷雪人 or 书虫）</strong>
</span>
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<pre name="code" class="java">//这个是任务Bean

public class Task {



　　private String downURL;



　　private String saveFile;

　　

　　private int bufferSize = 64 * 1024;

　　

　　private int workerCount;

　　

　　private int sectionCount;

　　

　　private long contentLength;

　　

　　private long[] sectionsOffset;

　　

　　public static final int HEAD_SIZE = 4096;

　　

　　//读下载描述文件内容

　　public synchronized void read(RandomAccessFile file) throws IOException {

　　　　byte[] temp = new byte[HEAD_SIZE];

　　　　file.seek(0);

　　　　int readed = file.read(temp);

　　　　if (readed != temp.length) {

　　　　　　throw new RuntimeException();

　　　　}

　　　　ByteArrayInputStream bais = new ByteArrayInputStream(temp);

　　　　DataInputStream dis = new DataInputStream(bais);

　　　　downURL = dis.readUTF();

　　　　saveFile = dis.readUTF();

　　　　sectionCount = dis.readInt();

　　　　contentLength = dis.readLong();

　　　　

　　　　sectionsOffset = new long[sectionCount];

　　　　for (int i = 0; i &lt; sectionCount; i++) {

　　　　　　sectionsOffset[i] = file.readLong();

　　　　}

　　}

　　

　　//创建下载描述文件内容

　　public synchronized void create(RandomAccessFile file) throws IOException {

　　　　if (sectionCount != sectionsOffset.length) {

　　　　　　throw new RuntimeException();

　　　　}

　　　　long len = HEAD_SIZE + 8 * sectionCount;

　　　　file.setLength(len);

　　　　ByteArrayOutputStream baos = new ByteArrayOutputStream();

　　　　DataOutputStream dos = new DataOutputStream(baos);

　　　　dos.writeUTF(downURL);

　　　　dos.writeUTF(saveFile);

　　　　dos.writeInt(sectionCount);

　　　　dos.writeLong(contentLength);

　　　　byte[] src = baos.toByteArray();

　　　　byte[] temp = new byte[HEAD_SIZE];

　　　　System.arraycopy(src, 0, temp, 0, src.length);

　　　　file.seek(0);

　　　　file.write(temp);

　　　　writeOffset(file);

　　}

　　

　　//更新下载的过程

　　public synchronized void writeOffset(RandomAccessFile file) throws IOException {

　　　　if (sectionCount != sectionsOffset.length) {

　　　　　　throw new RuntimeException();

　　　　}

　　　　file.seek(HEAD_SIZE);

　　　　for (int i = 0; i &lt; sectionsOffset.length; i++) {

　　　　　　file.writeLong(sectionsOffset[i]);

　　　　}

　　}

　　（下面是Getter、Setter）

}



//这个是下载主程序



public class TaskAssign {



　　public void work(Task task) throws IOException {

　　　　File file = new File(task.getSaveFile());

　　　　if (file.exists()) {

　　　　　　return;

　　　　}

　　　　//这个是记录是否下载成功。我这里也没有增加失败回复、重试之类的工作。

　　　　final AtomicBoolean success = new AtomicBoolean(true);

　　　　//任务描述文件

　　　　File taskFile = new File(task.getSaveFile() + &quot;.r_task&quot;);

　　　　//真正下载的文件

　　　　File saveFile = new File(task.getSaveFile() + &quot;.r_save&quot;);

　　　　boolean taskFileExist = taskFile.exists();

　　　　RandomAccessFile taskRandomFile = null;

　　　　RandomAccessFile downRandomFile = null;

　　　　try {

　　　　　　taskRandomFile = new RandomAccessFile(taskFile, &quot;rw&quot;);

　　　　　　downRandomFile = new RandomAccessFile(saveFile, &quot;rw&quot;);

　　　　　　long rtnLen = getContentLength(task.getDownURL());

　　　　　　if (!taskFileExist) {

　　　　　　　　//如果文件不存在，就初始化任务文件和下载文件

　　　　　　　　task.setContentLength(rtnLen);

　　　　　　　　initTaskFile(taskRandomFile, task);

　　　　　　　　downRandomFile.setLength(rtnLen);

　　　　　　} else {

　　　　　　　　//任务文件存在就读取任务文件

　　　　　　　　task.read(taskRandomFile);

　　　　　　　　if (task.getContentLength() != rtnLen) {

　　　　　　　　　　throw new RuntimeException();

　　　　　　　　}

　　　　　　}

　　　　　　int secCount = task.getSectionCount();

　　　　　　//分配线程去下载，这里用到线程池

　　　　　　ExecutorService es = Executors.newFixedThreadPool(task.getWorkerCount());

　　　　　　for (int i = 0; i &lt; secCount; i++) {

　　　　　　　　final int j = i;

　　　　　　　　final Task t = task;

　　　　　　　　final RandomAccessFile f1 = taskRandomFile;

　　　　　　　　final RandomAccessFile f2 = downRandomFile;

　　　　　　　　es.execute(new Runnable() {

　　　　　　　　　　public void run() {

　　　　　　　　　　　　try {

　　　　　　　　　　　　　　down(f1, f2, t, j);

　　　　　　　　　　　　} catch (IOException e) {

　　　　　　　　　　　　　　success.set(false);

　　　　　　　　　　　　　　e.printStackTrace(System.out);

　　　　　　　　　　　　}

　　　　　　　　　　}

　　　　　　　　});

　　　　　　}

　　　　　　es.shutdown();

　　　　　　try {

　　　　　　　　es.awaitTermination(24 * 3600, TimeUnit.SECONDS);

　　　　　　} catch (InterruptedException e) {

　　　　　　　　e.printStackTrace();

　　　　　　}

　　　　　　taskRandomFile.close();

　　　　　　taskRandomFile = null;

　　　　　　downRandomFile.close();

　　　　　　downRandomFile = null;

　　　　　　//如果下载成功，去掉任务描述文件、帮下载文件改名

　　　　　　if (success.get()) {

　　　　　　　　taskFile.delete();

　　　　　　　　saveFile.renameTo(file);

　　　　　　}

　　　　} finally {

　　　　　　if (taskRandomFile != null) {

　　　　　　　　taskRandomFile.close();

　　　　　　　　taskRandomFile = null;

　　　　　　}

　　　　　　if (downRandomFile != null) {

　　　　　　　　downRandomFile.close();

　　　　　　　　downRandomFile = null;

　　　　　　}

　　　　}

　　}

　　

　　public void down(RandomAccessFile taskRandomFile, RandomAccessFile downRandomFile, Task task, int sectionNo) throws IOException {

　　　　//这里我用HttpURLConnection下载，你也可以用HttpClient或者自己实现一个Http协议（不过貌似没有必要）

　　　　URL u = new URL(task.getDownURL());

　　　　HttpURLConnection conn = (HttpURLConnection) u.openConnection();

　　　　long start = task.getSectionsOffset()[sectionNo];

　　　　long end = -1;

　　　　//这里要注意一下，这里是计算当前块的长度

　　　　if (sectionNo &lt; task.getSectionCount() - 1) {

　　　　　　long per = task.getContentLength() / task.getSectionCount();

　　　　　　end = per * (sectionNo + 1);

　　　　} else {

　　　　　　end = task.getContentLength();

　　　　}

　　　　if (start &gt;= end) {

　　　　　　System.out.println(&quot;Section has finished before. &quot; + sectionNo);

　　　　　　return;

　　　　}

　　　　String range = &quot;bytes=&quot; + start + &quot;-&quot; + (end - 1);

　　　　conn.setRequestProperty(&quot;Range&quot;, range);

　　　　conn.setRequestProperty(&quot;User-Agent&quot;, &quot;Ray-Downer&quot;);

　　　　try {

　　　　　　conn.connect();

　　　　　　if (conn.getResponseCode() != 206) {

　　　　　　　　throw new RuntimeException();

　　　　　　}

　　　　　　if (conn.getContentLength() != (end - start)) {

　　　　　　　　throw new RuntimeException();

　　　　　　}

　　　　　　InputStream is = conn.getInputStream();

　　　　　　byte[] temp = new byte[task.getBufferSize()];

　　　　　　BufferedInputStream bis = new BufferedInputStream(is, temp.length);

　　　　　　int readed = 0;

　　　　　　while ((readed = bis.read(temp)) &gt; 0) {

　　　　　　　　long offset = task.getSectionsOffset()[sectionNo];

　　　　　　　　synchronized (task) {

　　　　　　　　　　//下载之后顺便更新描述文件，你可能会发现这里效率比较低，在一个线程同步里进行两次文件操作。你可以自己实现一个缓冲写。

　　　　　　　　　　downRandomFile.seek(offset);

　　　　　　　　　　downRandomFile.write(temp, 0, readed);

　　　　　　　　　　offset += readed;

　　　　　　　　　　task.getSectionsOffset()[sectionNo] = offset;

　　　　　　　　　　task.writeOffset(taskRandomFile);

　　　　　　　　}

　　　　　　}

　　　　} finally {

　　　　　　conn.disconnect();

　　　　}

　　　　System.out.println(&quot;Section finished. &quot; + sectionNo);

　　}

　　

　　public void initTaskFile(RandomAccessFile taskRandomFile, Task task) throws IOException {

　　　　int secCount = task.getSectionCount();

　　　　long per = task.getContentLength() / secCount;

　　　　long[] sectionsOffset = new long[secCount];

　　　　for (int i = 0; i &lt; secCount; i++) {

　　　　　　sectionsOffset[i] = per * i;

　　　　}

　　　　task.setSectionsOffset(sectionsOffset);

　　　　task.create(taskRandomFile);

　　}

　　

　　public long getContentLength(String url) throws IOException {

　　　　URL u = new URL(url);

　　　　HttpURLConnection conn = (HttpURLConnection) u.openConnection();

　　　　try {

　　　　　　return conn.getContentLength();

　　　　} finally {

　　　　　　conn.disconnect();

　　　　}

　　}

}



//稍微测试一下。

public class Main {

　　

　　public static void main(String[] args) throws IOException {

　　　　test3();

　　　　System.out.println(&quot;\n\n===============\nFinished.&quot;);

　　}

 

　　public static void test1() throws IOException {

　　　　Task task = new Task();

　　　　task.setDownURL(&quot;http://61.152.235.21/qqfile/qq/2007iistable/QQ2007IIKB1.exe&quot;);

　　　　task.setSaveFile(&quot;H:/Test2.exe&quot;);

　　　　task.setSectionCount(200);

　　　　task.setWorkerCount(100);

　　　　task.setBufferSize(256 * 1024);

　　　　TaskAssign ta = new TaskAssign();

　　　　ta.work(task);

　　}

　　

　　public static void test2() throws IOException {

　　　　Task task = new Task();

　　　　task.setDownURL(&quot;http://student1.scut.edu.cn:8880/manage/news/data/1208421861893.xls&quot;);

　　　　task.setSaveFile(&quot;H:/Test3.xls&quot;);

　　　　task.setSectionCount(5);

　　　　task.setWorkerCount(1);

　　　　task.setBufferSize(128 * 1024);

　　　　TaskAssign ta = new TaskAssign();

　　　　ta.work(task);

　　}

　　

　　public static void test3() throws IOException {

　　　　Task task = new Task();

　　　　task.setDownURL(&quot;http://go.microsoft.com/fwlink/?linkid=57034&quot;);

　　　　task.setSaveFile(&quot;H:/vc2005express.iso&quot;);

　　　　task.setSectionCount(500);

　　　　task.setWorkerCount(200);

　　　　task.setBufferSize(128 * 1024);

　　　　TaskAssign ta = new TaskAssign();

　　　　ta.work(task);

　　}

　　

　　public static void test4() throws IOException {

　　　　Task task = new Task();

　　　　task.setDownURL(&quot;http://down.sandai.net/Thunder5.7.9.472.exe&quot;);

　　　　task.setSaveFile(&quot;H:/Thunder.exe&quot;);

　　　　task.setSectionCount(30);

　　　　task.setWorkerCount(30);

　　　　task.setBufferSize(128 * 1024);

　　　　TaskAssign ta = new TaskAssign();

　　　　ta.work(task);

　　}

}



</pre>
&nbsp;
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/185625#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 22 Apr 2008 21:16:40 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/185625</link>
        <guid>http://liangguanhui.javaeye.com/blog/185625</guid>
      </item>
      <item>
        <title>人事考核系统设计时遇到的问题</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/148291" style="color:red;">http://liangguanhui.javaeye.com/blog/148291</a>&nbsp;
          发表时间: 2007年12月14日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          本人近来负责一个<strong>人事考核</strong>的项目，遇到一个比较棘手的问题需要请教各位，问题如下：<br /><br />人事考核是分一期一期的，1年一期，在考核的时候，员工需要把他的一些需要考核的信息填写到系统里面，然后由人事部审核，考核完毕之后，会根据这些信息使用对应的公式算出一个分数，然后加起来，这个就是考核结果。<br /><br />不过每一期的计算公式。审核内容并不确定，打个比方，假如某个考核项目是<strong>员工参加活动</strong>，上一次需要填写的内容：活动类型、活动人数、获取奖项，对应的权数是0.3，0.4，0.5，那上一年这个项目的分数的计算公式就是：活动类型×0.3＋活动人数×0.4＋获取奖项×0.5＝分数，不过可能今年有所改动，可能会变动权数，又可能会增加一些新的考核元素，例如增加一个权数是0.2的活动级别。<br /><br />甚至，他可能会增加一些新的考核项目，例如增加一个员工出勤考核，元素包括0.4的出勤率，0.2的旷工数等等。<br /><br />对于这种系统，我怎么设计，才能尽可能地保证系统在每一期的考核<strong>通用</strong>，或者<strong>少量修改</strong>就可以在下期考核使用？
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/148291#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 14 Dec 2007 11:40:35 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/148291</link>
        <guid>http://liangguanhui.javaeye.com/blog/148291</guid>
      </item>
      <item>
        <title>NetBeans6对于VisualWeb的修改</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/146259" style="color:red;">http://liangguanhui.javaeye.com/blog/146259</a>&nbsp;
          发表时间: 2007年12月05日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          　　NetBeans6的正式版终于出来了，这阵子稍微试用了一把，因为我这阵子是用VisualWeb来做项目的，所以就比较留意这方面，发现NetBeans6对于VisualWeb有如下的修改：<br /><br /><br /><br />　　1、在新建工程的时候已经找不到专门的VisualWeb功能，而是在新建Web工程后在选择框架的时候选择（我开始用的时候找了好久才找到这个）。或者换一种说法吧。在NetBeans6之前，我们通常叫VisualWebPack，因为它是NetBeans的一个Pack，现在在NetBeans6，已经做成一个单独的开源Project——Woodstock，详细可以看这个地址：https://woodstock.dev.java.net，这里有不少源代码和帮助文件，当初我就郁闷：怎么NetBeans不是开源的吗？怎么没有附上VisualWeb的源代码，原来是在这里。它在NetBeans的插件名称也由以前的Visual Web Pack变成Visual JSF了。<br />由于变成了插件形式，相对来说升级比较容易，具体可以访问：http://wiki.netbeans.org/wiki/view/UpdatingWoodstockLibraryInNetBeans6<br /><br /><br /><br />　　2、更换组件的渲染方式。原来的渲染是直接生成html代码的，现在的渲染是生成html的框架，然后使用dojo来设置相应的值。打个比方，原来渲染StaticText组件的时候是生成<br /><pre name="code" class="java">&lt;span id="XXX">内容&lt;/span></pre>现在是<br /><pre name="code" class="java">&lt;span id="XXX">&lt;/span>&lt;script type="text/javascript">dojo.addOnLoad(function() {webui.suntheme.widget.common.replaceElement("XXX", {"id":"XXX","widgetType":"webui.suntheme.widget.staticText","visible":true,"value":"内容","escape":true});});&lt;/script></pre><br />也就是说，把渲染方式以插件的形式提供，然后使用dojo在客户端渲染出来。这里跟金蝶的Apusic有点相似，新版的Apusic的jsf貌似也是用dojo来渲染的。（突然想到一个问题，假如有一天dojo也像Ext一样需要授权费用，那这个VisualWeb怎么办？ ^_^）<br />由于更改了渲染方式，带来了另外一个问题：页面显示的速度较慢。页面显示过程：首先是载入所有的页面元素，注意，那些jsf组件这个时候并没有显示，其次例如dojo显示jsf组件，最后，对于某些组件填写数据。由于经过了几个过程，所以相对于以前的直接渲染html来说，给人的感觉是速度变慢了。<br /><br /><br /><br />　　3、组件是VisualWeb的核心，不过今次没有对组件有什么大的动作：<br /><br />以前表现得非常遭糕的Calendar组件，终于得到改善了，现在已经没有以前那种一出来就是25k的html代码了，不过还有问题：calendar的渲染还是有待改进，例如，弹出选择器的时候，覆盖背景会出现错位，选择器不能移动，不会自动消失，格式说明文字（即那个“年/月/日”的输入时间的格式）不能去掉等等，估计这个组件还是不能用于生产。<br /><br />在系统应用中广泛使用的表格组件，仍然没有什么新的突破：部分UI不可定制、部分CSS不可修改，其设计器原有的bug也没有修改，例如自动钩选paginateButton属性这个bug。它和DataProvider体系仍然是紧密结合。至于DataProvider体系，整体没有什么大的变化，设计器默认还是把所有数据放到session里面。SQL编辑器界面是漂亮了不少，不过功能没有什么大的变化。<br /><br />和FileUpload组件配合使用的UploadFilter，跟以前一样，当上传文件太大是没有任何提示并且继续执行剩余的操作。窃以为，应该提示用户上传文件过大，并且中止连接。<br /><br />其余的组件基本没有什么变化。<br /><br />需要注意的是，新版本里面，新建JSF组件已经不需要像以前那样在faces-config.xml声明它的render、component等，而是可以直接使用Annotation，具体可以看：https://woodstock.dev.java.net/FacesAnnotationProcessingQuickGuide.htm<br /><br /><br /><br />　　4、css设计器比原来的好用一点，不过跟Dreamweaver这些专家级设计软件比较差距还是不小。另外，以前制作的CSS，在vw的page有时会显示不正常，需要重启NetBeans，现在改进了不少。<br /><br /><br /><br />　　5、新版本的VisualWeb对于div + css的识别加强了很多，以前把dw中做好的div + css放到vw中，设计界面不能正常显示，现在放到新版本中，可以正常显示，而且识别率颇高，可以满足基本需要。这个改进非常重要，因为jsf主要是使用CSS来布局的，以前不能使用div + css布局，vw的布局一直是其弱项，现在的改进，无疑是重要的改进。因为我们可以在dw等专业的UI设计软件中按照div+css的标准设好界面，然后把body里面的HTML代码copy过来和css copy过来。使美工和程序员分离成为可能。<br />这里有一个不错的UI模版插件，做得不错：http://blogs.sun.com/winston/entry/page_layout_templates<br />不过话又说回来，UI设计的重绘（即修改了jsp或者java后重新计算设计界面的过程）还是很满，不尽如人意。<br /><br /><br /><br />　　6、表格设计器对于POJO的识别能力提高了。在表格属性里面，以前只有绑定到数据库的DataProvider才能看到具体的列，而对于POJO是不能看到具体属性的，需要手动输入，现在，只需要绑定到POJO的数组或者有范性的List都可以看到具体属性，例如：<br /><pre name="code" class="java">private MyObject[] myArray;
public MyObject[] getMyArray(){
     return  myArray; 
}
public void setMyArray(MyObject[] myArray){
     this.myArray = myArray; 
}</pre><br /><pre name="code" class="java">private List&lt;MyObject> myList;
public List&lt;MyObject> getMyList(){
     return  myList; 
}
public void setMyList(List&lt;MyObject> myArray){
     this.myList = myList; 
}</pre><br />不过操作起来有点麻烦，需要编译项目，再关闭项目，再重新打开项目。<br /><br /><br /><br />　　7、以前修改Theme是一件痛苦的事情，我们不得不修改默认Theme的源代码和素材，现在NetBeans6提供了一个ThemeBuilder的插件，可以自己做一个Theme了：http://blogs.sun.com/winston/entry/theme_builder<br /><br /><br /><br />　　8、因为tomcat已经是6，所以已经可以使用jsf1.2，另外，也可以选择J2EE5了（以前tomcat5只能选择J2EE1.4，并且只能使用jsf1.1特性）。<br /><br /><br /><br />　　9、以前的ViewState默认是保存到session的，现在默认是保存在client。<br /><br /><br /><br />　　10、导航的设计界面好看了很多，不过还是跟以前那样，页面一多就非常难找到页面，没有一个搜索功能。<br /><br /><br />　　11、以前的配置文件是分成两个文件的faces-config.xml和managed-bean.xml，现在走回原路，合成一个faces-config.xml文件。<br /><br /><br /><br />　　另外，相关的教程和Blog都增加了不少关于NetBeans6的文章，为杯水车薪的VisualWeb文档略解燃眉之急。不过我最期待的定制组件功能，并没有出现在这个版本的NetBeans上，不得不说是一个遗憾。
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/146259#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Wed, 05 Dec 2007 17:51:02 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/146259</link>
        <guid>http://liangguanhui.javaeye.com/blog/146259</guid>
      </item>
      <item>
        <title>我遇到的Hibernate使用查询缓存的一个问题</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/135413" style="color:red;">http://liangguanhui.javaeye.com/blog/135413</a>&nbsp;
          发表时间: 2007年10月25日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          这两天在优化人事系统的性能，其中一个工作就是为系统应用Hibernate的缓存。Hibernate有几个缓存：一级缓存、二级缓存、查询缓存。其中我在实现查询缓存的时候出了一些毛病，弄了我两天。<br />事情是这样的，系统有一大堆代码表，按照一般的原则，代码表当然是缓存起来用。以其中一个表为例：<br /><br /><pre name="code" class="java">/**
 * 代码抽象类
 */
@MappedSuperclass
public abstract class Code implements Serializable {
　　
　　private static final long serialVersionUID = 8945711128421853350L;
　　
　　protected long id;
　　
　　protected String code;
　　
　　protected String name;
　　
　　public Code() {
　　　　super();
　　}
　　
　　@Id
　　@GeneratedValue(strategy=GenerationType.AUTO)
　　public long getId() {
　　　　return id;
　　}

　　其它getter、setter....
　　
}

/**
 * 工资类别代码
 */
@Entity
@Table(name="C_SalaryType")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class SalaryType extends Code {
　　
　　public SalaryType() {
　　}
　　
}

/**
 * 工资级别码
 */
@Entity
@Table(name="C_SalaryLevel")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class SalaryLevel extends Code {
　　
　　public SalaryLevel() {
　　}
　　
}</pre><br /><br />可以看到我这里的所有代码都继承一个Code的base class。在选择代码缓存类型的时候，考虑到代码不是经常修改，所以选择菲严格读写，即NONSTRICT_READ_WRITE。<br />（开始的时候，我把“@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)”这句放到Code类里面，发现并不起作用，必须要把它放到具体的类里面才能起作用。不过这是题外话。）<br /><br />然后重新部署应用，登陆、查看基本信息，问题来了：第二次或者之后查看基本信息，载入需要很长时间，凭感觉比不用缓存的时候还要慢。什么原因呢？查看了一下日志，不看不知道，一看吓一条，系统输出n条下面的sql语句：<br />Hibernate: <br />　　select<br />　　　　salarytype0_.id as id77_0_,<br />　　　　salarytype0_.code as code77_0_,<br />　　　　salarytype0_.name as name77_0_ <br />　　from<br />　　　　C_SalaryType salarytype0_ <br />　　where<br />　　　　salarytype0_.id in (<br />　　　　　　?, ?, ?, ?, ?, ?, ?, ?<br />　　　　)<br />怎么回事？<br />开始我还以为是查询基本信息表的时候有问题，不过经过查证并且使用调试（netbeans的webapp调试做得还可以）终于查到是查询这个代码表“From SalaryLevel”的时候出事。<br />我大概数了一下，貌似是使用这种方法重新遍历了一遍alarytype的所有数据，查询缓存不起作用。<br />不过其它代码类倒是没有这个问题，怎么回事？我认真查了一下代码，发现除了表名、类名不同之外，所有代码的确是一样的，既然一样，何解效果不同呢？<br /><br />我又尝试改变缓存类型，结果还是一样。怪了，到底怎么回事？<br /><br />第二天，我还是没有什么头绪，难道是hibernate的bug？不过应该不大可能，其它代码都没什么问题，就几个代码有这种问题。后来突发奇想，打开了org.hibernate.cache包的debug输出（之前是warn输出），输出如下：<br />junit测试代码是<br /><pre name="code" class="java">　　public void testGetCodeList() {
　　　　for (int i = 0; i &lt; 2; i++) {
　　　　　　System.out.println("\n\nTimes: " + (i + 1));
　　　　　　service.getCodeList(SalaryType.class);
　　　　}
　　}</pre><br />测试结果是<br /><br /><br />//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////<br />SimpleContext: bind [java:comp/env/jdbc/hsDB]<br />SimpleContext: lookup [java:comp/env/jdbc/hsDB]<br />12:39:03,031 DEBUG org.hibernate.cache.CacheFactory:39 - instantiating cache region: com.dcampus.hr3.domain.code.JobType usage strategy: nonstrict-read-write<br />12:39:03,031　WARN org.hibernate.cache.EhCacheProvider:86 - Could not find configuration [com.dcampus.hr3.domain.code.JobType]; using defaults.<br />12:39:03,046 DEBUG org.hibernate.cache.EhCacheProvider:89 - started EHCache region: com.dcampus.hr3.domain.code.JobType<br />(一大堆重复上面三句)<br />12:39:05,015　INFO org.hibernate.cache.UpdateTimestampsCache:41 - starting update timestamps cache at region: org.hibernate.cache.UpdateTimestampsCache<br />12:39:05,015　WARN org.hibernate.cache.EhCacheProvider:86 - Could not find configuration [org.hibernate.cache.UpdateTimestampsCache]; using defaults.<br />12:39:05,015 DEBUG org.hibernate.cache.EhCacheProvider:89 - started EHCache region: org.hibernate.cache.UpdateTimestampsCache<br />12:39:05,031　INFO org.hibernate.cache.StandardQueryCache:52 - starting query cache at region: org.hibernate.cache.StandardQueryCache<br />12:39:05,031　WARN org.hibernate.cache.EhCacheProvider:86 - Could not find configuration [org.hibernate.cache.StandardQueryCache]; using defaults.<br />12:39:05,031 DEBUG org.hibernate.cache.EhCacheProvider:89 - started EHCache region: org.hibernate.cache.StandardQueryCache<br /><br /><br />Times: 1<br />12:39:05,781 DEBUG org.hibernate.cache.StandardQueryCache:102 - checking cached query results in region: org.hibernate.cache.StandardQueryCache<br />12:39:05,781 DEBUG org.hibernate.cache.EhCache:68 - key: sql: select salarytype0_.id as id77_, salarytype0_.code as code77_, salarytype0_.name as name77_ from C_SalaryType salarytype0_; parameters: ; named parameters: {}<br />12:39:05,781 DEBUG org.hibernate.cache.EhCache:77 - Element for sql: select salarytype0_.id as id77_, salarytype0_.code as code77_, salarytype0_.name as name77_ from C_SalaryType salarytype0_; parameters: ; named parameters: {} is null<br />12:39:05,781 DEBUG org.hibernate.cache.StandardQueryCache:107 - query results were not found in cache<br />Hibernate: <br />　　select<br />　　　　salarytype0_.id as id77_,<br />　　　　salarytype0_.code as code77_,<br />　　　　salarytype0_.name as name77_ <br />　　from<br />　　　　C_SalaryType salarytype0_<br />12:39:06,015 DEBUG org.hibernate.cache.NonstrictReadWriteCache:71 - Caching: com.dcampus.hr3.domain.code.SalaryType#1<br />12:39:06,015 DEBUG org.hibernate.cache.NonstrictReadWriteCache:71 - Caching: com.dcampus.hr3.domain.code.SalaryType#2<br />12:39:06,015 DEBUG org.hibernate.cache.NonstrictReadWriteCache:71 - Caching: com.dcampus.hr3.domain.code.SalaryType#3<br />(重复至1546)<br />12:39:07,312 DEBUG org.hibernate.cache.StandardQueryCache:73 - caching query results in region: org.hibernate.cache.StandardQueryCache; timestamp=4887704147902464<br /><br /><br />Times: 2<br />12:39:07,328 DEBUG org.hibernate.cache.StandardQueryCache:102 - checking cached query results in region: org.hibernate.cache.StandardQueryCache<br />12:39:07,328 DEBUG org.hibernate.cache.EhCache:68 - key: sql: select salarytype0_.id as id77_, salarytype0_.code as code77_, salarytype0_.name as name77_ from C_SalaryType salarytype0_; parameters: ; named parameters: {}<br />12:39:07,328 DEBUG org.hibernate.cache.StandardQueryCache:156 - Checking query spaces for up-to-dateness: [C_SalaryType]<br />12:39:07,328 DEBUG org.hibernate.cache.EhCache:68 - key: C_SalaryType<br />12:39:07,328 DEBUG org.hibernate.cache.EhCache:77 - Element for C_SalaryType is null<br />12:39:07,328 DEBUG org.hibernate.cache.StandardQueryCache:117 - returning cached query results<br />12:39:07,343 DEBUG org.hibernate.cache.NonstrictReadWriteCache:41 - Cache lookup: com.dcampus.hr3.domain.code.SalaryType#1<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#1<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#1 is null<br />12:39:07,343 DEBUG org.hibernate.cache.NonstrictReadWriteCache:49 - Cache miss<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#2<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#2 is null<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#3<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#3 is null<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#4<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#4 is null<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#5<br />12:39:07,343 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#5 is null<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#6<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#6 is null<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#7<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#7 is null<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:68 - key: com.dcampus.hr3.domain.code.SalaryType#8<br />12:39:07,359 DEBUG org.hibernate.cache.EhCache:77 - Element for com.dcampus.hr3.domain.code.SalaryType#8 is null<br />Hibernate: <br />　　select<br />　　　　salarytype0_.id as id77_0_,<br />　　　　salarytype0_.code as code77_0_,<br />　　　　salarytype0_.name as name77_0_ <br />　　from<br />　　　　C_SalaryType salarytype0_ <br />　　where<br />　　　　salarytype0_.id in (<br />　　　　　　?, ?, ?, ?, ?, ?, ?, ?<br />　　　　)<br />(重复上面Time2后面的内容至1546)<br /><br />////////////////////// end ////////////////////////////////////////////////////////////////////<br /><br />从上面可以看到，原来是每次查询都不能击中缓存。怎么会击不中？我看到1546，突然想起我的ehcache的配置是默认1000个<br /><br /><pre name="code" class="java">&lt;ehcache>
　　&lt;defaultCache
　　　　　　maxElementsInMemory="1000"
　　　　　　timeToIdleSeconds="600"
　　　　　　timeToLiveSeconds="1800"
　　　　　　eternal="false"
　　　　　　overflowToDisk="false"
　　　　　　diskPersistent="false"
　　　　　　memoryStoreEvictionPolicy="LRU"
　　　　　　/>
&lt;/ehcache></pre><br />而那些代码的数目超过一千个，所以每次查询都不能命中。把1000改为10000，测试通过。
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/135413#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Thu, 25 Oct 2007 13:06:22 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/135413</link>
        <guid>http://liangguanhui.javaeye.com/blog/135413</guid>
      </item>
      <item>
        <title>浅谈Tomcat6使用NIO后对ThreadLocal的影响</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/127930" style="color:red;">http://liangguanhui.javaeye.com/blog/127930</a>&nbsp;
          发表时间: 2007年09月28日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          　　很早就听说tomcat6使用nio了，这几天突然想到一个问题，使用nio代替传统的bio，ThreadLocal岂不是会存在冲突？<br />　　<br />　　<br />　　<br />　　首先，何谓nio？<br />　　<br />　　如果读者有socket的编程基础，应该会接触过堵塞socket和非堵塞socket，堵塞socket就是在accept、read、write等IO操作的的时候，如果没有可用符合条件的资源，不马上返回，一直等待直到有资源为止。而非堵塞socket则是在执行select的时候，当没有资源的时候堵塞，当有符合资源的时候，返回一个信号，然后程序就可以执行accept、read、write等操作，这个时候，这些操作是马上完成，并且马上返回。而windows的winsock则有所不同，可以绑定到一个EventHandle里，也可以绑定到一个HWND里，当有资源到达时，发出事件，这时执行的io操作也是马上完成、马上返回的。一般来说，如果使用堵塞socket，通常我们时开一个线程accept socket，当有socket链接的时候，开一个单独的线程处理这个socket；如果使用非堵塞socket，通常是只有一个线程，一开始是select状态，当有信号的时候马上处理，然后继续select状态。<br />　　<br />　　按照大多数人的说法，堵塞socket比非堵塞socket的性能要好。不过也有小部分人并不是这样认为的，例如Indy项目（Delphi一个比较出色的网络包），它就是使用多线程＋堵塞socket模式的。另外，堵塞socket比非堵塞socket容易理解，符合一般人的思维，编程相对比较容易。<br />　　<br />　　nio其实也是类似上面的情况。在JDK1.4，sun公司大范围提升Java的性能，其中NIO就是其中一项。Java的IO操作集中在java.io这个包中，是基于流的阻塞API（即BIO，Block IO）。对于大多数应用来说，这样的API使用很方便，然而，一些对性能要求较高的应用，尤其是服务端应用，往往需要一个更为有效的方式来处理IO。从JDK 1.4起，NIO API作为一个基于缓冲区，并能提供非阻塞O操作的API（即NIO，non-blocking IO）被引入。<br />　　<br />　　BIO与NIO一个比较重要的不同，是我们使用BIO的时候往往会引入多线程，每个连接一个单独的线程；而NIO则是使用单线程或者只使用少量的多线程，每个连接共用一个线程。<br />　　<br />　　<br />　　<br />　　这个时候，问题就出来了：我们非常多的java应用是使用ThreadLocal的，例如JSF的FaceContext、Hibernate的session管理、Struts2的Context的管理等等，几乎所有框架都或多或少地应用ThreadLocal。如果存在冲突，那岂不惊天动地？<br />　　<br />　　后来终于在Tomcat6的文档（http://tomcat.apache.org/tomcat-6.0-doc/aio.html）找到答案。根据上面说明，应该Tomcat6应用nio只是用在处理发送、接收信息的时候用到，也就是说，tomcat6还是传统的多线程Servlet，我画了下面两个图来列出区别：<br />　　<br />　　<br />　　<br />　　tomcat5：客户端连接到达 -> 传统的SeverSocket.accept接收连接 ->  从线程池取出一个线程 -> 在该线程读取文本并且解析HTTP协议 -> 在该线程生成ServletRequest、ServletResponse，取出请求的Servlet -> 在该线程执行这个Servlet -> 在该线程把ServletResponse的内容发送到客户端连接 -> 关闭连接。<br />　　<br />　　我以前理解的使用nio后的tomcat6：客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议（单线程） -> 生成ServletRequest、ServletResponse，取出请求的Servlet -> 直接在本线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。<br />　　<br />　　实际的tomcat6：客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议（单线程） -> 生成ServletRequest、ServletResponse，取出请求的Servlet -> 从线程池取出线程，并在该线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。<br />　　<br />　　<br />　　<br />　　从上图可以看出，BIO与NIO的不同，也导致进入客户端处理线程的时刻有所不同：tomcat5在接受连接后马上进入客户端线程，在客户端线程里解析HTTP协议，而tomcat6则是解析完HTTP协议后才进入多线程，另外，tomcat6也比5早脱离客户端线程的环境。<br />　　<br />　　实际的tomcat6与我之前猜想的差别主要集中在如何处理servlet的问题上。实际上即使抛开ThreadLocal的问题，我之前理解tomcat6只使用一个线程处理的想法其实是行不同的。大家都有经验：servlet是基于BIO的，执行期间会存在堵塞的，例如读取文件、数据库操作等等。tomcat6使用了nio，但不可能要求servlet里面要使用nio，而一旦存在堵塞，效率自然会锐降。<br />　　<br />　　<br />　　<br />　　<br />　　所以，最终的结论当然是tomcat6的servlet里面，ThreadLocal照样可以使用，不存在冲突。<br />　　<br />　　<br />　　<br />　　<br />　　<br />　　<br />　　
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/127930#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 28 Sep 2007 15:41:01 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/127930</link>
        <guid>http://liangguanhui.javaeye.com/blog/127930</guid>
      </item>
      <item>
        <title>Visual Web Pack 私人经验（待续）</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/108577" style="color:red;">http://liangguanhui.javaeye.com/blog/108577</a>&nbsp;
          发表时间: 2007年08月03日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          　　以下都是在netbeans5.5.1 + visual web pack5.5.1 + tomcat5.5.17开发程序时的私人经验（引用请注明出处，本人email是liangguanhui@163.com）。<br />　　为了说明上的方便，Visual Web Pack将会简称为vwp。<br /><br /><br /><br />　　1、在安装了vwp之后，netbeans的帮助里就会包含vwp的帮助，如果你安装的时中文版，帮助还是中文的，非常实用，所以建议有时间看看这些帮助（方法是按F1，然后把左边的侧栏拖到最底）。<br /><br /><br />　　2、NetBeans是一个以Swing为GUI的软件，所以可以非常方便地实现换肤，方法是在${NetBeans_Home}/etc/netbeans.conf中的netbeans_default_options参数后增加“--laf 外观类类名”（没有双引号），要注意的时，我曾经试过换成金属主题（即增加--laf javax.swing.plaf.metal.MetalLookAndFeel），在拖放多选框组和单选框组的时候，设计页面就会出错，所以这个功能要慎用。<br /><br /><br />　　3、以前我们使用Sun JSF RI的时候，jsf页面的访问扩展名都是face，但在vwp中，还是jsp，只不过是放在${context_path}/faces下而已，所以我们可以通过这个路径直接访问。例如新建工程时默认的Page1.jsp，我们可以通过“${网址}/${context}/faces/Page1.jsp”访问。<br /><br /><br />　　4、vwp的布局由于没有类似Html的table，所以非常麻烦。vwp页面的默认布局是网格布局，这个布局有点类似Dreamver的绝对定位层（Div）布局，我们一般都不推荐使用这种布局，所以，要么是使用流布局，要么是在页面增加一个什么style属性都没有的网格面板（HtmlPanelGrid）组件。我比较喜欢后者。到目前位置，我的布局一般都是使用多个网格面板嵌套来实现。网格面板组件有个columns属性，是表示这个网格面板的列的数量，通过控制这个来达到布局目的。<br />　　不得不提的一种布局是，我们很经常会用到key-value的布局：一共有四列，第一、三列是说明，二、四列是值，就像<br /><br />　　　　姓名：张三　　　　出生地：1984-02-29<br />　　　　籍贯：佛山　　　　　民族：汉族<br />　　　　性别：男　　　　　　身高：190CM<br /><br />　　对于这种布局，我们可以通过这样实现：增加一个网格面板组件，把columns设为4，然后把12个Label属性按顺序放到这个网格面板里，然后修改Label的相应的Text即可。<br />　　但有两个问题是需要解决：（1）key的对齐，即第一、三列的垂直对齐；（2）第二、三列之间的空隙。对于（1），可以新建一个“层叠样式表”（方法是在“web页”的resources目录右击，选择“新建”->“层叠样式表”），然后在里面增加一个styleClass，把这个styleClass的文本对齐变成右对齐，并且给一个固定的长度，例如<br /><pre name="code" class="java">　　　　.commonFormKey {
　　　　　　text-align: right;
　　　　　　width: 150px
　　　　}</pre>　　然后把第一、三列的label的styleClass设成这个styleClass，并且在jsp页面引入这个css文件即可。对于（2），可以在“张三”这个Label处增加一个网格面板（HtmlPanelGrid）组件，并且把张三移到这个网格面板里面，最后把网格面板的宽度设为一个稍大的值即可。<br />　　布局是一个非常复杂的话题，也是vwp的弱项，希望在以后的版本能多多加强。<br /><br /><br />　　5、我们都知道jsf组件都有一个immediality（立即）属性，这个属性的作用是…，由于非常复杂，还是不说了。我们一般是把“返回”按钮的这个属性设为true。而在vwp则有了另外一个实现方法：虚拟表单。具体的使用可以查看vwp的帮助，这一块讲得还是比较详尽的。虚拟表单是vwp的特色，最好能掌握好。（需要提醒一下，立即属性为true并不是意味着跳过验证到达Action，读者最好能弄清楚）<br /><br /><br />　　6、vwp有不少bug，其中有一个是如果你在左边的“概要”这里把组件拖动的比较频繁，而且用了复制、粘贴等操作，则会发现，设计界面的组件顺序跟运行时有很大的出入。解决办法是点击设计界面上面的“刷新”或者直接在jsp页面里作修改。<br /><br /><br />　　7、vwp的设计时有不少地方是有缓存的，例如，你在你的页面的java文件里增加了一个getter方法，但在设计界面没有找到这个getter，那十有八九是缓存的问题，重启Netbeans就可以解决问题。根据我的个人经验，发现有些很古怪的错误，重启netbeans会搞定大部分这种错误。<br /><br /><br />　　8、我们都知道jsf有六个生命周期，这个是jsf的重点，也是难点，必须要掌握。而vwp在这个基础上作了一些修改，并且向用户暴露了4个方法：init、preprocess、prerender、destroy。具体的作用可以直接看页面上的javadoc注释。<br /><br /><br />　　9、以前在jsp年代，页面跳转非常容易，要么是redirect，要么是用dispatch；而在jsf，其实也有类似的页面跳转，方法是FacesContext.getCurrentInstance().getExternalContext().redirect(url)，这个方法会改变vwp的页面的4个过程的流程，即直接跳到destroy，然后退出。<br /><br /><br />　　10、怎么获取一个输入框的输入值？很简单，直接textField1.getText()。但如果有十几到几十个组件呢？而且非常多页面页呢，你还愿意这样逐个逐个getText吗？<br />　　我觉得“取值”是vwp的又一弱项，竟然是需要逐个getText，真是不可想象，真有点像以前jsp的getParameter。当然，我们可以写个helper来完整这些繁琐的工作，至于具体的代码我就不贴出来了，无非就是用反射来获取属性，然后配合UIComponent的getChildre来实现。<br /><br /><br />　　11、我们在修改页里，一般都会先在组件里填好原来的值，然后用户修改，提交，然后在数据库里修改。在vwp里，什么时候填原来的数据呢？有两个方法：（1）每次在prerender都填写一次数据；（2）在prerender，判断是否postback（方法是调用isPostBack()），如果不是，就填充数据。<br />　　至于填写数据，如果组件很多，也是一件麻烦的事情，可以写一个helper来实现。方法跟上面的取值类似。不过要注意，在jsf1.1，在页面第一次请求的时候，由于页面还没有渲染，所以如果你调用UIComponent的getChildre是得不到子组件的，需要对整个页面进行反射，然后调用setter。<br /><br /><br />　　12、我们应该都知道，vwp实际上是把sun的jsf ri，增加一个jsf设计时，然后增加一些其它额外组件组成的。为什么要有一个设计时呢？设计时实际上是vwp在页面的时候调用的东西。<br />　　自定义jsf组件在网上的教程非常多，读者可以上网去搜一下，简单的jsf组件还是比较容易实现的，无非就是三个类（component、render、tag）加一个tld标签说明文件。而vwp自定义组件则复杂一些，除了要这些之外，还需要设计时，否则，就只能用、不能设计。这句话有点拗口，意思就是说，如果没有设计时，就只能在jsp里写标签，在设计界面是看不到设计效果的，但运行的时候又没有什么问题。<br />　　至于设计时的开发，由于sun没有公布相关的资料，所以我也不知道，只知道是一些BeanInfo和配置问题。Netbeans.org好像有一个教程，可以下来看看。<br /><br /><br />　　13、（重） 不知读者有没有留意，如果你使用了日历（Calendar）组件，生成的html页面的代码都会暴涨，你可以看看生成的html代码，没错，生成很多html代码，大概是每个日历组件25k左右，也就是说，如果你页面有10个这种组件，页面大小就不少于250k啦，非常惊人（我实在想不出sun怎么会作出这么糟糕的组件来）。我在好像是vwp开发人员的blog里看到说对这个组件要作出修改，不知现在改好没有。当然，我们不能干等，自己来搞定好了。<br />　　在BlugPrint的ajax组件库（可以通过“工具”->“更新中心”来升级这个组件库）里有个popupcalendar的组件，这个日历组件生成的html代码比原来那个calendar少多了，不过有两个问题：（1）这个ajax组件库现在的版本好像是0.1，远远不能用于生产；（2）这个popupcalendar组件不能在IE里遮住Select组件（这个是IE的bug，做过设计都应该知道，但又必须要解决，毕竟用IE的人很多啊）。<br />　　这个不能用了，怎么办？改！对原来的Calendar组件进行修改。其实这种说法不准确，应该说是对Calendar的渲染类进行修改。由于sun没有提供组件的渲染器的源代码，所以我们只能反编译这个类。类在webui.jar文件，全名是com.sun.rave.web.ui.renderer. CalendarRenderer。从这个类的反编译代码可以看到Calendar是怎么画出来的。说实话，我觉得Calendar的实现还算优雅，达到组件重用的效果，但生成的html代码实在太大了，没戏。<br />　　我们还要找一款好的日期选择js组件，我找的是jscalendar，这款日历非常不错：跨浏览器、多皮肤、可拖动等等。<br />　　修改现在开始，新建一个类，是com.sun.rave.web.ui.renderer. CalendarRenderer，然后把反编译的代码copy过来，删除除getStyles的所有方法，然后重写encodeEnd方法。在这个方法里画一个输入框、一个图片按钮，并且把jscalendar装进去，就搞定了。具体代码我就不贴出来了，读者自己尝试。<br /><br /><br />　　14、（重） 我们都知道vwp是提供一揽子服务，即把我们以前的诸多分层都统统包揽起来了。有没有方法可以实现以前我们的分层架构呢？sure可以。<br />　　Netbeans.org提供了一个使用hibernate的例子，其实就是分层的一个例子，不过说实话，那个例子说得乱七八糟的，我到现在还没有看完。<br />　　闲话少说，这里我们假设是使用jsf＋spring＋hibernate。vwp分层其实很简单：把使用CachedRowSetDataProvider的地方都换成调用service的方法就可以了。对于数据的操作无非就是四个：CRUD，即增删改查。我们回顾一下我们系统的功能，发现其实是这样的：列表->选择记录->操作（删除或者修改）。其实就是列表那里不能直接用到service的方法，为什么？因为列表时用到Table组件，而这个组件需要一个TableDataProvider来提供数据，所以我们的工作就很简单了，找一个合适的TableDataProvider。<br />　　在Netbeans.org的那个使用Hibernate的例子，使用的是ObjectListDataProvider，我开始也是使用这个，后来发现了一个非常严重的问题，所有继承AbstraceTableDataProvider的DataProvider都存在一个非常严重的问题：AbstraceTableDataProvider是用顺序号（即1、2、3、4….）来标识不同的记录。<br />　　由于vwp默认是使用CachedRowSetDataProvider，数据默认保存在SessionBean里，所以每个人都有一份数据拷贝，这个问题也就不存在。但我们都知道，数据拷贝放在SessionBean里的做法弊端非常大（具体就不说了，读者可以慢慢思考）。当使用ObjectListDataProvider是，数据我们一般都是即时从数据库读出，这样，问题就出来了，假如A用户列表一些记录，如果B用户此时删掉了第二条记录，这个时候，A用户在选择第二条记录，点击删除，由于原来的第2记录已经删掉了，所以就会删掉第3条记录。<br />　　这个问题无法解决，所以我们必须自己写一个TableDataProvider，从根本上改造这个DataProvider体系。具体代码我也不贴出来了，读者自己摸索。（提示一下吧：重写一个TableDataProvider，还要写一个RowKey，最后还要稍微改一下TableRowGroup这个组件的代码）<br /><br /><br />　　15、页面间参数传递。这个其实是jsf的一个比较麻烦的问题。以前在jsp年代，直接在地址后面增加参数即可，但在jsf由于都是RequestDispatch的页面跳转，所以地址的作用已经不大。现在以例子说明问题，假如有两个页面A.jsp和B.jsp，A.jsp有两个按钮，text分别是“click A”和“click B”，都是跳转到B.jsp，请问怎么实现点击这些按钮，在B.jsp显示按钮的text呢？<br />　　vwp提供的例子是把数据放到SessionBean里，然后跳转。（说实话，我实在有点受不了sun动不动就把数据往session里塞）<br />　　可能很多人马上想到一个方法：request，是的，我们可以在这两个按钮的action方法里，往requestMap添加数据，然后在B.jsp取出来。不过这个方法有个弊端：就是如果在B.jsp里发生一些action操作，例如，点击了一个按钮，这时由于是另外一个request里，所以原来保存的数据就没有了。所以我们要想办法保存这个数据。<br />　　保存的方法有很多，不过在vwp提供了一种比较方便的方法：saveData和retrieveData。如果我没有理解错的话，saveData实际上是把数据数据保存到jsf view里，我们可以从它的源代码可以看到<br /><pre name="code" class="java">　　　　private static final String DATA_KEY = "com.sun.rave.web.ui.appbase.DATA";
　　　　public void saveData(String key, Object data) {
　　　　　　Map map = (Map) getFacesContext().getViewRoot().getAttributes().get(DATA_KEY);
　　　　　　if (map == null) {
　　　　　　　　map = new HashMap();
　　　　　　　　getFacesContext().getViewRoot().getAttributes().put(DATA_KEY, map);
　　　　　　}
　　　　　　map.put(key, data);
　　　　}</pre>　　我们可以在B.jsp的init方法里检查request里是否存在某个key，存在，则把它saveData。数据保存后，只要不跳出这个页面，数据都可以从retrieveData里获得。<br />　　另外一种方法，是使用HiddenField，我们在A.jsp里获取B.jsp的HiddenField，并且设置它的值，然后跳转。即在A.jsp的button的Action里执行（假如那个HiddenField的name是buttonText）<br /><pre name="code" class="java">　　　　setValue("#{B.buttonText.text}", button1.getText());
　　　　return "B";</pre><br />　　这样我们在B页面里就可以随时随地访问buttonText里的值。<br /><br /><br />　　16、vwp其实是有两套界面的，分别对应J2EE1.4和J2EE1.5，如果你使用的是vwp5.5.1，在新建工程的时候可以发现有两个服务器可以选：tomcat5.5.17和sun Application 9，由于tomcat5.5还不支持J2EE1.4，所以你会发现当你选了tomcat5.5.17这个服务器的时候，只能选J2EE1.3和1.4，尔sun application 9就有1.5可选。这两套界面外观不同，但代码、组织架构有很多相似的地方，另外，J2EE1.5那套好像是用了标注来写组件代码的。J2EE1.4的包名是com.sun.rave.web.ui，1.5是com.sun.webui.jsf，另外，组件标签命名也不同了，1.4是 xmlns:ui="http://www.sun.com/web/ui"，而1.5是 xmlns:webuijsf="http://www.sun.com/webui/webuijsf"。<br />　　由于一般人都比较喜欢用tomcat，所以一般来说对应J2EE1.4的那套界面用得比较多。
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/108577#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 03 Aug 2007 14:58:50 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/108577</link>
        <guid>http://liangguanhui.javaeye.com/blog/108577</guid>
      </item>
      <item>
        <title>多线程HashMap的读取是否需要同步？</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/103118" style="color:red;">http://liangguanhui.javaeye.com/blog/103118</a>&nbsp;
          发表时间: 2007年07月20日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          多线程HashMap的读取是否需要同步？这个问题一直困扰着我，虽然Collections提供了同步的map，但我一般都是直接使用HashMap，读的时候不同步，写的时候才同步。下面是我从HashMap里截取的读的源代码，估计读的时候应该是不用同步的。其他的Map我没有仔细看，但估计应该也是差不多。<br /><br /><pre name="code" class="java">    public Object get(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry e = table[i]; 
        while (true) {
            if (e == null)
                return e;
            if (e.hash == hash && eq(k, e.key)) 
                return e.value;
            e = e.next;
        }
    }

    public boolean containsKey(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry e = table[i]; 
        while (e != null) {
            if (e.hash == hash && eq(k, e.key)) 
                return true;
            e = e.next;
        }
        return false;
    }

    static Object maskNull(Object key) {
        return (key == null ? NULL_KEY : key);
    }

    static int hash(Object x) {
        int h = x.hashCode();

        h += ~(h &lt;&lt; 9);
        h ^=  (h >>> 14);
        h +=  (h &lt;&lt; 4);
        h ^=  (h >>> 10);
        return h;
    }

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    static boolean eq(Object x, Object y) {
        return x == y || x.equals(y);
    }
</pre>
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/103118#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Fri, 20 Jul 2007 09:35:28 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/103118</link>
        <guid>http://liangguanhui.javaeye.com/blog/103118</guid>
      </item>
      <item>
        <title>有一个关于Hibernate配置的问题</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/96960" style="color:red;">http://liangguanhui.javaeye.com/blog/96960</a>&nbsp;
          发表时间: 2007年07月02日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          我是使用Annontation mapping的，<br />有一个父类叫TeacherRefBase，定义如下：<br /><pre name="code" class="java">@MappedSuperclass
class TeacherRefBase implements Serializable {

    ..........
   
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public long getId() {
        return id;
    }

    @ManyToOne(targetEntity=Teacher.class)
    @JoinColumn(name="teacherId")
    @NotNull
    public Teacher getTeacher() {
        return teacher;
    }
}</pre>这里把TeacherRefBase join到 Teacher。<br />然后在来一个<br /><br /><pre name="code" class="java">@MappedSuperclass
class TeacherRef implements Serializable {

    ..........
   
    public String getName() {
        return name;
    }
   
    public int getAge() {
        return age;
    }

    @OneToOne(targetEntity=Teacher.class)
    @JoinColumn(name="teacherId")
    @NotNull
    public Teacher getTeacher() {
        return teacher;
    }
}</pre><br />最后来个<br /><pre name="code" class="java">@Entity
@Table(name="WorkHistory")
public class WorkHistory extends TeacherRef {
    
    ........

}</pre><br />这个时候启动的时候就报错<br />Duplicate property mapping of teacher found in WorkHistory <br /><br />请问这个问题 怎么解决？
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/96960#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Mon, 02 Jul 2007 18:20:18 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/96960</link>
        <guid>http://liangguanhui.javaeye.com/blog/96960</guid>
      </item>
      <item>
        <title>如何关闭一个正在accept的ServerSocket？</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/65801" style="color:red;">http://liangguanhui.javaeye.com/blog/65801</a>&nbsp;
          发表时间: 2007年03月27日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          加入一个ServerSocket正在另一个线程堵塞accept，那如何停止accept或者关闭Socket？
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/65801#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 27 Mar 2007 12:52:25 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/65801</link>
        <guid>http://liangguanhui.javaeye.com/blog/65801</guid>
      </item>
      <item>
        <title>应该选用哪个开源的JMS provider？</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/59499" style="color:red;">http://liangguanhui.javaeye.com/blog/59499</a>&nbsp;
          发表时间: 2007年03月13日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          我现在知道的比较好的JMS provider有OpenJMS、ActiveMQ、mom4j等，不知各自有什么优缺点？<br />我听说OpenJMS用的是最多的，不过我在这个论坛的一个帖子里看到很多人推荐ActiveMQ，我都觉得糊涂了。
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/59499#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 13 Mar 2007 09:38:28 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/59499</link>
        <guid>http://liangguanhui.javaeye.com/blog/59499</guid>
      </item>
      <item>
        <title>NetBeans+VisualWebPack到目前为止发现的问题</title>
        <author>liangguanhui</author>
        <description>
          <![CDATA[
          <br/>
          作者: <a href="http://liangguanhui.javaeye.com">liangguanhui</a>&nbsp;
          链接：<a href="http://liangguanhui.javaeye.com/blog/56178" style="color:red;">http://liangguanhui.javaeye.com/blog/56178</a>&nbsp;
          发表时间: 2007年02月27日
          <br/><br/>
          声明：本文系JavaEye网站发布的原创博客文章，未经作者书面许可，严禁任何网站转载本文，否则必将追究法律责任！
          <br/><br/>
          （可能还有人不知道VisualWebPack是什么，<strong>VisualWebPack</strong>其实是<strong>sun java studio creator2</strong>在<strong>netbeans</strong>的免费插件&lt;<a href="http://www.netbeans.org/kb/55/vwp-index_zh_CN.html" target="_blank">http://www.netbeans.org/kb/55/vwp-index_zh_CN.html</a>>，主要用于<strong><u>JSF</u></strong>开发，其拖拉式组件开发是其一个主要卖点。）<br /><br /><br /><strong>首先需要先说说JSF</strong><br /><br />1、尽管JSF是标准，但不见得标准就是好的。君不见EJB2的状况？像JSF这个复杂（指的是实现的复杂）的一个框架（相对于jsp、struts等），我怀疑它是否是另外一个ejb。而且虽然说JSF是标准，但每个厂商自己的实现多多少少都会有一些扩展（通常是组件的扩展），一个JSF程序一般很难在不同厂商的JSF实现中切换。<br /><br />2、JSF的生命周期相对jsp来说比较复杂，虽然VisualWebPack已经简化了，但还是复杂，有很多时候我想取一个值（大多数情况是动态值），但是没有取到，我怀疑有一些bug（当然不排除我自己技术没到家），记得之前的项目大部分时间就是耗在这个取值的问题上。<br /><br />3、JSF的组件设计如果不是使用IDE的拖拉式开发，工作量反而比直接使用JSP、Struts要多。IDE在JSF开发中显得尤为重要。<br /><br />4、JSF的错误提示太多无用的信息，不能对错误迅速定位。<br /><br />5、在ajax大行其道的今天，JSF这种把一切都包揽在server端的做法，我还真是有所保留！虽然有Ajax4JSF项目，但这个项目好像不是标准来的。<br /><br />6、相对与ASP.NET和Tapestry，JSF对HTML的侵入性比较大，令Dreamver这些优秀的网页设计工具无用武之地。<br /><br /><br /><br /><strong>现在来说说NetBeans+VisualWebPack</strong>：<br /><br />1、项目、资料、社区的支持度：现在使用JSF开发的项目还比较少，用NetBeans+VisualWebPack的就更少了。同时，由于VisualWebPack的出现比较晚，相关资料实在太少了，就算它的前身sun Java Studio Creator2的资料也是很少。缺少成熟的、稳定的、可持续的资料、社区的支持，如果出了问题，还真不知如何解决呢。<br /><br />2、团体合作：（1）与美工比较难配合。我们很难说服一个用惯Dreamvwear的美工转用这个VisualWebPack。其实也是不可能的，你没有理由要求一个美工去学习JSF、VisualWebPack的标签，可能有人会认为，即使只是使用VisualWebPack的拖拉式也一样可以做出漂亮的界面。不过正如没有一个美工不懂HTML的道理一样，不懂JSF标签的美工，做界面总会有点美中不足。（2）由于NetBeans+VisualWebPack对某些文件极为敏感，很多时候，把一个工程打包到另外一个机器的时候，往往不能打开；有时候还要解决包引用的问题（NetBeans说这个是它的优点，但我反而觉得是一个不足）；更加不用说使用cvs版本控制了。到目前为止，VisualWebPack给我的感觉就是很难做大项目。<br /><br />3、IDE的问题：不得不说，VisualWebPack还有很多bug，功能比较弱，组件偏少，而且耗资源很大，速度慢得有点受不了；跟Visual Studio相比，感觉还是有相当的差距。JSF本来对IDE的依赖就比较大，VisualWebPack的不足无疑是雪上加霜！<br /><br />4、组件性开发：VisualWebPack的拖拉式组件开发从表面上看好像是很方便，不过用起来却是诸多不足，如果某一个功能可以有现成的组件实现，那倒很方便，如果没有，那是一件很痛苦的事情（同时由于VisualWebPack组件相对不足，令这种痛苦更加升级）。桌面程序不同于web程序，一般人对他们都不会有额外的美工要求，基本就是堆上组件就可以了。而web就不同，情况要复杂得多，经常有千奇百怪的要求，这种要求，通常用javascript实现可能比较容易，但由于VisualWebPack偏重于server端以及生命周期的限制，注定它跟客户端的javascript比较难结合，换言之，web的特殊效果也就很难实现。比较常见的是菜单、IFrame等。<br /><br />5、与旧有项目结合：我们以前已经习惯了spring+hibernate做底层，现在发现VisualWebPack跟他们集成在一起好像问题比较大（或者说，处理系统分层比较麻烦）。例如，VisualWebPack的表格组件，对数据库的操作都推荐使用那个DataProvider。到目前为止，我们还没有找到集成的方法（还望高人指导）。另外，VisualWebPack里处理数据库的DataProvider用得最多得是CachedDataProvider，这个东西也真够古怪的，sun默认的例子是把数据缓存到Session里面。我真搞不懂，为什么sun那么喜欢把数据缓存到Session里面。<br /><br />6、历史的倒退：（1）JSF有值绑定和控件绑定两种绑定方法（不知是不是这样叫），VisualWebPack默认是控件绑定的，在取得用户输入值得时候，我发现这是一个倒退，因为我还是需要一个一个控件调用getText()，这种操作我觉得跟request.getParameter倒是没有什么区别（可能就是增加了验证和类型转换吧）。（2）JSF刚出来的时候就嘲笑Struts的action必须要继承Action，因为JSF的managed bean是一个普通的java类（POJO）。但VisualWebPack现在的managed bean却走回struts的旧路，需要集成AbstrackPageBean。<br /><br />7、风险成本：无论是从人员贮备、资源文档、厂商支持、安全稳定来说，相当多公司使用VisualWebPack基本都是亮红灯的。风险成本不容忽视！<br /><br />8、其余问题：（1）VisualWebPack基本上是抛弃jsf的标准组件而使用自己的一套组件，这样多多少少会增加一些学习成本（不过这点也说不上是缺点）。（2）jsp文件对应的java文件，里面有一大堆的getter/setter，把源代码搞得很乱，同时，很难从IDE的“成员试图”中迅速定位。（3）NetBeans由于是使用Swing，字体显得比较丑。同时，字体边缘会有一些模糊。（4）有时候，修改了jsp文件或者java文件后，可视化设计就不能使用，由于提示不足，很难知道错在哪里，有时甚至需要重建页面。<br /><br /><br />谈就谈到这里，希望高手们都来说说自己的观点，并指出我的不足，谢谢<br /><br /><img src="http://www.javaeye.com/topics/download/17906154-1d11-4e79-9b3d-2217e32c2429?disposition=jpeg" />
          <br/>
          <span style="color:red;">
            <a href="http://liangguanhui.javaeye.com/blog/56178#comments" style="color:red;">本文的讨论也很精彩，浏览讨论>></a>
          </span>
          <br/><br/><br/>
          <span style="color:#E28822;">JavaEye推荐</span>
          <br/>
          <ul class='adverts'><li><a href='/adverts/41' target='_blank'><span style="color:red;font-weight:bold;">北京: 千橡集团暨校内网诚聘软件研发工程师</span></a></li><li><a href='/adverts/106' target='_blank'><span style="color:blue;font-weight:bold;">JavaEye问答大赛开始了！ 从6月23日 至 7月6日，奖品丰厚 ！</span></a></li><li><a href='/adverts/97' target='_blank'><span style="color:blue;font-weight:bold;">Oracle专区上线，有Oracle最新文章，重要下载及知识库等精彩内容，欢迎访问。</span></a></li><li><a href='/adverts/42' target='_blank'><span style="color:red;font-weight:bold;">搜狐网站诚聘Java、PHP和C++工程师</span></a></li><li><a href='/adverts/92' target='_blank'><span style="color:red;font-weight:bold;">快来参加7月17日在成都举行的SOA中国技术论坛</span></a></li></ul>
          <br/><br/><br/>
          ]]>
        </description>
        <pubDate>Tue, 27 Feb 2007 11:15:11 +0800</pubDate>
        <link>http://liangguanhui.javaeye.com/blog/56178</link>
        <guid>http://liangguanhui.javaeye.com/blog/56178</guid>
      </item>
  </channel>
</rss>