欧美一区二区三区婷婷月色,久久视频免费观看,99精品久久只有精品http://www.aygfsteel.com/yongboy/category/48044.html記錄工作/學習的點點滴滴。zh-cnMon, 01 Jun 2015 21:43:57 GMTMon, 01 Jun 2015 21:43:57 GMT60Servlet 3.0筆記之文件下載的那點事情http://www.aygfsteel.com/yongboy/archive/2012/01/19/369577.htmlnieyongnieyongThu, 19 Jan 2012 02:21:00 GMThttp://www.aygfsteel.com/yongboy/archive/2012/01/19/369577.htmlhttp://www.aygfsteel.com/yongboy/comments/369577.htmlhttp://www.aygfsteel.com/yongboy/archive/2012/01/19/369577.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/369577.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/369577.html使用Servlet 3.0 提供文件下載,當然了任何Servlet版本,都可以做到,這里僅僅作為知識的積累。下面貼出代碼,防止忘卻。

一。常規方式文件下載示范


很普通,簡單易用,不多說。一般情況下,夠用了。

二。偽零拷貝(zero copy)方式文件下載示范

變化不大,關鍵代碼在于:利用
FileChannel.transferTo(long position, long count, WritableByteChannel target)
方法達到零拷貝(zero copy)目的。把HttpServletResponse的輸出對象(ServletOutputStream)利用Channels.newChannel(OutputStream out)工具,構建一個WritableByteChannel對象而已。
OutputStream out = response.getOutputStream();
WritableByteChannel outChannel = Channels.newChannel(out);
測試代碼:

心存疑慮的是,這個是偽零拷貝方式實現。查看一下Channels.newChannel的源碼:
public static WritableByteChannel newChannel(final OutputStream out) {
if (out == null) {
throw new NullPointerException();
}

if (out instanceof FileOutputStream &&
FileOutputStream.class.equals(out.getClass())) {
return ((FileOutputStream)out).getChannel();
}

return new WritableByteChannelImpl(out);
}
因為輸入的方法參數為ServletOutputStream類型實例,因此只能返回一個新構建的WritableByteChannelImpl對象。具體構建:
private static class WritableByteChannelImpl
extends AbstractInterruptibleChannel // Not really interruptible
implements WritableByteChannel
{
OutputStream out;
private static final int TRANSFER_SIZE = 8192;
private byte buf[] = new byte[0];
private boolean open = true;
private Object writeLock = new Object();

WritableByteChannelImpl(OutputStream out) {
this.out = out;
}

public int write(ByteBuffer src) throws IOException {
int len = src.remaining();
int totalWritten = 0;
synchronized (writeLock) {
while (totalWritten < len) {
int bytesToWrite = Math.min((len - totalWritten),
TRANSFER_SIZE);
if (buf.length < bytesToWrite)
buf = new byte[bytesToWrite];
src.get(buf, 0, bytesToWrite);
try {
begin();
out.write(buf, 0, bytesToWrite);
} finally {
end(bytesToWrite > 0);
}
totalWritten += bytesToWrite;
}
return totalWritten;
}
}

protected void implCloseChannel() throws IOException {
out.close();
open = false;
}
}
很顯然,也是屬于內存類型的拷貝了,只能算作偽零拷貝實現了。

三。轉發到文件服務器上

一般常識為,讓最擅長的人來做最擅長的事情,是為高效。使用類如Nginx高效的Web服務器專門處理文件下載業務,達到零拷貝的目的,也是最佳搭配組合。Nginx可以利用header元數據X-Accel-Redirect來控制文件下載行為,甚是不錯。利用JAVA進行業務邏輯判斷,若符合規則,則提交給Nginx進行處理文件的下載,否則,返回給終端用戶權限不夠等信息。
用于控制用戶是否具有資格進行文件下載業務的控制器:

當然,這個僅僅用于演示,邏輯簡單。因為需要和nginx服務器進行配合,構建一個Server,其配置文件:

我們在nginx配置文件中,設置/dowloads/目錄是不允許直接訪問的,必須經由/download/控制器進行轉發方可。經測試,中文名不會出現亂碼問題,保存的文件也是我們所請求的文件,同名,也不會出現亂碼問題。但是,若在后臺獲取文件名,用于顯示/輸出,則需要從ISO-8859-1解碼成GBK編碼方可。
但這樣做,可能被綁定到某個類型的服務器,但也值得。實際上切換到Apache也是很簡單的。
PS : Apache服務器誒則對應X-Sendfile頭部屬性,因很長時間不再使用Apache,這里不再測試。
源碼下載
參考資料:
  1. 通過零拷貝實現有效數據傳輸

  2. Most effective way to write File to ServletOutputStream
  3. Java NIO FileChannel versus FileOutputstream performance / usefulness
  4. NginxChsXSendfile


nieyong 2012-01-19 10:21 發表評論
]]>
Servlet 3.0筆記之Servlet的異步特性支持失效怎么辦?http://www.aygfsteel.com/yongboy/archive/2012/01/18/369578.htmlnieyongnieyongWed, 18 Jan 2012 02:32:00 GMThttp://www.aygfsteel.com/yongboy/archive/2012/01/18/369578.htmlhttp://www.aygfsteel.com/yongboy/comments/369578.htmlhttp://www.aygfsteel.com/yongboy/archive/2012/01/18/369578.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/369578.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/369578.html滿心歡喜的為Servlet 添加上異步支持注解(asyncSupported = true),不曾想,其異步特性完全不起作用,仔細檢測項目,發現存在一個編碼攔截器(Filter),雖使用注解,但未標明支持異步,導致被攔截的標注為異步支持的Servlet,異步特性皆失效。怎么辦,在Filter中注解里面添加asyncSupported = true。問題解決。
但轉念一想,因歷史原因,遺留系統會存在很多的Servlet 2.*規范的Filter,無法支持異步,怎么辦?全部手動修改為注解版本,可能不太現實。還好,Doug Lea的JUC并發包,為我們提供了一種實現思路。
實際步驟:
  1. 準備一個線程池
  2. 把當前請求相關屬性包裝進一個任務線程中
  3. 獲取當前任務線程執行結果(不一定會有返回值)
  4. 阻塞,執行完畢或超時,或被中斷異常,可以輸出客戶端
  5. 整個請求結束
實際上,提交到一個線程池的任務線程,默認會返回一個Future對象,利用Future對象的get方法阻塞的特性,當前請求需要等待任務線程執行的結束,若指定時間內任務線程順利完成,則不必等到設定的時間的邊界即可自然往下執行。
實際代碼:

需要備注說明的是,若Future.get()無參數,則意味著需要等待計算完成,然后獲取其結果。這樣可不用設定等待時間了。更多信息,請參考JDK。
測試代碼下載


nieyong 2012-01-18 10:32 發表評論
]]>
Servlet 3.0筆記之Redis操作示范Retwis JAVA版http://www.aygfsteel.com/yongboy/archive/2011/04/06/347672.htmlnieyongnieyongWed, 06 Apr 2011 01:42:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/04/06/347672.htmlhttp://www.aygfsteel.com/yongboy/comments/347672.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/04/06/347672.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/347672.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/347672.html 最近學習Redis,一個目前風頭正勁的KEY-VALUE NOSQL,支持LIST、SET、MAP等格式存儲,某些操作竟然是原子級別,比如incr命令,相當驚艷。官方提供的一個Retwis(simple clone of Twitter)示范,證明了完全可以使用redis替代SQL數據庫存儲數據,真是一大開創性創新,挑戰人們的傳統思維模式,為之驚嘆!

不過那是一個PHP版本,我下載到本地PHP但無法部署成功;還好有一個Retwis-RB示范,還提供一個在線版,試用之后,再下載源代碼,RUBY + Sinatra MVC結構,看的流口水,代碼那般簡潔。

為體驗Redis,同時也為對比Ruby代碼,因此誕生了Retwis-JAVA,Retwis-RB的山寨版,因此其邏輯和HTML資源一致,再次表示對原作者Dan Lucraft的感謝。

Retwis-JAVA,基于Servlet 3.0 + UrlRewrite + Freemarker + Jedis。示范運行在Tomcat 7中,redis為最新的2.22版本,jedis為redis的java客戶端操作框架。在Servlet 3.0規范中,對Url映射處理依然沒有進步,因此只能使用UrlRewrite框架讓部分url看起來友好一些。另外,項目沒有使用IOC容器框架,沒有使用MVC框架,代碼量稍微多些,代碼相對耦合一些。若使用Struts2 + Spring 代碼量會少一些。

對涉及到的redis存儲結構,大致如下:


涉及到的兩個對象很簡單:


序列化后以二進制數據保存到redis中:

    private byte[] object2Bytes(V value) {
        
if (value == null)
            
return null;

        ByteArrayOutputStream arrayOutputStream 
= new ByteArrayOutputStream();
        ObjectOutputStream outputStream;
        
try {
            outputStream 
= new ObjectOutputStream(arrayOutputStream);

            outputStream.writeObject(value);
        } 
catch (IOException e) {
            e.printStackTrace();
        } 
finally {
            
try {
                arrayOutputStream.close();
            } 
catch (IOException e) {
                e.printStackTrace();
            }
        }

        
return arrayOutputStream.toByteArray();
    }


    
public void save(String key, V value) {
        jedis.set(getKey(key), object2Bytes(value));
    }

獲取用戶的timeline時,redis的LRANGE命令提供對list類型數據提供分頁操作:

    private List<Status> timeline(String targetId, int page) {
        
if (page < 1)
            page 
= 1;

        
int startIndex = (page - 1* 10;
        
int endIndex = page * 10;

        List
<String> idList = super.jedis
                .lrange(targetId, startIndex, endIndex);

        
if (idList.isEmpty())
            
return new ArrayList<Status>(0);

        List
<Status> statusList = new ArrayList<Status>(idList.size());
        
for (String id : idList) {
            Status status 
= load(Long.valueOf(id));

            
if (status == null)
                
continue;

            status.setUser(userService.load(status.getUid()));

            statusList.add(status);
        }

        
return statusList;
    }

很顯然,LRANGE取出了Status對象的ID,然后我們需要再次根據ID獲取對應的Status對象二進制數據,然后反序列化:

    public Status load(long id) {
        
return super.get(getFormatId(id));
    }

    
private String getFormatId(long id) {
        
return String.format(STATUS_ID_FORMAT, id);
    }

    
private static final String STATUS_ID_FORMAT = "status:id:%d";

    
public V get(String key) {
        
return byte2Object(jedis.get(getKey(key)));
    }

    @SuppressWarnings(
"unchecked")
    
private V byte2Object(byte[] bytes) {
        
if (bytes == null || bytes.length == 0)
            
return null;

        
try {
            ObjectInputStream inputStream;
            inputStream 
= new ObjectInputStream(new ByteArrayInputStream(bytes));
            Object obj 
= inputStream.readObject();

            
return (V) obj;
        } 
catch (IOException e) {
            e.printStackTrace();
        } 
catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        
return null;
    }

以上使用JDK內置的序列化支持;更多序列化,可參考hessian、google protobuf等序列化框架,后者提供業界更為成熟的跨平臺、更為高效的序列化方案。更多代碼請參見附件。

一些總結和思考:

  1. 不僅僅是緩存,替代SQL數據庫已完全成為可能,更高效,更經濟;雖然只是打開了一扇小的窗戶,但說不準以后人們會把大門打開。
  2. 實際環境中,可能最佳方式為SQL + NOSQL配合使用,互相彌補不足;還好,redis指令不算多,可速查,簡單易記。
  3. JAVA和RUBY代碼相比,有些重

另:

在線版,請參考 http://retwisrb.danlucraft.com/。那個誰誰,要運行范例,保證redis運行才行。

Retwis-JAVA下載

參考資料:

Writing a simple Twitter clone with PHP and Redis

https://github.com/xetorthio/jedis

http://redis.io/commands



nieyong 2011-04-06 09:42 發表評論
]]>
Servlet 3.0筆記之Servlet單實例以及線程安全小結http://www.aygfsteel.com/yongboy/archive/2011/03/14/346239.htmlnieyongnieyongMon, 14 Mar 2011 05:17:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/03/14/346239.htmlhttp://www.aygfsteel.com/yongboy/comments/346239.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/03/14/346239.html#Feedback3http://www.aygfsteel.com/yongboy/comments/commentRss/346239.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346239.html眾所周知,Servlet為單實例多線程,非線程安全的。

若一個Servlet對應多個URL映射,那么將會生成一個還是多個Servlet實例呢?

最好的辦法就是動手體驗一下

@WebServlet({ "/demoServlet1", "/demoServlet2" })
public class DemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println(request.getServletPath() + "[" + this + "]");
out.flush();
out.close();
}
}

輸出結果:

/demoServlet1[com.learn.servlet3.thread.DemoServlet@1320a41]
/demoServlet2[com.learn.servlet3.thread.DemoServlet@9abce9]

輸出結果可以看到映射/demoServlet1/demoServlet2對應Servlet實例是不同的。

結果證明:Servlet將為每一個URL映射生成一個實例;一個Servlet可能存在多個示例,但每一個實例都會對應不同的URL映射。


下面討論單個Servlet、多線程情況下保證數據線程同步的幾個方法。

  1. synchronized:代碼塊,方法
    大家都會使用的方式,不用詳細介紹了。
    建議優先選擇修飾方法。
  2. volatile
    輕量級的鎖,可以保證多線程情況單線程讀取所修飾變量時將會強制從共享內存中讀取最新值,但賦值操作并非原子性。
    一個具有簡單計數功能Servlet示范:
    /**
    * 使用Volatile作為輕量級鎖作為計數器
    *
    * @author yongboy
    * @date 2011-3-12
    * @version 1.0
    */
    @WebServlet("/volatileCountDemo")
    public class VolatileCountServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private volatile int num = 0;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    addOne();
    response.getWriter().write("now access num : " + getNum());
    }

    /**
    * 讀取開銷低
    */
    private int getNum() {
    return num;
    }

    /**
    * 其寫入為非線程安全的,賦值操作開銷高
    */
    private synchronized void addOne() {
    num ++;
    }
    }
    我們在為volatile修飾屬性賦值時,還是加把鎖的。
  3. ThreadLocal
    可以保證每一個線程都可以獨享一份變量副本,每個線程可以獨立改變副本,不會影響到其它線程。
    這里假設多線程環境一個可能落顯無聊的示范,初始化一個計數,然后循環輸出:
    @WebServlet("/threadLocalServlet")
    public class ThreadLocalServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static ThreadLocal threadLocal = new ThreadLocal() {
    @Override
    protected Integer initialValue() {
    return 0;
    }
    };

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Connection", "Keep-Alive");

    PrintWriter out = response.getWriter();
    out.println("start... " + " [" + Thread.currentThread() + "]");
    out.flush();

    for (int i = 0; i < 20; i++) {
    out.println(threadLocal.get());
    out.flush();

    threadLocal.set(threadLocal.get() + 1);
    }

    // 手動清理,當然隨著當前線程結束,亦會自動清理調
    threadLocal.remove();
    out.println("finish... ");
    out.flush();
    out.close();
    }
    }
    若創建一個對象較為昂貴,但又是非線程安全的,在某種情況下要求僅僅需要在線程中獨立變化,不會影響到其它線程。選擇使用ThreadLocal較好一些,嗯,還有,其內部使用到了WeakHashMap,弱引用,當前線程結束,意味著創建的對象副本也會被垃圾回收。
    Hibernate使用ThreadLocal創建Session;Spring亦用于創建對象會使用到一點。
    嗯,請注意這不是解決多線程共享變量的鑰匙,甚至你想讓某個屬性或對象在所有線程中都保持原子性,顯然這不是解決方案。
  4. Lock
    沒什么好說的,現在JDK版本支持顯式的加鎖,相比synchronized,添加與釋放更加靈活,功能更為全面。
    @WebServlet("/lockServlet")
    public class LockServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static int num = 0;
    private static final Lock lock = new ReentrantLock();

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try{
    lock.lock();
    num ++;
    response.getWriter().println(num);
    }finally{
    lock.unlock();
    }
    }
    }
    必須手動釋放鎖,否則將會一直鎖定。
  5. wait/notify
    較老的線程線程同步方案,較之Lock,不建議再次使用。
  6. 原子操作
    原子包裝類,包括一些基本類型(int, long, double, boolean等)的包裝,對象屬性的包裝等。
    @WebServlet("/atomicServlet")
    public class AtomicServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final AtomicInteger num = new AtomicInteger(0);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    out.println(num.incrementAndGet());
    out.flush();
    out.close();
    }
    }
    包裝類提供了很多的快捷方法,比如上面的incrementAndGet方法,自身增加1,然后返回結果值,并且還是線程安全的,省缺了我們很多手動、笨拙的編碼實現。
    更多原子類,請參見 java.util.concurrent.atomic包。
  7. 一些建議
    盡量不要在Servlet中單獨啟用線程
    使用盡可能使用局部變量
    盡可能避免使用鎖
  8. 數據結構小結
    在平常環境下使用的一些數據結構,在多線程并發環境下可選擇使用java.util.concurrent里內置替代品。下面有一個小結,僅供參考。
非線程安全工具版JUC版本
HashMap Collections.synchronizedMap ConcurrentHashMap
ArrayList Collections.synchronizedList CopyOnWriteArrayList
HashSet Collections.synchronizedSet synchronizedSet
Queue   ConcurrentLinkedQueue



Servlet線程安全有很太多的話題要說,以上僅僅為蜻蜓點水,真正需要學習和實踐的還有一段長路要學習。另,話說,這里已和Servlet版本無關,是知識積累和分享。



nieyong 2011-03-14 13:17 發表評論
]]>
Servlet 3.0筆記之異步請求重新梳理Comet的幾種形式http://www.aygfsteel.com/yongboy/archive/2011/02/27/346195.htmlnieyongnieyongSun, 27 Feb 2011 09:17:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/02/27/346195.htmlhttp://www.aygfsteel.com/yongboy/comments/346195.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/02/27/346195.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346195.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346195.html本來只是想梳理以下Servlet 3.0異步請求,不曾想涉及到Comet(維基百科上面對comet有具體的定義和介紹),篇章多了起來,對以往所學習和心得,重新梳理一下。
Comet的實現主要流(streaming) 形式和長輪詢(long polling)兩種形式。流和長輪詢區別主要在于,在一次連接中,服務器端推送多次的數據為流,只推送一次為長輪詢。
流推送(Comet Streaming)
Comet流推送目前一般采用:
  1. 隱藏IFrame(Hidden iframe)
    服務器端一般會輸出一段javascript腳本
    <script>comet.showMsg(“這是新的消息”)</script>

    好處在于各個瀏覽器都支持iframe,目前在IE、Firefox下面效果最好,頁面不再顯示正在加載中的討厭的提示(見Servlet 3.0筆記之異步請求Comet推送iFrame示范
  2. XMLHttpRequest
    很可憐,目前支持情況最好的是火狐瀏覽器、IE8以及Safari
    參考文章:
    Servlet 3.0筆記之異步請求Comet推送XMLHttpRequest示范
    Servlet 3.0筆記之異步請求Comet流推送(Streaming)實現小結
長輪詢(Ajax with long polling)
客戶端會與服務器建立一個持久的連接,直到服務器端有數據發送過來(超時也會發送數據),雙方斷開連接,客戶端處理完推送的數據,會再次發起一個持久的連接,循環往復。其實現形式:
  1. XHR方式長輪詢(XMLHttpRequest long polling)
    服務器端可以返回原始的數據,也可以返回格式化的JSON、XML、JAVASCRIPT信息,相比iframe形式流推送,更自由一些。使用形式和傳統意義上的AJAX GET方式大致一樣,只不過下一次的輪詢需要等到有數據返回處理完方可。
    默認不支持跨域,實踐中會打折扣。
  2. JAVASCRIPT標簽輪詢(Script tag long polling)
    可能翻譯不太準確,主要使用類似于如下形式:
    <script src='js/yourCometProvider.js' type='text/javascript'></script>
    使用跨域的JS腳本,可能會有安全方面隱患,或許這個間接風險是可控的。
    也可以使用JSONP用以規避這個風險。
    進階閱讀:Comet (long polling) for all browsers using ScriptCommunicator
    代碼寫的很巧妙,也很簡短,思路不錯,推薦一看。
    參考文章:
    Servlet 3.0筆記之異步請求Comet推送長輪詢(long polling)篇
    Servlet 3.0筆記之異步請求Comet推送長輪詢(long polling)篇補遺
Comet瀏覽器支持匯總
大致總結一下各個瀏覽器對Comet支持大致情況,可能不太科學,僅以自己實踐中感知為準。table{border:1px solid #888888;border-collapse:collapse;font-family:Arial,Helvetica,sans-serif;margin-top:10px;width:100%;}table th {background-color:#CCCCCC;border:1px solid #888888;padding:5px 15px 5px 5px;text-align:left;vertical-align:baseline;}table td {background-color:#EFEFEF;border:1px solid #AAAAAA;padding:5px 15px 5px 5px;vertical-align:text-top;}
瀏覽器 流(streaming)長輪詢(long polling) 
IE(6-8) htmlfile + iframe 支持流形式;
IE8的XDomainRequest支持HTTP Streaming
支持腳本加載事件;頁面表現完美
Firefox(3.5) 支持XMLHttpRequest Streaming 和隱藏的IFrame組件 支持腳本文件async屬性;頁面表現完美
Chrome(>9.0) 不支持XMLHttpRequest Streaming;使用IFrame會一直出現正在加載中圖標 支持腳本文件async屬性;頁面表現完美
Safari(5) 瀏覽器支持XMLHttpRequest Streaming 支持腳本文件的async屬性;頁面表現完美
Opera(>9.0) 不支持XMLHttpRequest Streaming,使用IFrame的話會一樣會出現正在加載中的標志 本機測試腳本文件長輪詢時間若長于20秒,則會出現連接中斷情況;會出現正在加載中的標志
Servlet 3.0 支持
長輪詢或流推送,相對于Servlet 3.0 異步請求,相差不大,都需要定義一個連接超時時間,但后者可以向客戶端推送多次,前者推送一次之后需要立刻斷開了與客戶端的連接;在超時事件發生后,都可以通知客戶端超時已發生。另外在高并發環境下可能需要做多層次的消息路由服務以降低單機的處理極限。
實踐中使用Comet流推送,也需要在一定的時間后再次重連,或許可以稱之為流輪詢(streaming poll),只是人們沒有提起而已。
發布訂閱模型(Publish/Subscribe)
Comet為發布/訂閱模型在前端應用的一種變形。可以很直觀的將服務器端視為內容發布者,瀏覽器端為訂閱者。相對于谷歌的pubsubhubbub協議,區別在于Comet使用HTTP 1.1 長連接協議,客戶端為瀏覽器,沒有公開的可回調的客戶端地址。而pubsubhubbub則要求訂閱者有一個可以回調的URL地址(這個URL對于發布者服務器是直達的),一旦有新的可用數據,可以推送到客戶端,客戶端某個URL監聽就是。雙方在用途、使用環境上不相同,需要區別對待。
話說觀察者模式在實際應用中十分廣泛,變體也很多。
參考資料:
觀察者模式在線版:訂閱-發布模型的在線示范


nieyong 2011-02-27 17:17 發表評論
]]>
Servlet 3.0筆記之異步請求Facebook BigPipe簡單模型實現http://www.aygfsteel.com/yongboy/archive/2011/02/22/346196.htmlnieyongnieyongTue, 22 Feb 2011 12:47:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/02/22/346196.htmlhttp://www.aygfsteel.com/yongboy/comments/346196.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/02/22/346196.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346196.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346196.html當前的前端技術明星為Facebook,相繼拋出了Quickling,BigPipe等新鮮概念,引領著前端優化的潮流。果然具有大公司的范兒,適時的回饋給整個開發社群,讓全體朝前前進了一小步。超贊!
抽空學習了BigPipe,大概相當于分段刷新(flush),強制輸出。雖有點舊瓶裝新酒的味道,不過前端已經進入了細節為王的階段,或許在國內已經有人在用,但缺乏分享溝通,或者還不成熟,缺乏關注,缺少重要數據用以論證等。
BigPipe:高性能的“流水線技術”網頁,或許可以為需要認知的人們打開一扇窗戶。
原文介紹BigPiple:
BigPipe是一個重新設計的基礎動態網頁服務體系。大體思路是,分解網頁成叫做Pagelets的小塊,然后通過Web服務器和瀏覽器建立管道并管理他們在不同階段的運行。這是類似于大多數現代微處理器的流水線執行過程:多重指令管線通過不同的處理器執行單元,以達到性能的最佳。雖然BigPipe是對現有的服務網絡基礎過程的重新設計,但它卻不需要改變現有的網絡瀏覽器或服務器,它完全使用PHP和JavaScript來實現。
BigPipe在Facebook應用中的工作流程:
1. 請求解析:Web服務器解析和完整性檢查的HTTP請求。
2. 數據獲取:Web服務器從存儲層獲取數據。
3. 標記生成:Web服務器生成的響應的HTML標記。
4. 網絡傳輸:響應從Web服務器傳送到瀏覽器。
5. CSS的下載:瀏覽器下載網頁的CSS的要求。
6. DOM樹結構和CSS樣式:瀏覽器構造的DOM文檔樹,然后應用它的CSS規則。
7. JavaScript中下載:瀏覽器下載網頁中JavaScript引用的資源。
8. JavaScript執行:瀏覽器的網頁執行JavaScript代碼。
其帶來效果較為明顯處在于:
這種高度并行系統的最終結果是,多個Pagelets的不同執行階段同時進行。例如,瀏覽器可以正在下載三個Pagelets CSS的資源,同時已經顯示另一Pagelet內容,與此同時,服務器也在生成新的Pagelet。從用戶的角度來看,頁面是逐步呈現的。最開始的網頁內容會更快的顯示,這大大減少了用戶的對頁面延時的感知。如果您要自己親眼看到區別,你可以嘗試以下連接: 傳統模式BigPipe。第一個鏈接是傳統模式單一模式顯示頁面。第二個鏈接是BigPipe管道模式的頁面。如果您的瀏覽器版本比較老,網速也很慢,瀏覽器緩存不佳,哪么兩頁之間的加截時間差別將更加明顯。
值得一提的是BigPipe是從微處理器的流水線中得到啟發。然而,他們的流水線過程之間存在一些差異。例如,雖然大多數階段BigPipe只能操作一次Pagelet,但有時多個Pagelets的CSS和JavaScript下載卻可以同時運作,這類似于超標量微處理器。BigPipe另一個重要區別是,我們實現了從并行編程引入的“障礙”概念,所有的Pagelets要完成一個特定階段,如多個Pagelet顯示區,它們都可以進行進一步JavaScript下載和執行。
啟用了BigPipe后,服務器端優先輸出頁面整體布局,瀏覽器渲染布局;服務器按照串行方式,一段段按優先級順序,輸出片段內容到瀏覽器端,瀏覽器執行JS函數,同時可能會同時請求新的JS文件(盡可能不要涉及外部JS),下載特定樣式文件(這個時候可以產生并發連接)。瀏覽器渲染頁面單個組件(Pagelet),組件同時加載CSS、JS文件時,不會影響到下一個組件的渲染工作。以往的頁面模型在于,瀏覽器接收所有數據,然后一次性渲染,顯示最終結果給客戶。
BigPipe涉及到的一些問題,原文沒有涉及,來自淘寶的李牧在他的博客中(Facebook讓網站速度提升一倍的BigPipe技術分析)提出一些疑問:
腳本阻滯:
         我們知道直接在html中引入外部腳本會造成頁面阻滯,即使使用無阻腳本下載的一系列方法引入外部js,但因為JS單線程,當這些腳本load進來之后運行時也會發生阻滯.因為Facebook頁面多樣,依賴腳本大小不一,這些阻滯會對頁面的快速展現造成影響.
        Facebook做法是在ondomready之前只在頭部輸出一個很小的外部腳本,作為bigpipe的支撐.其余所有模塊如果依賴外部腳本(比如某一模塊需要日歷控件支持),都會在domready事件之后加載.這樣做即保證了所有頁面所有模塊都能在domready前快速形成展現,又可以保證無腳本阻滯的快速的domready時間.
        最快可交互時間:
         domready再快也至少是在頁面第一個使用bigpipe輸出的chunked的http請求完成之后,對于Facebook來說,這很可能是2秒之后了.那在這2s期間,如果只是頁面展現了而不能交互(點擊日歷無反應),那方案依然不夠完美.
         Facebook的做法是,在domready后所依賴腳本已被加載之前,點擊行為將會生成一個額外的腳本請求,只將這個點擊所依賴的腳步預先load進來.這樣保證了最快的可交互時間.Facebook在另一篇文章中對這個技術進行了詳細的描述.
         Bigpipe原理簡單,實現不復雜,但Facebook卻用了1年的時間才形成完備的解決方案.生成方案需要時間,而解決隨之而來的腳本阻滯,保障最快交互時間等等問題也會消耗大量時間. 
較為全面的了解了BigPipe,下面使用使用JAVA Servlet 3.0 異步特性,簡單模擬BigPipe實現,沒有涉及到頁面組件單獨請求的JS、CSS文件等,僅僅用以演示其過程(最終效果大打折扣)。
我們目標頁面大致結構如下,在 The 1KB CSS Grid 生成結構上修改。 moz-screenshot-2
異步Servlet代碼:

傳統MVC模式在這里貌似去除了,一切在服務器端生成,可借助Freemarker或者靜態頁面輔助,減輕純手工拼接HTML代碼的麻煩。
生成客戶端代碼:
moz-screenshot-3_thumb[1].png (800×441)
在Servlet代碼中,每輸出一段HTML腳本都會迫使當前線程沉睡一定時間,用戶在瀏覽頁面時,按照優先級順序一段一段的輸出,用戶體驗不錯。下面的截圖,可以略微感知一下。
image
話說若使用BigPipe,須分段刷新,則服務器無法獲得最終輸出內容長度,只能采用chunked壓縮編碼格式輸出內容,能節省網絡流量;但不利于SEO,按照Facebook做法就是,若搜索爬蟲訪問,就會按照正常方式生成頁面。另一方面,BigPipe的應用場景適合計算量大、較為耗時、擁有若干相互獨立模塊的頁面,中小頁面不太適合。


nieyong 2011-02-22 20:47 發表評論
]]>
Servlet 3.0筆記之異步請求Comet推送長輪詢(long polling)篇補遺http://www.aygfsteel.com/yongboy/archive/2011/02/19/346197.htmlnieyongnieyongSat, 19 Feb 2011 02:19:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/02/19/346197.htmlhttp://www.aygfsteel.com/yongboy/comments/346197.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/02/19/346197.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346197.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346197.html上篇文章有些東西言猶未盡,這里補遺,使之理解更為全面些。
還多虧了jQuery,提供了JSONP請求的方法:
$.getJSON('http://192.168.0.99/servlet3/getjsonpnew?callback=?')
指定需要回調的參數名callback,但其值使用一問號替代,作為占位符,jQuery會自動貼心的生成一個隨機的調用函數名,服務器端的生成結果:
jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
我們直接在success函數中直接獲取傳入的值,無須擔心那個看起來怪怪的回調函數名。JSONP的原理,在頁面頭部區動態產生一個JS文件引用(有人稱為動態標簽引用)。
<script src="http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777"/>
雖然這一切看起來很好,很貼心,但也存在著不少問題:
  1. 無法直接顯示指定回調函數名完美主義者可能無法忍受長長的、怪怪的函數名;當然了,可以定制的東西,總是讓人很舒心
  2. 在長連接情況下,瀏覽器會一直顯示正在加載中圖示jQuery生成的JSONP JS文件在長連接情況下,會一直加載中,可能會影響到良好的用戶體驗,也會讓前端UI體驗師比較鬧心。
很顯然jQuery無法滿足我們更高的實際要求。怎么辦,請出jQuery-JSONP組件,基本上可以滿足我們的要求。服務器端代碼沒有什么變化,有所變化的是客戶端代碼。
<html>
<head>
<title>Comet JSONP TEST</title>
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
<meta name="author" content="yongboy@gmail.com"/>
<meta name="keywords" content="servlet3, comet, ajax"/>
<meta name="description" content=""/>
<link type="text/css" rel="stylesheet" href="css/main.css"/>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
<script type="text/javascript" src="js/jquery.jsonp-2.1.4.js"></script>
<script type="text/javascript">
String.prototype.template=function(){
var args=arguments;
return this.replace(/\{(\d )\}/g, function(m, i){
return args[i];
});
}
var html = '<div class="logDiv">'
'<div class="contentDiv">{0}</div>'
'<div class="tipDiv">last date : {1}</div>'
'<div class="clear">&nbsp;</div>'
'</div>';

function showContent(json) {
$("#showDiv").prepend(html.template(json.content, json.date));
}
var jsonpUrl = "http://192.168.0.99/servlet3/getjsonpnew";
function initJsonp(){
$.jsonp({
url: jsonpUrl,
callbackParameter: 'callback',
callback : 'yongboy', //eg:http://192.168.0.99/servlet3/getjsonpnew?callback=yongboy&_1297934909500=
success: function(json) {
if(json.state == 1){
showContent(json);
}
initJsonp();
},error: function(data) {
}
});
}
$(function (){
// 為了兼容opera瀏覽器,設置為20秒延時時間(我本地測試若大于20s,可能出現連接中斷問題)
if($.browser.opera){
jsonpUrl = "?timeout=20000";
}

initJsonp();
});
</script>
</head>
<body style="margin: 0; overflow: hidden">
<div id="showDiv" class="inputStyle">loading ...</div>
</body>
</html>
嗯,jQuery-JSONP組件提供了更為靈活的jsonp參數配置:
  • 指定回調函數名
  • 提供錯誤處理函數,兼容opera
  • 在目前大部分瀏覽器中沒有加載進度顯示

經測試:
  1. IE
    頁面不刷新,效果很滿意。jQuery-JSONP源代碼:
         if ( browser.msie ) {

    script.event = STR_ONCLICK;
    script.htmlFor = script.id;
    script[ STR_ONREADYSTATECHANGE ] = function() {
    /loaded|complete/.test( script.readyState ) && callback();
    };

    // All others: standard handlers
    }
    使用了IE瀏覽器onreadystatechange加載事件等特性。
  2. Firefox
          script[ STR_ONERROR ] = script[ STR_ONLOAD ] = callback;

    browser.opera ?

    // Opera: onerror is not called, use synchronized script execution
    ( ( scriptAfter = $( STR_SCRIPT_TAG )[ 0 ] ).text = "jQuery('#" + script.id + "')[0]." + STR_ONERROR + "()" )

    // Firefox: set script as async to avoid blocking scripts (3.6+ only)
    : script[ STR_ASYNC ] = STR_ASYNC;
    看來我當前瀏覽器中3.6版本的火狐支持腳本文件async屬性,非阻塞加載,再添加上onload,onerror事件支持,頁面不刷新顯示,也不難辦到了。
  3. Chrome
    很不錯,當前作為主力的Chrome瀏覽器也支持腳本文件的async屬性,效果同3.6版本火狐。
  4. Opera
    情況很糟糕,在Windows Opera 11.01以及Ubuntu Opera 11.00下測試,長輪詢時間若長于20秒,則會出現連接中斷情況。樣例中在Opear瀏覽器下設置長輪詢過期時間為20s。
    瀏覽器會一直顯示為正在加載中的狀態。
    Opera瀏覽器中腳本加載不支持onerror事件,只能使用阻塞式的腳本加載機制用以處理有可能發生的異常。
    有關阻塞方式實現JSONP,延伸閱讀:
    Comet (long polling) for all browsers using ScriptCommunicator
  5. Safari
    既然同為一個內核的谷歌瀏覽器也支持腳本文件的async屬性,同樣的異步加載也是水到渠成的了。
有關瀏覽器腳本異步加載機制,可進階閱讀:非阻塞式JavaScript腳本介紹


nieyong 2011-02-19 10:19 發表評論
]]>
Servlet 3.0筆記之異步請求Comet推送長輪詢(long polling)篇http://www.aygfsteel.com/yongboy/archive/2011/02/17/346198.htmlnieyongnieyongThu, 17 Feb 2011 10:03:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/02/17/346198.htmlhttp://www.aygfsteel.com/yongboy/comments/346198.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/02/17/346198.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346198.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346198.htmlComet另一種形式為長輪詢(long polling),客戶端會與服務器建立一個持久的連接,直到服務器端有數據發送過來,服務器端斷開,客戶端處理完推送的數據,會再次發起一個持久的連接,循環往復。
和流(Streaming)區別主要在于,在一次長連接中,服務器端只推送一次,然后斷開連接。
其實現形式大概可分文AJAX長輪詢和JAVASCRIPT輪詢兩種。
  1. AJAX方式請求長輪詢

    服務器端可以返回原始的數據,或格式化的JSON、XML、JAVASCRIPT信息,客戶端可擁有更多的處理自由。在形式上和傳統意義上的AJAX GET方式大致一樣,只不過下一次的輪詢需要等到上一次的輪詢數據返回處理完方可進行。
    這里給出客戶端的小示范:
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with long poll</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" + resp + "</b>");
    }

    log("loading");

    // 去除緩存
    $.ajaxSetup({ cache: false });

    function initGet(){
    $.get("getNextTime")
    .success(function(resp){
    log(resp);
    }).error(function(){
    log("ERROR!");
    }).done(initGet);
    }

    initGet();
    });
    </script>
    </body>
    </html>
    基于jQuery 1.5,事件鏈,比以往更簡潔明了,尤其是在done方法中又一次調用自身,棒極了。
    服務器端很簡單,每1秒輸出一次當前系統時間:
    /**
    * 簡單模擬每秒輸出一次當前系統時間,精細到毫秒
    *
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime")
    public class GetNextTimeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    response.setContentType("text/plain");
    PrintWriter out = response.getWriter();

    out.print(DateFormatUtils.format(System.currentTimeMillis(),
    "yyyy-MM-dd HH:mm:ss SSS"));
    out.flush();

    out.close();
    }
    }
  2. JAVASCRIPT標簽輪詢(Script tag long polling)

    引用的JAVASCRIPT腳本文件是可以跨域的,再加上JSONP,更為強大了。
    這里給出一個演示,實現功能類似上面。
    客戶端清單:
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jQuery 1.5 with JSONP FOR Comet</title>
    </head>
    <body>
    <div id="tip"></div>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    $(function (){
    function log(resp){
    $("#tip").html("<b>" resp "</b>");
    }

    log("loading");

    function jsonp(){
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?').success(function(resp){
    log("now time : " resp);
    }).done(jsonp);

    // 以下方式也是合法的
    /*
    $.getJSON('http://192.168.0.99:8080/servlet3/getNextTime2?callback=?',function(date){
    log(date);
    }).done(jsonp);
    */
    }

    jsonp();
    });
    </script>
    </body>
    </html>

    服務器端清單:
    /**
    * JSONP形式簡單模擬每秒輸出一次當前系統時間,精細到毫秒
    * @author yongboy
    * @date 2011-2-11
    * @version 1.0
    */
    @WebServlet("/getNextTime2")
    public class GetNextTimeServlet2 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    try {
    Thread.sleep(1000L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    String callback = request.getParameter("callback");

    if(StringUtils.isBlank(callback)){
    callback = "showResult";
    }

    PrintWriter out = response.getWriter();

    StringBuilder resultBuilder = new StringBuilder();
    resultBuilder
    .append(callback)
    .append("('")
    .append(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss SSS"))
    .append("');");

    out.print(resultBuilder);
    out.flush();

    out.close();
    }
    }

    每次請求時,都會在HTML頭部生成一個JS網絡地址(實現跨域):
    http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777
    我們不用指定,jquery會自動生成一個隨機的函數名。
    請求上面地址服務器端在有新的數據輸出時,生成的回調JS函數:
    jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');
    從下面截圖可以看到這一點。
    jspon snapshot
    生成相應內容:
    image

     不過,長連接情況下,瀏覽器認為當前JS文件還沒有加載完,會一直顯示正在加載中。
上面的JSONP例子太簡單,服務器和客戶端的連接等待時間不過1秒鐘時間左右,若在真實環境下,需要設置一個超時時間。
以往可能會有人告訴你,在客戶端需要設置一個超時時間,若指定期限內服務器沒有數據返回,則需要通知服務器端程序,斷開連接,然后自身再次發起一個新的連接請求。
但在Servlet 3.0 異步連接的規范中,我們就不用那么勞心費神,那般麻煩了。掉換個兒,讓服務器端通知客戶端超時啦,趕緊重新連接啊。
在前面幾篇博客中,演示了一個偽真實場景,博客主添加博客,然后主動以流形式推送給終端用戶。這里采用JSONP跨域長輪詢連接形式。
客戶端:
<html>
<head>
<title>Comet JSONP %u63A8%u9001%u6D4B%u8BD5</title>
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
<meta name="author" content="yongboy@gmail.com"/>
<meta name="keywords" content="servlet3, comet, ajax"/>
<meta name="description" content=""/>
<link type="text/css" rel="stylesheet" href="css/main.css"/>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
<script type="text/javascript">
String.prototype.template=function(){
var args=arguments;
return this.replace(/\{(\d )\}/g, function(m, i){
return args[i];
});
}
var html = '<div class="logDiv">'
'<div class="contentDiv">{0}</div>'
'<div class="tipDiv">last date : {1}</div>'
'<div class="clear">&nbsp;</div>'
'</div>';

function showContent(json) {
$("#showDiv").prepend(html.template(json.content, json.date));
}
function initJsonp(){
$.getJSON('http://192.168.0.99/servlet3/getjsonpnew?callback=?').success(function(json){
if(json.state == 1){
showContent(json);
}else{
initJsonp();
return;
}
}).done(initJsonp)
.error(function(){
alert("error!");
});
}
$(function (){
initJsonp();
});
</script>
</head>
<body style="margin: 0; overflow: hidden">
<div id="showDiv" class="inputStyle">loading ...</div>
</body>
</html>
服務器端接收長輪詢連接請求:
/**
* JSONP獲取最新信息
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@WebServlet(urlPatterns = "/getjsonpnew", asyncSupported = true)
public class GetNewJsonpBlogPosts extends HttpServlet {
private static final long serialVersionUID = 565698895565656L;
private static final Log log = LogFactory.getLog(GetNewJsonpBlogPosts.class);

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
response.setHeader("Proxy-Connection", "Keep-Alive");
response.setContentType("text/javascript;charset=UTF-8");
response.setCharacterEncoding("UTF-8");

String timeoutStr = request.getParameter("timeout");

long timeout;

if (StringUtils.isNumeric(timeoutStr)) {
timeout = Long.parseLong(timeoutStr);
} else {
// 設置1分鐘
timeout = 1L * 60L * 1000L;
}

log.info("new request ...");
final HttpServletResponse finalResponse = response;
final HttpServletRequest finalRequest = request;
final AsyncContext ac = request.startAsync(request, finalResponse);
// 設置成長久鏈接
ac.setTimeout(timeout);
ac.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
log.info("onComplete Event!");
NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
}

public void onTimeout(AsyncEvent event) throws IOException {
// 嘗試向客戶端發送超時方法調用,客戶端會再次請求/blogpush,周而復始
log.info("onTimeout Event!");

// 通知客戶端再次進行重連
final PrintWriter writer = finalResponse.getWriter();

String outString = finalRequest.getParameter("callback")
+ "({state:0,error:'timeout is now'});";
writer.println(outString);
writer.flush();
writer.close();

NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
}

public void onError(AsyncEvent event) throws IOException {
log.info("onError Event!");
NewBlogJsonpListener.ASYNC_AJAX_QUEUE.remove(ac);
}

public void onStartAsync(AsyncEvent event) throws IOException {
log.info("onStartAsync Event!");
}
});

NewBlogJsonpListener.ASYNC_AJAX_QUEUE.add(ac);
}
}
很顯然,在有博客數據到達時,會通知到已注冊的客戶端。
注意,推送數據之后,需要調用當前的異步上下文complete()函數:
/**
* 監聽器單獨線程推送到客戶端
*
* @author yongboy
* @date 2011-2-17
* @version 1.0
*/
@WebListener
public class NewBlogJsonpListener implements ServletContextListener {
private static final Log log = LogFactory.getLog(NewBlogListener.class);
public static final BlockingQueue BLOG_QUEUE = new LinkedBlockingQueue();
public static final Queue ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue();

public void contextDestroyed(ServletContextEvent arg0) {
log.info("context is destroyed!");
}

public void contextInitialized(ServletContextEvent servletContextEvent) {
log.info("context is initialized!");
// 啟動一個線程處理線程隊列
new Thread(runnable).start();
}

private Runnable runnable = new Runnable() {
public void run() {
boolean isDone = true;

while (isDone) {
if (!BLOG_QUEUE.isEmpty()) {
try {
log.info("ASYNC_AJAX_QUEUE size : "
+ ASYNC_AJAX_QUEUE.size());
MicBlog blog = BLOG_QUEUE.take();

if (ASYNC_AJAX_QUEUE.isEmpty()) {
continue;
}

String targetJSON = buildJsonString(blog);

for (AsyncContext context : ASYNC_AJAX_QUEUE) {
if (context == null) {
log.info("the current ASYNC_AJAX_QUEUE is null now !");
continue;
}
log.info(context.toString());
PrintWriter out = context.getResponse().getWriter();

if (out == null) {
log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
continue;
}

out.println(context.getRequest().getParameter(
"callback")
+ "(" + targetJSON + ");");
out.flush();

// 通知,執行完成函數
context.complete();
}
} catch (Exception e) {
e.printStackTrace();
isDone = false;
}
}
}
}
};

private static String buildJsonString(MicBlog blog) {
Map info = new HashMap();
info.put("state", 1);
info.put("content", blog.getContent());
info.put("date",
DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS"));

JSONObject jsonObject = JSONObject.fromObject(info);

return jsonObject.toString();
}
}
長連接的超時時間,有人貼切的形容為“心跳頻率”,聯動著兩端,需要根據環境設定具體值。
JSONP方式無論是輪詢還是長輪詢,都是可以很容易把目標程序融合到第三方網站,當前的個人主頁、博客等很多小掛件大多也是采用這種形式進行跨域獲取數據的。


nieyong 2011-02-17 18:03 發表評論
]]>
Servlet 3.0筆記之Tomcat(7.0.6)穩定版發布http://www.aygfsteel.com/yongboy/archive/2011/01/26/346199.htmlnieyongnieyongWed, 26 Jan 2011 14:42:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/26/346199.htmlhttp://www.aygfsteel.com/yongboy/comments/346199.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/26/346199.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346199.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346199.html2011年1月11日Tomcat 7.0.6正式發布,很有紀念意義。
這是Tomcat 7分支的第一個穩定版本,可以用在生產環境上面了,用以取代2007年2月發布的Tomcat 6。
來自infoQ新聞(Apache Tomcat 7成為最新穩定版本)簡要說明了Tomcat 7其主要
Apache決定不在Tomcat中添加對Java EE 6 Web Profile的完整支持,至少在眼下是這樣的。因此Tomcat 7中只是簡單地增加了Servlet 3.0(Java EE 6中引入的)的支持以及JavaServer Pages 2.2和EL 2.2的支持。新版本要求使用Java SE 6或更高版本。
可見此版本,完善的支持了Servlet 3.0 規范,以及可能被人忽略的JSP 2.2、EL 2.2,這已經夠了,不要那么多,簡單就好,否則就會變成另一個J2EE應用服務器。
在安全方面也有較為主要的改善:
Tomcat 7中的改進也不是全都針對Servlet 3.0 API的,其中還有不少重要的安全性改進。現在針對基于腳本的訪問、基于Web的訪問、JMX代理訪問和狀態頁訪問有了獨立的角色,允許做更具體的訪問控制。為了避免跨站請求偽造(CSRF)攻擊,所有的非冪等性請求(即多次執行不會產生相同結果的操作)都要求生成一個隨機數。Tomcat 7還針對會話固定攻擊(session fixation attack)采取了一些防御措施。會話固定攻擊就是將客戶端的會話ID強制設置為一個明確的已知值。
總之,我們可以在在Tomcat 7.0.6中使用Servlet 3.0做實際意義上的開發了。

nieyong 2011-01-26 22:42 發表評論
]]>
Servlet 3.0筆記之會話Cookie設置相關http://www.aygfsteel.com/yongboy/archive/2011/01/19/346200.htmlnieyongnieyongWed, 19 Jan 2011 13:58:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/19/346200.htmlhttp://www.aygfsteel.com/yongboy/comments/346200.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/19/346200.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346200.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346200.html在Servlet 3.0中增加對Cookie(請注意,這里所說的Cookie,僅指和Session互動的Cookie,即人們常說的會話Cookie)較為全面的操作API。最為突出特性:支持直接修改Session ID的名稱(默認為“JSESSIONID”),支持對cookie設置HttpOnly屬性以增強安全,避免一定程度的跨站攻擊。
相關較為深入信息,請訪問進階閱讀部分。
以前的實現
雖然新的API提供了簡答便捷的API操作會話Cookie,但新的API之前,我們可以較為生硬的操作響應頭部,完成設定工作。看看以前的代碼吧:
/**
* 自定義會話Cookie屬性
* @author yongboy
* @date 2011-1-19
* @version 1.0
*/
@WebFilter(dispatcherTypes = { DispatcherType.REQUEST }, urlPatterns = { "/*" })
public class CustomCookieFilter implements Filter {
private static final Log log = LogFactory.getLog(CustomCookieFilter.class);
private static final String CUSTOM_SESSION_ID = "YONGBOYID";
private static final String HTTP_ONLY = "HttpOnly";
private static final String SET_COOKIE = "SET-COOKIE";

public void destroy() {
}

public void doFilter(ServletRequest req, ServletResponse rep,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) rep;

if (response.containsHeader(SET_COOKIE)) {
log.info("haha,we have one new user visit the site ...");

String sessionId = request.getSession().getId();
String cookieValue = CUSTOM_SESSION_ID + "=" + sessionId + ";Path="
+ request.getContextPath() + ";" + HTTP_ONLY;

log.info(SET_COOKIE + ":" + cookieValue);

response.setHeader(SET_COOKIE, cookieValue);
}

chain.doFilter(req, response);
}

public void init(FilterConfig fConfig) throws ServletException {
}
}

利用攔截器實現,判斷每次請求的響應是否包含SET-COOKIE頭部,重寫會話Cookie,添加需要的屬性。雖較為生硬,但靈活性強。

新的規范API

新的規范添加SessionCookieConfig接口,用于操作會話Cookie,需要掌握以下主要方法:
  1. setName(String name)
    修改Session ID的名稱,默認為"JSESSIONID"
  2. setDomain(String domain)
    設置當前Cookie所處于的域
  3. setPath(String path)
    設置當前Cookie所處于的相對路徑
  4. setHttpOnly(boolean httpOnly)
    設置是否支持HttpOnly屬性
  5. setSecure(boolean secure)
    若使用HTTPS安全連接,則需要設置其屬性為true
  6. setMaxAge(int maxAge)
    設置存活時間,單位為秒
如何使用呢,很方便,在ServletContextListener監聽器初始化方法中進行設定即可;下面實例演示如何修改"JSESSIONID",以及添加支持HttpOnly支持:
/** 全局設置Session-Cookie相交互部分屬性
*
* @author yongboy
* @date 2011-1-19
* @version 1.0
*/
@WebListener
public class SessionCookieInitialization implements ServletContextListener {
private static final Log log = LogFactory
.getLog(SessionCookieInitialization.class);

public void contextInitialized(ServletContextEvent sce) {
log.info("now init the Session Cookie");

ServletContext servletContext = sce.getServletContext();

SessionCookieConfig sessionCookie = servletContext
.getSessionCookieConfig();
sessionCookie.setName("YONGBOYID");
sessionCookie.setPath(servletContext.getContextPath());
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(false);

log.info("name : " + sessionCookie.getName() + "\n" + "domain:"
+ sessionCookie.getDomain() + "\npath:"
+ sessionCookie.getPath() + "\nage:"
+ sessionCookie.getMaxAge());

log.info("isHttpOnly : " + sessionCookie.isHttpOnly());
log.info("isSecure : " + sessionCookie.isSecure());
}

public void contextDestroyed(ServletContextEvent sce) {
log.info("the context is destroyed !");
}
}
需要通過ServletContext對象獲得SessionCookieConfig對象,才能夠進一步自定義session cookie的名字等屬性。
無論以前的硬編碼還是新的API實現,目標都是一致的,所產生頭部信息也是完全一致。毫無疑問,后者更為方便快捷,省缺了顯示的操作響應元數據。
對當前站點的第一次請求,很容易從響應頭信息中看到Set-Cookie的屬性值:

image


不同瀏覽器平臺上測試

  1. 在Safari、IE8、Opera 11 一切都很正常
  2. Firefox 3.6、Chrome 9.0,JSESSIONID會繼續存在:
    YONGBOYID=601A6C82D535343163B175A4FD5376EA; JSESSIONID=AA78738AB1EAD1F9C649F705EC64D92D; AJSTAT_ok_times=6; JSESSIONID=abcpxyJmIpBVz6WHVo_1s; BAYEUX_BROWSER=439-1vyje1gmqt8y8giva7pqsu1
  3. 在所有瀏覽器中,SESSION ID等于新設置的YONGBOYID值(若不相等,問題就嚴重了!)
  4. 在客戶端JS無法獲得正確的SESSIONI ID了。
Tomcat服務器內置支持

在Tomcat 6-7,可以不用如上顯示設置Cookie domain、name、HttpOnly支持,在conf/context.xml文件中配置即可:
<Context useHttpOnly="true", sessionCookieName="YONGBOYID", sessionCookieDomain="/servlet3" … >
...
</Context>
既然JAVA應用服務器本身支持會話Cookie設定,那就沒有必要在程序代碼中再次進行編碼了。這是一個好的實踐:不要重復造輪子。
這里給出一段測試Session重寫的一段腳本:
<div style="margin: 40px; paddding: 10px">
<div><a href="sessionCookieTest">正常連接</a></div>
<div><a href="<%=response.encodeURL("sessionCookieTest") %>">重定向連接</a></div>
</div>
會被重寫的URL地址類似于:
http://localhost/servlet3/sessionCookieTest;YONGBOYID=19B94935D50245270060E49C9E69F5B6
嗯,在取消會話Cookie之后,可以直接看到修改后的SESSION ID名稱了,當然這時候HttpOnly屬性也沒有多大意義了。
有一點別忘記,設置HttpOnly之后,客戶端的JS將無法獲取的到會話ID了。

進階閱讀:

  1. 維基對HttpOnly的解釋
  2. 利用HTTP-only Cookie緩解XSS之痛
  3. Tomcat Context 屬性
  4. HttpOnly cookie與跨站點追蹤


nieyong 2011-01-19 21:58 發表評論
]]>
Servlet 3.0筆記之異步請求相關方法和AsyncContext轉發等http://www.aygfsteel.com/yongboy/archive/2011/01/17/346201.htmlnieyongnieyongMon, 17 Jan 2011 13:07:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/17/346201.htmlhttp://www.aygfsteel.com/yongboy/comments/346201.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/17/346201.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346201.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346201.html在ServletRequest中增加的有關異步相關方法分為:
  1. startAsync(servletRequest, servletResponse) 傳入指定的request和response對象,便于在AsyncContext中重復使用(這樣被Filter、Servlet包裝過的請求、相應對象才會在異步的環境下繼續生效)。
  2. startAsync() 若request或者response被包裝,將失去預期的效果。
  3. isAsyncSupported()isAsyncStarted()
    輔助性的方法,用于判斷當前請求是否支持異步或者異步已經開始。
  4. getAsyncContext()
    需要在異步啟動之后才能夠訪問,否則會報異常。
在AsyncContext中分發的方法有三個,不太容易區分:
  1. AsyncContext.dispatch()
    若當前AsyncContext由ServletRequest.startAsync(ServletRequest, ServletResponse)方法啟動,返回的地址可以通過HttpServletRequest.getRequestURI()得到。
    否則,分發的地址則是當前URL request對象最后一次分發的地址。
    雖有些拗口,兩者分發的地址大部分情況下一致;但盡量使用帶有參數的異步上下文啟動器。
    如本例中請求/asyncDispatch2Async?disUrl=self,執行dispatch()方法之后,自身會再次分發到自身,包括傳遞的參數。
  2. AsyncContext.dispatch(String path)
    等同于ServletRequest.getRequestDispatcher(String)算是一個快捷方法。
    可以轉向一個同步或異步的servlet,或者JSP,或其它資源地址等。
  3. AsyncContext.dispatch(ServletContext context, String path)
    請求的地址將在給定的上下文里面(ServletContext),有有可能傳入的上下文與當前站帶你應用的上下文有所區別。
展示一個較為有趣、但沒有多少實際意義的小示范:
/**
* 異步上下文的轉向分發
*
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
@WebServlet(urlPatterns = { "/asyncDispatch2Async" }, asyncSupported = true)
public class AsyncContextDispatch2AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 46172233331022236L;
private static final Log log = LogFactory
.getLog(AsyncContextDispatch2AsyncServlet.class);

protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
response.setHeader("Connection", "Keep-Alive");
response.setHeader("Proxy-Connection", "Keep-Alive");
response.setContentType("text/html;charset=UTF-8");

PrintWriter out = response.getWriter();
out.println("

Start ...

");
out.flush();

if (!request.isAsyncSupported()) {
log.info("the servlet is not supported Async");
return;
}

request.startAsync(request, response);

if (request.isAsyncStarted()) {
AsyncContext asyncContext = request.getAsyncContext();
asyncContext.setTimeout(1L * 60L * 1000L);// 60sec

new CounterThread(asyncContext).start();
} else {
log.error("the ruquest is not AsyncStarted !");
}
}

private static class CounterThread extends Thread {
private AsyncContext asyncContext;

public CounterThread(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}

@Override
public void run() {
int interval = 1000 * 20; // 20sec

try {
log.info("now sleep 20s, just as do some big task ...");
Thread.sleep(interval);
log.info("now dispatch to another Async Servlet");

ServletRequest request = asyncContext.getRequest();

String disUrl = request.getParameter("disUrl");

if (StringUtils.isBlank(disUrl)) {
disUrl = "/demoAsyncLink";
}

if (disUrl.endsWith(".jsp")) {
request.setAttribute("dateStr", DateFormatUtils.format(
System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"));
}

log.info("disUrl is : " + disUrl);

// 將當前異步上下文所持有的request, response分發給Servlet容器
if (StringUtils.equals("self", disUrl)) {
// 將分發到自身,即當前異步請求地址
asyncContext.dispatch();
} else {
// 將分發到指定的路徑
asyncContext.dispatch(disUrl);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
當前請求完成之后,將分發到下一個請求上面,若都是異步的Servlet,則很好的組成了異步Servlet請求鏈。
有趣的地方在于,異步上下文環境可以分發到下一個異步或同步的servlet、jsp、html等資源。若訪問類似于如下地址,當前URL永遠不會斷開,又一個永動機,除非網絡鏈接出錯或者服務器關閉。
http://localhost/servlet3/asyncDispatch2Async?disUrl=self
一個視圖:
image
上面的異步Servlet總是在不斷的請求自我,成為了一個永動機;為disUrl傳入要轉發到的異步或同步的資源地址,組成一個鏈的模式,相當的簡單輕松。


nieyong 2011-01-17 21:07 發表評論
]]>
Servlet 3.0筆記之超方便的文件上傳支持http://www.aygfsteel.com/yongboy/archive/2011/01/15/346202.htmlnieyongnieyongSat, 15 Jan 2011 12:43:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/15/346202.htmlhttp://www.aygfsteel.com/yongboy/comments/346202.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/15/346202.html#Feedback1http://www.aygfsteel.com/yongboy/comments/commentRss/346202.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346202.html在以前,處理文件上傳是一個很痛苦的事情,大都借助于開源的上傳組件,諸如commons fileupload等。現在好了,很方便,便捷到比那些組件都方便至極。以前的HTML端上傳表單不用改變什么,還是一樣的multipart/form-data MIME類型。
讓Servlet支持上傳,需要做兩件事情
  1. 需要添加MultipartConfig注解
  2. 從request對象中獲取Part文件對象
但在具體實踐中,還是有一些細節處理,諸如設置上傳文件的最大值,上傳文件的保存路徑。
需要熟悉MultipartConfig注解,標注在@WebServlet之上,具有以下屬性:
屬性名類型是否可選描述
fileSizeThresholdint當數據量大于該值時,內容將被寫入文件。
locationString存放生成的文件地址。
maxFileSizelong允許上傳的文件最大值。默認值為 -1,表示沒有限制。
maxRequestSizelong針對該 multipart/form-data 請求的最大數量,默認值為 -1,表示沒有限制。

一些實踐建議:
  1. 若是上傳一個文件,僅僅需要設置maxFileSize熟悉即可。
  2. 上傳多個文件,可能需要設置maxRequestSize屬性,設定一次上傳數據的最大量。
  3. 上傳過程中無論是單個文件超過maxFileSize值,或者上傳總的數據量大于maxRequestSize值都會拋出IllegalStateException異常;
  4. location屬性,既是保存路徑(在寫入的時候,可以忽略路徑設定),又是上傳過程中臨時文件的保存路徑,一旦執行Part.write方法之后,臨時文件將被自動清除。
  5. 但Servlet 3.0規范同時也說明,不提供獲取上傳文件名的方法,盡管我們可以通過part.getHeader("content-disposition")方法間接獲取得到。
  6. 如何讀取MultipartConfig注解屬性值,API沒有提供直接讀取的方法,只能手動獲取。
    來一個示例吧,上傳前臺頁面:
    后臺處理Servlet:
    /**
    * 上傳文件測試 location為臨時文件保存路徑
    *
    * @author yongboy
    * @date 2011-1-13
    * @version 1.0
    */
    @MultipartConfig(location = "/home/yongboy/tmp/", maxFileSize = 1024 * 1024 * 10)
    @WebServlet("/upload")
    public class UploadFileAction extends HttpServlet {
    private static final long serialVersionUID = 92166165626L;
    private static final Log log = LogFactory.getLog(UploadFileAction.class);
    // 得到注解信息
    private static final MultipartConfig config;

    static {
    config = UploadFileAction.class.getAnnotation(MultipartConfig.class);
    }

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/upload.jsp").forward(request, response);
    }

    protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    // 為避免獲取文件名稱時出現亂碼
    request.setCharacterEncoding("UTF-8");

    Part part = null;
    try {
    // <input name="file" size="50" type="file" />
    part = request.getPart("file");
    } catch (IllegalStateException ise) {
    // 上傳文件超過注解所標注的maxRequestSize或maxFileSize值
    if (config.maxRequestSize() == -1L) {
    log.info("the Part in the request is larger than maxFileSize");
    } else if (config.maxFileSize() == -1L) {
    log.info("the request body is larger than maxRequestSize");
    } else {
    log.info("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
    }

    forwardErrorPage(request, response, "上傳文件過大,請檢查輸入是否有誤!");
    return;
    } catch (IOException ieo) {
    // 在接收數據時出現問題
    log.error("I/O error occurred during the retrieval of the requested Part");
    } catch (Exception e) {
    log.error(e.toString());
    e.printStackTrace();
    }

    if (part == null) {
    forwardErrorPage(request, response, "上傳文件出現異常,請檢查輸入是否有誤!");
    return;
    }

    // 得到文件的原始名稱,eg :測試文檔.pdf
    String fileName = UploadUtils.getFileName(part);

    log.info("contentType : " + part.getContentType());
    log.info("fileName : " + fileName);
    log.info("fileSize : " + part.getSize());
    log.info("header names : ");
    for (String headerName : part.getHeaderNames()) {
    log.info(headerName + " : " + part.getHeader(headerName));
    }

    String saveName = System.currentTimeMillis() + "."
    + FilenameUtils.getExtension(fileName);

    log.info("save the file with new name : " + saveName);

    // 因在注解中指定了路徑,這里可以指定要寫入的文件名
    // 在未執行write方法之前,將會在注解指定location路徑下生成一臨時文件
    part.write(saveName);

    request.setAttribute("fileName", fileName);
    request.getRequestDispatcher("/uploadResult.jsp").forward(request,
    response);
    }

    private void forwardErrorPage(HttpServletRequest request,
    HttpServletResponse response, String errMsg)
    throws ServletException, IOException {
    request.setAttribute("errMsg", errMsg);

    request.getRequestDispatcher("/upload.jsp").forward(request, response);
    }
    }
    獲取文件名的函數,很簡單:
     /**
    * 如何得到上傳的文件名, API沒有提供直接的方法,只能從content-disposition屬性中獲取
    *
    * @param part
    * @return
    */
    protected static String getFileName(Part part) {
    if (part == null)
    return null;

    String fileName = part.getHeader("content-disposition");
    if (StringUtils.isBlank(fileName)) {
    return null;
    }

    return StringUtils.substringBetween(fileName, "filename=\"", "\"");
    }
    文件上傳成功之后,以及日志輸出的截圖如下:
    截圖中可以看到Part包含content-disposition屬性,可以很容易從值中抽取出文件名。臨時生成的上傳文件大都以 .tmp為后綴,大致如下:
    讓上傳出現錯誤,就可以在保存路徑下看到大致如上的臨時文件。
    一次上傳多個文件的后臺servlet示范:
    /**
    * 多文件上傳支持
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    @MultipartConfig(
    location = "/home/yongboy/tmp/",
    maxFileSize = 1024L * 1024L, // 每一個文件的最大值
    maxRequestSize = 1024L * 1024L * 10L // 一次上傳最大值,若每次只能上傳一個文件,則設置maxRequestSize意義不大
    )
    @WebServlet("/uploadFiles")
    public class UploadFilesAction extends HttpServlet {
    private static final long serialVersionUID = 2304820820384L;
    private static final Log log = LogFactory.getLog(UploadFilesAction.class);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    request.getRequestDispatcher("/uploads.jsp").forward(request, response);
    }

    protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");

    Collection parts = null;
    try {
    parts = request.getParts();
    } catch (IllegalStateException ise) {
    // 可能某個文件大于指定文件容量maxFileSize,或者提交數據大于maxRequestSize
    log.info("maybe the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
    } catch (IOException ioe) {
    // 在獲取某個文件時遇到拉IO異常錯誤
    log.error("an I/O error occurred during the retrieval of the Part components of this request");
    } catch (Exception e) {
    log.error("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
    e.printStackTrace();
    }

    if (parts == null || parts.isEmpty()) {
    doError(request, response, "上傳文件為空!");
    return;
    }

    // 前端具有幾個file組件,這里會對應幾個Part對象
    List fileNames = new ArrayList();
    for (Part part : parts) {
    if (part == null) {
    continue;
    }
    // 這里直接以源文件名保存
    String fileName = UploadUtils.getFileName(part);

    if (StringUtils.isBlank(fileName)) {
    continue;
    }

    part.write(fileName);
    fileNames.add(fileName);
    }

    request.setAttribute("fileNames", fileNames);
    request.getRequestDispatcher("/uploadsResult.jsp").forward(request,
    response);
    }

    private void doError(HttpServletRequest request,
    HttpServletResponse response, String errMsg)
    throws ServletException, IOException {
    request.setAttribute("errMsg", errMsg);

    this.doGet(request, response);
    }
    }
    批量上傳很簡單,但也有風險,任一個文件若超過maxFileSize值,意味著整個上傳都會失敗。若不限大小,那就不存在以上憂慮了。
    總之,在Servlet 3.0 中,無論是上傳一個文件,或者多個批量上傳都是非常簡單,但要處理好異常,避免出錯。


    nieyong 2011-01-15 20:43 發表評論
    ]]>
    Servlet 3.0筆記之異步攔截器(async filter)的學習http://www.aygfsteel.com/yongboy/archive/2011/01/15/346203.htmlnieyongnieyongSat, 15 Jan 2011 11:39:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/15/346203.htmlhttp://www.aygfsteel.com/yongboy/comments/346203.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/15/346203.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346203.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346203.html異步Servlet有時需要一個攔截器,但必須是異步的Filter,否則將會報錯:
    嚴重: Servlet.service() for servlet [com.learn.servlet3.async.DemoAsyncLinkServlet] in context with path [/servlet3] threw exceptionjava.lang.IllegalStateException: Not supported.
    因此異步的Filter攔截異步Servlet,不要搞錯。
    我們需要預先定義這么一個異步連接,每秒輸出一個數字字符串,從0到99,諸如下面HTML字符串:
    <div>2</div>
    最后輸出Done!
    給出兩個訪問地址,一個用于被攔截(/demoAsyncLink),一個用于單獨訪問(/demoAsyncLink2),便于對照:
    /**
    * 模擬長連接實現,每秒輸出一些信息
    *
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    @WebServlet(
    urlPatterns = { "/demoAsyncLink", "/demoAsyncLink2" },
    asyncSupported = true
    )
    public class DemoAsyncLinkServlet extends HttpServlet {
    private static final long serialVersionUID = 4617227991063927036L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Proxy-Connection", "Keep-Alive");
    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();
    out.println("<div>Start ...</div>");
    out.flush();

    AsyncContext asyncContext = request.startAsync(request, response);

    new CounterThread(asyncContext).start();
    }

    private static class CounterThread extends Thread {
    private AsyncContext asyncContext;
    public CounterThread(AsyncContext asyncContext) {
    this.asyncContext = asyncContext;
    }

    @Override
    public void run() {
    int num = 0;
    int max = 100;
    int interval = 1000;

    // 必須設置過期時間,否則將會出連接過期,線程無法運行完畢異常
    asyncContext.setTimeout((max + 1) * interval);
    PrintWriter out = null;

    try {
    try {
    out = asyncContext.getResponse().getWriter();
    } catch (IOException e) {
    e.printStackTrace();
    }

    while (true) {
    out.println("<div>" + (num++) + "</div>");
    out.flush();

    if (num >= max) {
    break;
    }

    Thread.sleep(interval);
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    if (out != null) {
    out.println("<div>Done !</div>");
    out.flush();
    out.close();
    }

    asyncContext.complete();
    }
    }
    }
    若想讓HttpServletResponse包裝器發揮包裝的效果,須調用帶有參數的startAsync(request, response)方法開啟異步輸出,否則MarkWapperedResponse將不起作用。因為,若不傳遞現有的request,response對象,將會調用原生的request和response對象。
    在tomcat7下面,異步連接超時時間為10000單位,若不指定超時時間,遞增的數字不會按照預想完整輸出到99。
    我們假設需要定義這樣一個Filter,為每一次的異步輸出的內容增加一個特殊標記:
    <!--marked filter-->
    <div>2</div>
    邏輯很簡單,作為示范也不需要多復雜。
    再看看一個異步Filter的代碼:
    /**
    * 異步攔截器
    *
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    @WebFilter(
    dispatcherTypes = {
    DispatcherType.REQUEST,
    DispatcherType.FORWARD,
    DispatcherType.INCLUDE
    },
    urlPatterns = { "/demoAsyncLink" },
    asyncSupported = true //支持異步Servlet
    )
    public class AsyncServletFilter implements Filter {
    private Log log = LogFactory.getLog(AsyncServletFilter.class);

    public AsyncServletFilter() {
    }

    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response,
    FilterChain chain) throws IOException, ServletException {

    log.info("it was filted now");

    MarkWapperedResponse wapper = new MarkWapperedResponse(
    (HttpServletResponse) response);

    chain.doFilter(request, wapper);
    }

    public void init(FilterConfig fConfig) throws ServletException {
    }
    }
    很簡單,添加上asyncSupported = true屬性即可。在上面Filter中包裝了一個HttpServletResponse對象,目的在于返回一個定制的PrintWriter對象,簡單重寫flush方法(不見得方法多好):
    /**
    * HttpServletResponse簡單包裝器,邏輯簡單
    *
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    public class MarkWapperedResponse extends HttpServletResponseWrapper {
    private PrintWriter writer = null;
    private static final String MARKED_STRING = "<!--marked filter--->";

    public MarkWapperedResponse(HttpServletResponse resp) throws IOException {
    super(resp);

    writer = new MarkPrintWriter(super.getOutputStream());
    }

    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
    return writer;
    }

    private static class MarkPrintWriter extends PrintWriter{

    public MarkPrintWriter(OutputStream out) {
    super(out);
    }

    @Override
    public void flush() {
    super.flush();

    super.println(MARKED_STRING);
    }
    }
    }
    在瀏覽器端請求被包裝的/demoAsyncLink鏈接,截圖以及firebug檢測截圖如下:
     
    可以在瀏覽器內同時請求/demoAsyncLink2前后作為對比一下。


    nieyong 2011-01-15 19:39 發表評論
    ]]>
    Servlet 3.0筆記之異步請求Comet流推送(Streaming)實現小結http://www.aygfsteel.com/yongboy/archive/2011/01/14/346204.htmlnieyongnieyongFri, 14 Jan 2011 02:26:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/14/346204.htmlhttp://www.aygfsteel.com/yongboy/comments/346204.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/14/346204.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346204.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346204.htmlServlet3規范支持異步請求(或者稱為長連接,或者反向AJAX,或者COMET,或者服務器推送技術):無阻塞的輸入與輸出模型,可以延時的請求和響應功能,還有超時事件通知,看上去一切都是那么完美。
    但終端瀏覽器支持長連接情況差強人意,對Comet的支持大致匯總如下:
    1. IE瀏覽器最佳實踐是使用htmlfile ActiveXObject,以及創建隱藏IFrame組件,可以跨越IE6-IE8;雖IE 8支持XDomainRequest支持HTTP Streaming,但僅僅是IE 8。
    2. Firefox 瀏覽器相當棒,支持XMLHttpRequest Streaming 和隱藏的IFrame組件。
    3. Safari 瀏覽器支持XMLHttpRequest Streaming。
    4. Chrome有些無奈,算不上支持XMLHttpRequest Streaming,使用IFrame的話會一直出現正在加載中的標志。
    5. Opera也不支持XMLHttpRequest Streaming,使用IFrame的話會一直出現正在加載中的標志。
    總之,使用IFrame是一個不錯的方案,在IE、Firefox下表現的很完美,在其它瀏覽器下只能忍受討厭的正在加載中。數據交換格式可以采用JS腳本調用。
    但無論哪一種方案,都必須認識到,一個持久的連接,當頁面內容一直在遞增時,會越來越膨脹,會占用用戶機器的CPU,盡量隔一段時間斷開連接,重新請求。
    HTTP 1.1規范中聲明客戶端不應該與服務器端建立超過兩個 HTTP 連接,因此瀏覽器內需要借助腳本避免客戶重開兩個腳本。
    按照目前情形下,需要借助AJAX PULL  + COMET PUSH 相結合來打造相當好的用戶體驗。
    Servlet本身,無論2.4或者2.5的版本,可以使用一個循環達到長連接的目標:
    /**
    * 一個典型的長連接實現
    *
    * @author yongboy
    * @date 2011-1-14
    * @version 1.0
    */
    @WebServlet("/demoLongLink")
    public class DemoLongLinkServlet extends HttpServlet {
    private static final long serialVersionUID = 4617227991063927036L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {

    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Proxy-Connection", "Keep-Alive");
    response.setContentType("text/html;charset=UTF-8");

    PrintWriter out = response.getWriter();
    out.println("<div>Start ...</div>");
    out.flush();

    int num = 0;
    int max = 100;
    while (true) {
    out.println("<div>" + (num++) + "</div>");
    out.flush();

    if (num >= max) {
    break;
    }

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    out.println("<div>Done !</div>");
    out.flush();
    out.close();
    }
    }
    每一個連接線程都處于一個不斷循環之中,不能夠有效釋放,相當的浪費服務器資源,有可能導致容器內線程池耗盡,將無法應對后續請求。同時少了異步連接的特性,無法直接定義超時時間,更不要說超時事件,超時監聽器等企業特性了。
    當然也可以實現異步請求,但可能沒有規范那般嚴格。
    同步請求的模型:
    對比異步請求模型:
    上面兩張圖借用了涂0實驗室,表示感謝。
    在前兩篇文章中,使用一個單獨線程處理資源,分發到大部分的異步請求中。


    nieyong 2011-01-14 10:26 發表評論
    ]]>
    Servlet 3.0筆記之異步請求Comet推送XMLHttpRequest示范http://www.aygfsteel.com/yongboy/archive/2011/01/13/346205.htmlnieyongnieyongThu, 13 Jan 2011 02:20:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/13/346205.htmlhttp://www.aygfsteel.com/yongboy/comments/346205.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/13/346205.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346205.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346205.html說實話,各個瀏覽器對XMLHttpRequest內置對象請求異步連接(或者稱呼為長連接)的支持不一而足,除了Firefox,Safari,IE8等,其它的要么不支持,要么有缺陷。總之,單純依靠XHR(XMLHttpRequest的簡稱)來調用長連接,不靠譜。
    XMLHttpRequest對象在標準情況下,在請求長連接情況下,readyState = 3時處于一直監聽情況,但各大主流瀏覽器內的支持相關情況不盡相同:
    1. IE(IE6-IE7),只有在請求的內容不再發生變化時以及 readyState = 4 的時候才會獲取到返回的responseText內容,因此不支持長連接。
    2. IE8:以XDomainRequest取代ActiveXObject對象,也算是一個進步,但在服務器端有邀請,必須在頭部設置 Access-Control-Allow-Origin值,若不知道客戶端調用JS所處的域名,設置成星號,通用即可。
    3. Opera:貌似只有在readyState=3時才會觸發一次onreadystatechange函數,不支持長連接。
    4. Firefox 3.6 和Safari 4:默認支持XMLHttpRequest Streaming。
    5. Chrome 4.1版本:只有在請求內容不再發生變化才會達到readyState=2狀態,所以支持情況不太好。
    要檢測各個瀏覽器對XMLHttpRequest的支持請求,這里有一個好的去處:
     Streaming Test page 
    以上內容,翻譯摘選自:COMET Streaming in Internet Explore 
    一個客戶端訂閱頁面:
     page
    頁面代碼:
    <html>
    <head>
    <title>comet XHR測試</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta name="author" content="yongboy@gmail.com" />
    <meta name="keywords" content="servlet3, comet, ajax" />
    <meta name="description" content="" />
    <link type="text/css" rel="stylesheet" href="css/main.css" />
    </head>
    <body style="margin: 0; overflow: hidden" onload="">
    <div id="showDiv" class="inputStyle"></div>
    <script>
    function showMsg(msg) {
    document.getElementById("showDiv").innerHTML = msg;
    }
    function doXHR() {
    var xhr = null;
    // 在IE8下面,window.XMLHttpRequest = true,因此需要window.XDomainRequest放在第一位置
    if (window.XDomainRequest) {
    xhr = new XDomainRequest();
    } else if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
    var aVersions = [ "Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0",
    "Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp", "Microsoft.XMLHttp" ];
    for ( var i = 0; i < aVersions.length; i++) {
    try {
    xhr = new ActiveXObject(aVersions[i]);
    break;
    } catch (e) {
    }
    }
    }

    if (xhr == null) {
    showMsg("當前瀏覽器不支持創建XMLHttpRequest !");
    return;
    }

    try {
    xhr.onload = function() {
    showMsg(xhr.responseText);
    };
    xhr.onerror = function() {
    showMsg("XMLHttpRequest Fatal Error.");
    };
    xhr.onreadystatechange = function() {
    try {
    if (xhr.readyState > 2) {
    showMsg(xhr.responseText);
    }
    } catch (e) {
    showMsg("XMLHttpRequest Exception: " + e);
    }
    };
    // 經測試:
    // IE8,Safayi完美支持onprogress事件(可以不需要onreadystatechange事件);
    // Chrome也支持,在后臺數據推送到時,會調用其方法,但無法得到responseText值;除非(長連接關閉)
    // Firefox 3.6 也支持,但得到返回值有些BUG
    xhr.onprogress = function() {
    showMsg(xhr.responseText);
    };
    xhr.open("GET", "getnew?" + Math.random(), true);
    xhr.send(null);
    } catch (e) {
    showMsg("XMLHttpRequest Exception: " + e);
    }
    }
    if (window.addEventListener) {
    window.addEventListener("load", doXHR, false);
    } else if (window.attachEvent) {
    window.attachEvent("onload", doXHR);
    }
    </script>
    </body>
    </html>
    當然后臺需要一個內容發布的頁面:
    write2
    后臺處理的代碼和上篇隱藏IFrame服務器推送部分較為類似相似:
    /**
    * XHR獲取最新信息
    *
    * @author yongboy
    * @date 2011-1-10
    * @version 1.0
    */
    @WebServlet(urlPatterns = "/getnew", asyncSupported = true)
    public class GetNewBlogPosts extends HttpServlet {
    private static final long serialVersionUID = 5656988888865656L;
    private static final Log log = LogFactory.getLog(GetNewBlogPosts.class);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Connection", "Keep-Alive");
    response.setHeader("Proxy-Connection", "Keep-Alive");
    response.setHeader("Access-Control-Allow-Origin", "*");
    response.setContentType("text/html;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    PrintWriter writer = response.getWriter();

    // 若當前輸出內容較少情況下,需要產生大約2KB的垃圾數據,諸如下面產生一些空格
    for (int i = 0; i < 10; i++) {
    writer.print(" ");
    }

    writer.println("<div class='logDiv,clear'>waiting for ......</div>");
    writer.flush();

    final AsyncContext ac = request.startAsync();
    // 設置成長久鏈接
    ac.setTimeout(10 * 60 * 1000);
    ac.addListener(new AsyncListener() {
    public void onComplete(AsyncEvent event) throws IOException {
    NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
    }

    public void onTimeout(AsyncEvent event) throws IOException {
    NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
    }

    public void onError(AsyncEvent event) throws IOException {
    NewBlogXHRListener.ASYNC_XHR_QUEUE.remove(ac);
    }

    public void onStartAsync(AsyncEvent event) throws IOException {
    log.info("now add the AsyncContext");
    }
    });

    NewBlogXHRListener.ASYNC_XHR_QUEUE.add(ac);
    }
    }
    不過多了兼容IE8的代碼部分:
    response.setHeader("Access-Control-Allow-Origin", "*");
    在IE8以及Chrome平臺下,需要預先生成一些大小為2K的垃圾數據,諸如一些空格。

    單獨線程代碼和上篇隱藏IFrame服務器推送部分相似,這里不再貼出。
    經測試:
    1. 在ubuntu 10.10系統下Chrome(版本號8.0.552.224),支持XHR Streaming,測試通過。
    2. 2.Firefox 3.6 下測試通過。
    3. Safari 5.0.3 測試通過。
    4. IE8測試通過。


    nieyong 2011-01-13 10:20 發表評論
    ]]>
    Servlet 3.0筆記之異步請求Comet推送iFrame示范http://www.aygfsteel.com/yongboy/archive/2011/01/10/346206.htmlnieyongnieyongMon, 10 Jan 2011 02:57:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/10/346206.htmlhttp://www.aygfsteel.com/yongboy/comments/346206.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/10/346206.html#Feedback5http://www.aygfsteel.com/yongboy/comments/commentRss/346206.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346206.html
    Servlet3規范提出異步請求,絕對是一巨大歷史進步。之前各自應用服務器廠商紛紛推出自己的異步請求實現(或者稱comet,或者服務器推送支持,或者長連接),諸如Tomcat6中的NIO連接協議支持,Jetty的continuations編程架構,SUN、IBM、BEA等自不用說,商業版的服務器對Comet的支持,自然走在開源應用服務器前面,各自為王,沒有一個統一的編程模型,怎一個亂字了得。相關的comet框架也不少,諸如pushlet、DWR、cometd;最近很熱HTML5也不甘寂寞,推出WebSocket,只是離現實較遠。
    總體來說,在JAVA世界,很亂!缺乏規范,沒有統一的編程模型,會嚴重依賴特定服務器,或特定容器。
    好在Servlet3具有了異步請求規范,各個應用服務器廠商只需要自行實現即可,這樣編寫符合規范的異步Servlet代碼,不用擔心移植了。
    現在編寫支持comet風格servlet,很簡單:
    1. 在注解處標記上 asyncSupported = true;
    2. final AsyncContext ac = request.startAsync();
    這里設定簡單應用環境:一個非常受歡迎博客系統,多人訂閱,終端用戶僅僅需要訪問訂閱頁面,當后臺有新的博客文章提交時,服務器會馬上主動推送到客戶端,新的內容自動顯示在用戶的屏幕上。整個過程中,用戶僅僅需要打開一次頁面(即訂閱一次),后臺當有新的內容時會主動展示用戶瀏覽器上,不需要刷新什么。下面的示范使用到了iFrame,有關Comet Stream,會在以后展開。有關理論不會在本篇深入討論,也會在以后討論。
    這個系統需要一個博文內容功能:
    新的博文后臺處理部分代碼:
     protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    MicBlog blog = new MicBlog();

    blog.setAuthor("發布者");
    blog.setId(System.currentTimeMillis());
    blog.setContent(iso2UTF8(request.getParameter("content")));
    blog.setPubDate(new Date());

    // 放到博文隊列里面去
    NewBlogListener.BLOG_QUEUE.add(blog);

    request.setAttribute("message", "博文發布成功!");

    request.getRequestDispatcher("/WEB-INF/pages/write.jsp").forward(
    request, response);
    }

    private static String iso2UTF8(String str){
    try {
    return new String(str.getBytes("ISO-8859-1"), "UTF-8");
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    }
    return null;
    }
    當用戶需要訂閱博客更新時的界面:
    當前頁面HTML代碼可以說明客戶端的一些情況:
    <html>
    <head>
    <title>comet推送測試</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
    <meta name="author" content="yongboy@gmail.com"/>
    <meta name="keywords" content="servlet3, comet, ajax"/>
    <meta name="description" content=""/>
    <link type="text/css" rel="stylesheet" href="css/main.css"/>
    <script type="text/javascript" src="js/jquery-1.4.min.js"></script>
    <script type="text/javascript" src="js/comet.js"></script>
    </head>
    <body style="margin: 0; overflow: hidden">
    <div id="showDiv" class="inputStyle"></div>
    </body>
    </html>
    id為“showDiv”的div這里作為一個容器,用于組織顯示最新的信息。
    而客戶端邏輯,則在comet.js文件中具體展示了如何和服務器交互的一些細節:
    /**
    * 客戶端Comet JS 渲染部分
    * @author yongboy@gmail.com
    * @date 2010-10-18
    * @version 1.0
    */
    String.prototype.template=function(){
    var args=arguments;
    return this.replace(/\{(\d+)\}/g, function(m, i){
    return args[i];
    });
    }
    var html = '<div class="logDiv">'
    + '<div class="contentDiv">{0}</div>'
    + '<div class="tipDiv">last date : {1}</div>'
    + '<div class="clear">&nbsp;</div>'
    + '</div>';

    function showContent(json) {
    $("#showDiv").prepend(html.template(json.content, json.date));
    }
    var server = 'blogpush';
    var comet = {
    connection : false,
    iframediv : false,

    initialize: function() {
    if (navigator.appVersion.indexOf("MSIE") != -1) {
    comet.connection = new ActiveXObject("htmlfile");
    comet.connection.open();
    comet.connection.write("<html>");
    comet.connection.write("<script>document.domain = '"+document.domain+"'");
    comet.connection.write("</html>");
    comet.connection.close();
    comet.iframediv = comet.connection.createElement("div");
    comet.connection.appendChild(comet.iframediv);
    comet.connection.parentWindow.comet = comet;
    comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='"+server+"'></iframe>";
    }else if (navigator.appVersion.indexOf("KHTML") != -1 || navigator.userAgent.indexOf('Opera') >= 0) {
    comet.connection = document.createElement('iframe');
    comet.connection.setAttribute('id', 'comet_iframe');
    comet.connection.setAttribute('src', server);
    with (comet.connection.style) {
    position = "absolute";
    left = top = "-100px";
    height = width = "1px";
    visibility = "hidden";
    }
    document.body.appendChild(comet.connection);
    }else {
    comet.connection = document.createElement('iframe');
    comet.connection.setAttribute('id', 'comet_iframe');
    with (comet.connection.style) {
    left = top = "-100px";
    height = width = "1px";
    visibility = "hidden";
    display = 'none';
    }
    comet.iframediv = document.createElement('iframe');
    comet.iframediv.setAttribute('onLoad', 'comet.frameDisconnected()');
    comet.iframediv.setAttribute('src', server);
    comet.connection.appendChild(comet.iframediv);
    document.body.appendChild(comet.connection);
    }
    },
    frameDisconnected: function() {
    comet.connection = false;
    $('#comet_iframe').remove();
    //setTimeout("chat.showConnect();",100);
    },
    showMsg:function(data){
    showContent(data);
    },
    timeout:function(){
    var url = server + "?time=" + new Date().getTime();
    if (navigator.appVersion.indexOf("MSIE") != -1) {
    comet.iframediv.childNodes[0].src = url;
    } else if (navigator.appVersion.indexOf("KHTML") != -1 || navigator.userAgent.indexOf('Opera') >= 0) {
    document.getElementById("comet_iframe").src = url;
    } else {
    comet.connection.removeChild(comet.iframediv);
    document.body.removeChild(comet.connection);
    comet.iframediv.setAttribute('src', url);
    comet.connection.appendChild(comet.iframediv);
    document.body.appendChild(comet.connection);
    }
    },
    onUnload: function() {
    if (comet.connection) {
    comet.connection = false;
    }
    }
    }

    if (window.addEventListener) {
    window.addEventListener("load", comet.initialize, false);
    window.addEventListener("unload", comet.onUnload, false);
    } else if (window.attachEvent) {
    window.attachEvent("onload", comet.initialize);
    window.attachEvent("onunload", comet.onUnload);
    }
    需要注意的是comet這個對象在初始化(initialize)和超時(timeout)時的處理方法,能夠在IE以及火狐下面表現的完美,不會出現正在加載中標志。當然超時方法(timeout),是在服務器端通知客戶端調用。在Chrome和Opera下面一直有進度條顯示,暫時沒有找到好的解決辦法。
    后臺處理客戶端請求請求代碼:
    /**
    * 負責客戶端的推送
    * @author yongboy
    * @date 2011-1-13
    * @version 1.0
    */
    @WebServlet(urlPatterns = { "/blogpush" }, asyncSupported = true)
    public class BlogPushAction extends HttpServlet {
    private static final long serialVersionUID = 8546832356595L;
    private static final Log log = LogFactory.getLog(BlogPushAction.class);

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {

    response.setHeader("Cache-Control", "private");
    response.setHeader("Pragma", "no-cache");
    response.setContentType("text/html;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    final PrintWriter writer = response.getWriter();

    // 創建Comet Iframe
    writer.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");
    writer.println("<script type=\"text/javascript\">var comet = window.parent.comet;</script>");
    writer.flush();

    final AsyncContext ac = request.startAsync();
    ac.setTimeout(10 * 60 * 1000);// 10分鐘時間;tomcat7下默認為10000毫秒

    ac.addListener(new AsyncListener() {
    public void onComplete(AsyncEvent event) throws IOException {
    log.info("the event : " + event.toString()
    + " is complete now !");
    NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onTimeout(AsyncEvent event) throws IOException {
    log.info("the event : " + event.toString()
    + " is timeout now !");

    // 嘗試向客戶端發送超時方法調用,客戶端會再次請求/blogpush,周而復始
    log.info("try to notify the client the connection is timeout now ...");
    String alertStr = "<script type=\"text/javascript\">comet.timeout();</script>";
    writer.println(alertStr);
    writer.flush();
    writer.close();

    NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onError(AsyncEvent event) throws IOException {
    log.info("the event : " + event.toString() + " is error now !");
    NewBlogListener.ASYNC_AJAX_QUEUE.remove(ac);
    }

    public void onStartAsync(AsyncEvent event) throws IOException {
    log.info("the event : " + event.toString()
    + " is Start Async now !");
    }
    });

    NewBlogListener.ASYNC_AJAX_QUEUE.add(ac);
    }
    }
    每一個請求都需要request.startAsync(request,response)啟動異步處理,得到AsyncContext對象,設置超時處理時間(這里設置10分鐘時間),注冊一個異步監聽器。
    異步監聽器可以在異步請求于啟動、完成、超時、錯誤發生時得到通知,屬于事件傳遞機制,從而更好對資源處理等。
    在長連接超時(onTimeout)事件中,服務器會主動通知客戶端再次進行請求注冊。
    若中間客戶端非正常關閉,在超時后,服務器端推送數量就減少了無效的連接。在真正應用中,需要尋覓一個較為理想的值,以保證服務器的有效連接數,又不至于浪費多余的連接。
    每一個異步請求會被存放在一個高效并發隊列中,在一個線程中統一處理,具體邏輯代碼:
    /**
    * 監聽器單獨線程推送到客戶端
    * @author yongboy
    * @date 2011-1-13
    * @version 1.0
    */
    @WebListener
    public class NewBlogListener implements ServletContextListener {
    private static final Log log = LogFactory.getLog(NewBlogListener.class);
    public static final BlockingQueue<MicBlog> BLOG_QUEUE = new LinkedBlockingDeque<MicBlog>();
    public static final Queue<AsyncContext> ASYNC_AJAX_QUEUE = new ConcurrentLinkedQueue<AsyncContext>();
    private static final String TARGET_STRING = "<script type=\"text/javascript\">comet.showMsg(%s);</script>";

    private String getFormatContent(MicBlog blog) {
    return String.format(TARGET_STRING, buildJsonString(blog));
    }

    public void contextDestroyed(ServletContextEvent arg0) {
    log.info("context is destroyed!");
    }

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    log.info("context is initialized!");
    // 啟動一個線程處理線程隊列
    new Thread(runnable).start();
    }

    private Runnable runnable = new Runnable() {
    public void run() {
    boolean isDone = true;

    while (isDone) {
    if (!BLOG_QUEUE.isEmpty()) {
    try {
    log.info("ASYNC_AJAX_QUEUE size : "
    + ASYNC_AJAX_QUEUE.size());
    MicBlog blog = BLOG_QUEUE.take();

    if (ASYNC_AJAX_QUEUE.isEmpty()) {
    continue;
    }

    String targetJSON = getFormatContent(blog);

    for (AsyncContext context : ASYNC_AJAX_QUEUE) {
    if (context == null) {
    log.info("the current ASYNC_AJAX_QUEUE is null now !");
    continue;
    }
    log.info(context.toString());
    PrintWriter out = context.getResponse().getWriter();

    if (out == null) {
    log.info("the current ASYNC_AJAX_QUEUE's PrintWriter is null !");
    continue;
    }

    out.println(targetJSON);
    out.flush();
    }
    } catch (Exception e) {
    e.printStackTrace();
    isDone = false;
    }
    }
    }
    }
    };

    private static String buildJsonString(MicBlog blog) {
    Map<String, Object> info = new HashMap<String, Object>();
    info.put("content", blog.getContent());
    info.put("date",
    DateFormatUtils.format(blog.getPubDate(), "HH:mm:ss SSS"));

    JSONObject jsonObject = JSONObject.fromObject(info);

    return jsonObject.toString();
    }
    }
    異步請求上下文AsyncContext獲取輸出對象(response),向客戶端傳遞JSON格式化序列對象,具體怎么解析、顯示,由客戶端(見comet.js)決定。
    鑒于Servlet為單實例多線程,最佳實踐建議是不要在servlet中啟動單獨的線程,本文放在ServletContextListener監聽器中,以便在WEB站點啟動時中,創建一個獨立線程,在有新的博文內容時,遍歷推送所有已注冊客戶端
    整個流程梳理一下:
    1. 客戶端請求 blog.html
    2. blog.html的comet.js開始注冊啟動事件
    3. JS產生一個iframe,在iframe中請求/blogpush,注冊異步連接,設定超時為10分鐘,注冊異步監聽器
    4. 服務器接收到請求,添加異步連接到隊列中
    5. 客戶端處于等待狀態(長連接一直建立),等待被調用
    6. 后臺發布新的博客文章
    7. 博客文章被放入到隊列中
    8. 一直在守候的獨立線程處理博客文章隊列;把博客文章格式化成JSON對象,一一輪詢推送到客戶端
    9. 客戶端JS方法被調用,進行JSON對象解析,組裝HTML代碼,顯示在當前頁面上
    10. 超時發生時,/blogpush通知客戶端已經超時,調用超時(timeout)方法;同時從異步連接隊列中刪除
    11. 客戶端接到通知,對iframe進行操作,再次進行連接請求,重復步驟2
    大致流程圖,如下:
    diagram2

    其連接模型,偷懶,借用IBM上一張圖片說明:


    nieyong 2011-01-10 10:57 發表評論
    ]]>
    關于代碼片段分享的那些事http://www.aygfsteel.com/yongboy/archive/2011/01/05/346207.htmlnieyongnieyongWed, 05 Jan 2011 10:10:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/05/346207.htmlhttp://www.aygfsteel.com/yongboy/comments/346207.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/05/346207.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346207.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346207.html作為程序員,想必有時候必須要分享一些代碼,或單獨分享一段代碼,或粘貼在博客文章中,若使用到了現有的一些博客BSP服務(諸如:CSDN,JAVAEYE,BLOGJAVA.NET)編輯器具有代碼粘貼服務,若使用到了blogger.com, wordpress自然有些難處。按照自己有限實踐分一下類:1。Eclipse平臺
         安裝java2html插件,官方地址為:http://www.java2html.de/
         這里有一篇向導,可以閱讀大致了解一下:
         Java2Html使用詳解
         缺陷:對JAVA代碼格式化有效,生成的代碼有些雜亂,并且對HTML等代碼無能為力。
    2。在線工具
        http://www.palfrader.org/code2html/code2html.html
        個人沒有嘗試成功,放棄。
       http://codepaste.net
       嚴重推薦,注冊賬號可以使用谷歌OPENID服務,方便。所粘貼的代碼支持輸出RSS,方便匯總與訂閱。整個操作速度都很快。
       雖支持代碼格式不算多,但夠用。
       http://www.copypastecode.com/
       支持格式很多,也很細。操作簡單,不用登陸即可粘貼分享。沒有廣告在旁邊閃爍,可設密碼,采用WORDPRESS程序改造而成,在表單提交時速度有些慢。
       https://gist.github.com/
       這個是GITHUB項目托管網站提供的附屬功能,優點是簡潔無比,從下面的截圖可以略知一二:
    image
    功能相比以上兩個稍微差些,但對代碼分享來說,已經夠用。
    3。Windows Live Writer
              很贊,但默認情況下對程序代碼格式化不支持。可以采用下面這么一個插件:
              Insert Code for Windows Live Writer
    4。代碼高亮JS&CSS框架
    推薦有兩個:
    1)。Prettify ,很輕,很方便,可以很方便放入Blogger平臺里面或者Wordpress中,教程;或者單獨使用也是可以的,教程。推薦使用。
    2)。SyntaxHighlighter,也很出名,但安裝較為麻煩,官網有詳細教程。

    5. Postable工具
       有時候需要特殊符號需要修改一下,比如 “<”需要修改成“&lt;”,“>”需要修改成“&gt;”,要是手動修改很麻煩的,推薦兩個在線版本:
    http://www.elliotswan.com/postable/
    http://www.khurshid.com/i-make-postable/
    暫時就是用到這么多了,若有新的,順手的,會不定期更新。

    nieyong 2011-01-05 18:10 發表評論
    ]]>
    Servlet 3.0筆記之包含在JAR文件中可直接訪問的資源文件特性(資源綁定)http://www.aygfsteel.com/yongboy/archive/2011/01/03/346208.htmlnieyongnieyongMon, 03 Jan 2011 04:20:00 GMThttp://www.aygfsteel.com/yongboy/archive/2011/01/03/346208.htmlhttp://www.aygfsteel.com/yongboy/comments/346208.htmlhttp://www.aygfsteel.com/yongboy/archive/2011/01/03/346208.html#Feedback1http://www.aygfsteel.com/yongboy/comments/commentRss/346208.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346208.html
    這次標題夠長的,在Servlet3協議規范中,包含在JAR文件/META-INFO/resources/路徑下的資源可以直接訪問了。這么說一說,可能感覺不到到底有什么好處,以往的JSP或者HTML頁面只能存在站點的目錄下,或者在WEB-INF目錄下(只是不能直接訪問)。
    規范說,${jar}/META-INF/resources/被視為根目錄,假設home.jsp被放在${jar}/META-INF/resources/home.jsp,用戶可以直接通過 http://域名/home.jsp 訪問了。
    呈現一個常見的代碼片段:
    /**
    * 簡單示范
    * @author yongboy
    * @date 2011-1-16
    * @version 1.0
    */
    @WebServlet("/jarHome")
    public class HelloJarServlet extends HttpServlet {
    private static final long serialVersionUID = 6177346686921106540L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    request.setAttribute("date", new Date());
    request.getRequestDispatcher("/jarpage/jarhome.jsp").forward(request, response);
    }
    }
    而jarhome.jsp文件路徑則位于 ${jar}/META-INF/resources/jarpage/jarhome.jsp
    當然jarhome.jsp文件則沒有什么特別之處:
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="css/style.css" type="text/css" />
    <title>Welcome to jar home</title>
    </head>
    <body>
    <img alt="j2ee" src="img/j2ee.png" /><br/>
    <br/>
    now date : <%=((java.util.Date)request.getAttribute("date")).toString()%>
    </body>
    </html>
    jarhome.jsp文件所引用css/style.css和img/j2ee.png等文件分別位于 ${jar}/META-INF/resources/css/style.css${jar}/META-INF/resources/img/j2ee.png目錄下。
    把生成的jar文件存放在 WEB-INF/lib/ 中,下面為一個運行示范圖:

    樣式和圖片等都可以正常訪問。
    有時候可能需要使用路徑信息等,再看一個示范吧:
    /**
    * 演示jarDemo
    *
    * @author yongboy
    * @date 2011-1-16
    * @version 1.0
    */
    @WebServlet("/jarDemo")
    public class DemoWebINFPagesServlet extends HttpServlet {
    private static final long serialVersionUID = -1040850432869481349L;
    private static final Log log = LogFactory
    .getLog(DemoWebINFPagesServlet.class);

    @SuppressWarnings("deprecation")
    @Override
    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    log.info("the /jarDemo is accessed now!");

    log.info("getRealPath : " + request.getRealPath("/"));
    log.info("ServletContext : getRealPath : "
    + getServletContext().getRealPath("/"));
    log.info("getPathTranslated : " + request.getPathTranslated());

    log.info("get jar's resource:");

    InputStream is = getServletContext().getResourceAsStream(
    "/jarfile/demo.txt");
    log.info("the JAR/META-INF/resources/jarfile/demo.txt's content is :\n"
    + IOUtils.toString(is));

    request.getRequestDispatcher("/WEB-INF/pages/notaccess.jsp");
    }
    }
    輸入命令行信息為:
    [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798292 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - the /jarDemo is accessed now!
    [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798292 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - getRealPath : /home/yongboy/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/d/
    [framework] 2011-01-03 11:45:16,664 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798292 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - ServletContext : getRealPath : /home/yongboy/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/d/
    [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798293 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - getPathTranslated : null
    [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798293 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - get jar's resource:
    [framework] 2011-01-03 11:45:16,665 - com.learn.servlet3.jardync.DemoWebINFPagesServlet -798293 [http-8080-exec-6] INFO  com.learn.servlet3.jardync.DemoWebINFPagesServlet  - the ${JAR}/META-INF/resources/jarfile/demo.txt's content is :
    haha,the demo.s's content!
    haha,haha,haha!
    可以看到getRealPath("/")得到的是項目的根路徑(可以參照JAR解壓后的路徑)。而request.getPathTranslated則因為是包含在jar文件中(規范中說包含在遠程主機、數據庫、JAR存檔文件中,getPathTranslated都會返回null),則返回null。
    包含在jar中的資源文件,一樣可以使用getResourceAsStream獲取到。
    再來看看jar文件META-INF/resources目錄下文件結構:
    • css/style.css
    • img/j2ee.png
    • jarfile/demo.txt
    • jarpage/jarhome.jsp
    • jsp/h.jsp
    • jsp/helloWorld.jsp
    很顯然,就是一個小型站點目錄結構。
    每一個模塊,建立一個WEB站點應用,使用ANT腳本自動打包成jar文件,拷貝到真正站點WEB-INF/lib下。
    假設一個JAR文件包含一個具體的模塊,那么模塊的部署與裝載將是十分方便的。


    nieyong 2011-01-03 12:20 發表評論
    ]]>
    servlet 3.0筆記之servlet的動態注冊http://www.aygfsteel.com/yongboy/archive/2010/12/30/346209.htmlnieyongnieyongThu, 30 Dec 2010 09:27:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/12/30/346209.htmlhttp://www.aygfsteel.com/yongboy/comments/346209.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/12/30/346209.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346209.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346209.html在Servlet3.0中可以動態注冊Servlet,Filter,Listener,在ServletContext對應注冊API為:
      /**
       * 添加Servlet
       */
      public ServletRegistration.Dynamic addServlet(String servletName,
          String className);

      public ServletRegistration.Dynamic addServlet(String servletName,
          Servlet servlet);

      public ServletRegistration.Dynamic addServlet(String servletName,
          Class<? extends Servlet> servletClass);

      /**
       * 添加Filter
       */
      public FilterRegistration.Dynamic addFilter(String filterName,
          String className);

      public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);

      public FilterRegistration.Dynamic addFilter(String filterName,
          Class<? extends Filter> filterClass);

      /**
       * 添加Listener
       */
      public void addListener(String className);

      public <T extends EventListener> void addListener(T t);

      public void addListener(Class<? extends EventListener> listenerClass);
    每個組件注冊都提供三個方法,很細心。
    下面談談動態注冊Servlet,但不要希望太高,只能在初始化時進行注冊。在運行時為了安全原因,無法完成注冊。在初始化情況下的注冊Servlet組件有兩種方法:
    1.實現ServletContextListener接口,在contextInitialized方法中完成注冊.
    2.在jar文件中放入實現ServletContainerInitializer接口的初始化器
    先說在ServletContextListener監聽器中完成注冊。
        public void contextInitialized(ServletContextEvent sce) {

            ServletContext sc = sce.getServletContext();

            // Register Servlet
            ServletRegistration sr = sc.addServlet("DynamicServlet",
                "web.servlet.dynamicregistration_war.TestServlet");
            sr.setInitParameter("servletInitName", "servletInitValue");
            sr.addMapping("/*");

            // Register Filter
            FilterRegistration fr = sc.addFilter("DynamicFilter",
                "web.servlet.dynamicregistration_war.TestFilter");
            fr.setInitParameter("filterInitName", "filterInitValue");
            fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST),
                                         true, "DynamicServlet");

            // Register ServletRequestListener
            sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener");
        }
    很簡單,難度不大。
    再說說在jar文件中的servlet組件注冊,需要在jar包含META-INF/services/javax.servlet.ServletContainerInitializer文件,文件內容為已經實現ServletContainerInitializer接口的類:
    com.learn.servlet3.jardync.CustomServletContainerInitializer
    該實現部分代碼:
    @HandlesTypes({ JarWelcomeServlet.class })
    public class CustomServletContainerInitializer implements
        ServletContainerInitializer {
      private static final Log log = LogFactory
          .getLog(CustomServletContainerInitializer.class);

      private static final String JAR_HELLO_URL = "/jarhello";

      public void onStartup(Set<Class<?>> c, ServletContext servletContext)
          throws ServletException {
        log.info("CustomServletContainerInitializer is loaded here...");
       
        log.info("now ready to add servlet : " + JarWelcomeServlet.class.getName());
       
        ServletRegistration.Dynamic servlet = servletContext.addServlet(
            JarWelcomeServlet.class.getSimpleName(),
            JarWelcomeServlet.class);
        servlet.addMapping(JAR_HELLO_URL);

        log.info("now ready to add filter : " + JarWelcomeFilter.class.getName());
        FilterRegistration.Dynamic filter = servletContext.addFilter(
            JarWelcomeFilter.class.getSimpleName(), JarWelcomeFilter.class);

        EnumSet<DispatcherType> dispatcherTypes = EnumSet
            .allOf(DispatcherType.class);
        dispatcherTypes.add(DispatcherType.REQUEST);
        dispatcherTypes.add(DispatcherType.FORWARD);

        filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL);

        log.info("now ready to add listener : " + JarWelcomeListener.class.getName());
        servletContext.addListener(JarWelcomeListener.class);
      }
    }
    其中@HandlesTypes注解表示CustomServletContainerInitializer 可以處理的類,在onStartup 方法中,可以通過Set<Class<?>> c 獲取得到。
    jar文件中不但可以包含需要自定義注冊的servlet,也可以包含應用注解的servlet,具體怎么做,視具體環境而定。
    把處理某類事物的servlet組件打包成jar文件,有利于部署和傳輸,功能不要了,直接去除掉jar即可,方便至極!

    nieyong 2010-12-30 17:27 發表評論
    ]]>
    ServletRest對XML配置文件的支持http://www.aygfsteel.com/yongboy/archive/2010/10/04/333735.htmlnieyongnieyongMon, 04 Oct 2010 10:19:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/10/04/333735.htmlhttp://www.aygfsteel.com/yongboy/comments/333735.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/10/04/333735.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/333735.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/333735.html雖然注解很方便,簡單:

    @RestSupport("/book/*/chapter/*")
    但耦合性較強,每寫一個servlet都要添加上一個注解,想降低耦合或者URL經常變動者,可以試試使用XML進行配置:
      <filter>
        <filter-name>RestFilter</filter-name>
        <filter-class>com.servlet.rest.RestFilter</filter-class>
        <init-param>
          <param-name>scanPackage</param-name>
          <param-value>/servlets.xml,/servlets2.xml</param-value>
        </init-param>    
      </filter>
      <filter-mapping>
        <filter-name>RestFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>

    為scanPackage參數添加需要傳入的XML文件即可,多個XML配置文件之間需要使用逗號隔開

    我們再看看servlets.xml配置文件內容:

    <?xml version="1.0" encoding="UTF-8"?>
    <servlets>
    	<servlet>
    		<class>com.yong.test.servlet.xml.WelcomeServlet</class>
    		<url>/</url>
    		<url>/home/</url>
    		<url>/welcome/</url>
    	</servlet>
    	<servlet>
    		<class>com.yong.test.servlet.xml.UserHomeAction</class>
    		<url>/user/</url>
    	</servlet>
    </servlets>

    具體到單個servlet配置為

    	<servlet>
    		<class>servlet 類路徑</class>
    		<url>對應URL1</url>
                  <url>對應URL2</url>
    	</servlet>

    其實很簡單的說,嗯,不要忘記xml一定要放在classpath相應位置。


     

     



    nieyong 2010-10-04 18:19 發表評論
    ]]>
    介紹一個為Servlet增加REST URL支持的超小框架,一點都不侵入!http://www.aygfsteel.com/yongboy/archive/2010/10/01/333609.htmlnieyongnieyongFri, 01 Oct 2010 08:06:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/10/01/333609.htmlhttp://www.aygfsteel.com/yongboy/comments/333609.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/10/01/333609.html#Feedback1http://www.aygfsteel.com/yongboy/comments/commentRss/333609.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/333609.html這個框架(ServletRest)最近剛剛被提交上谷歌代碼托管,目標是為servlet增加rest風格URL支持,僅僅如此而已,目標非常明確:不做那么多,只做一點點。

    說這個很輕、很微小的框架,一點都不過分,只有10個java文件,只是用注解的話,那就可以減少為7個java文件。

    說這個小東西目標明確,一點不摻假,就是為現有的servlet增加rest風格的URL支持,僅此而已。

    我們表示一個具有結構形式的URL為:

    /book/head first java/chapter/12 看圖說故事

    傳統的servlet在URL處怎么映射呢 ?

    /book/*

    那么chapter怎么辦 ?

    /chapter/*(這里假設/book/*排在較上位置)

    顯然上面的鏈接地址則很難到達 /chapter/*。當然會有兄弟跳出來說,這一切可以交給 /book/*進行處理,嗯,book也一并處理了chapter部分,當然是可以的,帶來了責任不單一的問題了,混雜在一起,造成代碼管理的混亂。

    那么怎么在ServletRest里面怎么做呢 ?

    @RestSupport("/book/*/chapter/*")
    其風格完全是以往的servlet映射的風格,只不過支持的參數多了些。
    更重要的是以往的Servlet編程經驗一點都沒有舍棄,都還保留著。在ServletRest里沒有強迫人們按照新的繼承格式創造一個處理類,還是和以往一樣,創建一個需要繼承 HttpServlet 的servlet,重寫GET、POST、DELETE、PUT等方法,在類的合適位置添加上注解即可:
    @RestSupport("/book/*/chapter/*") 即可。當然這部分可以在xml文件中完成配置,下一篇文章會講到。
    這里有一個示范:
    @RestSupport("/book/*/chapter/*")
    public class ChapterServlet extends HttpServlet {
            private static final long serialVersionUID = -1534235656L;
    
            protected void doGet(HttpServletRequest request,
                            HttpServletResponse response) throws ServletException, IOException {
                    // code here ...
            }
    
            protected void doPost(HttpServletRequest request,
                            HttpServletResponse response) throws ServletException, IOException {
                    // code here ...
            }
    
            protected void doPut(HttpServletRequest request,
                            HttpServletResponse response) throws ServletException, IOException {
                    // code here ...
            }
    
            protected void doDelete(HttpServletRequest request,
                            HttpServletResponse response) throws ServletException, IOException {
                    // code here ...
            }
    }
    RestSupport同時支持多個url @RestSupport({"url1","url2"}),除此之外,沒有多余功能。

     

    那么怎么在項目中使用呢 ?

    在web.xml 中配置filter:

    <filter>
      <filter-name>RestFilter</filter-name> 
      <filter-class>com.servlet.rest.RestFilter</filter-class> 
      <init-param>
        <param-name>scanPackage</param-name> 
        <param-value>com.yong.test.servlet</param-value> 
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>RestFilter</filter-name> 
      <url-pattern>/*</url-pattern> 
    </filter-mapping>

    scanPackage需要接收一個需要掃描的包的參數,若有多個包,可以包路徑之間有一個分隔符逗號“,”,eg:

    com.yong.test.servlet,com.xiaomin.bookstore

    ServletRest 注解需要JDK 1.5支持,Servlet 2.*, 3.* 版本,僅僅把ServletRest-0.8.jar (下載地址)放到項目classpath中,并且不依賴于第三方JAR包,除了servlet.jar之外。

    在運行期間可以對Servlet的進行動態裝載和卸載等操作,ServletRest已經封裝了相應的接口(必須從全局Context中獲取,下面代碼是從示范JSP代碼中摘取):

    		ServletFactory servletFactory = (ServletFactory)application.getAttribute("servletFactory");
    		
    		// 注冊新的Servlet
    		String mappingUrl = "新的Servlet映射URL";
    		Class servletClass = Class.forName("要映射的Servlet Class路徑");
    		
    		servletFactory.register(mappingUrl, servletClass);
    		
    		// 注銷Servlet
    		//servletFactory.destory(servletClass);

     

    假如不在JSP中操作,那就需要:

    getServletContext().getAttribute("servletFactory");

    ServletRest遵循的原則和原有的servlet容器處理方式一致,一個URL對應一個Servlet實例原則。

    更多信息請閱讀ServletRest源代碼。



    nieyong 2010-10-01 16:06 發表評論
    ]]>
    Servlet 3.0筆記之體驗可插拔特性,以及在實際中可能的應用范圍http://www.aygfsteel.com/yongboy/archive/2010/07/05/346223.htmlnieyongnieyongMon, 05 Jul 2010 14:08:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/07/05/346223.htmlhttp://www.aygfsteel.com/yongboy/comments/346223.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/07/05/346223.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346223.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346223.htmlwebfragment
    建立Servlet,Filter,Listener等,沒有什么區別。
    使用Web Fragment,較為麻煩的是頁面文件的存放,其它到沒有多大區別。但有以下兩個方法解決:

    1. 使用ANT編寫 build.xml 文件,自動打成JAR包,假如有HTML文件的話,可以把HTML等頁面文件部署到主項目的部署設定目錄下
            比如,我們設置轉向到oneuser.html文件:
           request.getRequestDispatcher("/oneuser.html").forward(request, response);



             就需要在發布時把oneuser.html文件放在主項目的部署根目錄下,其它路徑,以此類推。
            建議編寫ANT腳本搞定。


    2. 把頁面文件也打進JAR包,使用Freemarker硬編碼實現頁面渲染。這種方式有些硬,另一方面移植性好,一個JAR包直接包含了Servlet 和頁面文件,但會帶來修改頁面文件的麻煩。
    下面為演示如何使用Freemarker實現硬編碼:

    /**
    * 這次,我們把模板文件放在JAR文件包中
    *
    * @author xiaomin
    *
    */
    @WebServlet("/some/")
    public class ViewSomethingAction extends HttpServlet {
        private static final long serialVersionUID = 65464645L;
        private static final String DEFAULT_ENCODING = "UTF-8";
        private static final String TEMPLATE_NAME = "some";
        private String templateString;
        private Configuration configuration;
        public void init() throws ServletException {
            configuration = new Configuration();
            configuration.setDefaultEncoding(DEFAULT_ENCODING);
            configuration.setEncoding(Locale.CHINA, DEFAULT_ENCODING);
            // 初始化模板
            templateString = getTemplateString("pages/something.html");
        }
        protected void doGet(HttpServletRequest request,
                HttpServletResponse response) throws ServletException, IOException {
            // 構造要傳遞到模板的屬性值
            Map<String, Object> map = new HashMap<String, Object>();       
            map.put("userName", "小敏");
            response.setContentType("text/html; charset=" + DEFAULT_ENCODING);
            printResponsesString(response, map);
        }
        /**
         * 輸出請求內容
         * @param response
         * @param map
         * @throws IOException
         */
        private void printResponsesString(HttpServletResponse response,
                Map<String, Object> map) throws IOException {
            Template template = new Template(TEMPLATE_NAME, new StringReader(
                    templateString), configuration, DEFAULT_ENCODING);
            Writer out = response.getWriter();
            try {
                template.process(map, out);
            } catch (TemplateException e) {
                e.printStackTrace();
            } finally {
                out.flush();
                template = null;
            }
        }

        /**
         * 獲取JAR包內的HTML模板文件內容
         * @param jarHtmlPath eg: pages/something.html
         * @return
         */
        private String getTemplateString(String jarHtmlPath) {
            ClassLoader myCL = this.getClass().getClassLoader();
            InputStream is = myCL.getResourceAsStream(jarHtmlPath);
            if (is == null) {
                return null;
            } else {
                try {
                    return templateString = IOUtils.toString(is, DEFAULT_ENCODING);
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }
    }

    運行效果如圖:
    something
    注意在Eclipse下,可在Web Fragment項目上點擊運行,即可正常運行主項目,見下圖:
    web fragment startup
    當然也可以在主項目上點擊運行,依然可以運行。
    關于多個Web Fragment之間順序加載問題,可以參閱如下文章:
    http://blogs.sun.com/swchan/entry/servlet_3_0_web_fragment
    獲得更多認知。
    小結一下Servlet Web Fragment 可能在以下情況下很受用:
    1. 作為用戶攔截、日志記錄,實現項目之間的解耦。
    2. 提供RSS訂閱模塊
    3. 后臺管理
    4. 項目檢測等
    5. 不需要頁面的模塊
    ......
    最后附加上一個JAR文件,里面包含了源代碼和要發布的文件:
    本次項目演示,依賴JAR:
    commons-io-1.4.jar
    freemarker-2.3.13.jar
    commons-lang-2.3.jar
    本次項目JAR文件:
    下載
    下次寫些什么呢,不如實現更加友好的URL,也來一把REST,讓URL簡單一些。

    nieyong 2010-07-05 22:08 發表評論
    ]]>
    Servlet 3.0筆記之使用Freemarker替代JSP,更快更輕更高效http://www.aygfsteel.com/yongboy/archive/2010/07/04/346224.htmlnieyongnieyongSat, 03 Jul 2010 18:36:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/07/04/346224.htmlhttp://www.aygfsteel.com/yongboy/comments/346224.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/07/04/346224.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346224.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346224.html很輕的,Servlet + Freemarker 組合體,沒有那么硬~》,不過那是基于Servlet 2.× 系列的,今天談談如何在Servlet 3.0 下使用Freemarker進行更快的開發方式。
    Servlet 3.0的強大、簡單的,擺脫以前的約束,重構類名還得需要到web.xml中手動修改,如今再也沒有那么多煩惱,當然這僅僅是一個側面而已,就已經說明了其強大。
    Freemarker強大的模板化能力,據說解析速度超越JSP,讓討厭JSP+ JAVA混合體編程的人得到一種解脫,身心的。還有一點就是快速的模型填充,不需要隨處可見的JAVA代碼,任何角落都是。
    總之:Servlet 3.0 + Freemarker, 超級輕的MVC組合,讓人愉悅。
    閑話短說,先來一個Servlet + JSP組合體:

    TemplateTestAction.java@WebServlet("/test")
    public class TemplateTestAction extends HttpServlet {
    private static final long serialVersionUID = 88687687987980L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String id = "1687";

    String title = "JSP測試";
    String content = "這是JSP測試";

    request.setAttribute("blog", new UserBlog(id, title, content, new Date()));

    request.getRequestDispatcher("/WEB-INF/pages/template.jsp").forward(request, response);
    }
    }
    對應JSP:
    template.jsp<%@page import="java.text.SimpleDateFormat"%>
    <%@page import="com.demo.UserBlog"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>JSP + Servlet</title>
    </head>
    <body>
    <%UserBlog blog = (UserBlog)request.getAttribute("blog"); %>
    <h1>ID : <%=blog.getId() %></h1>
    <h5>TITLE : <%=blog.getTitle() %></h5>
    <p>DATETIME : <%=new SimpleDateFormat("yyyy-MM-dd HH:mm").format(blog.getDate()) %></p>
    <div>
    CONTENT : <br/>
    <p><%=blog.getContent() %></p>
    </div>
    </body>
    </html>

    看看代碼,以前大家也都是這些寫過來的。
    對比一下Servlet + Freemarker :
    TemplateTest1Action.java@WebServlet("/test1")
    public class TemplateTest1Action extends HttpServlet {
    private static final long serialVersionUID = 6576879808909808L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String id = "1688";

    String title = "使用freemarker";
    String content = "這是測試";

    request.setAttribute("blog", new UserBlog(id, title, content, new Date()));

    request.getRequestDispatcher("/WEB-INF/pages/template1.html").forward(request, response);
    }
    }

    Servlet代碼沒有發生什么變化,只是這次轉向了html文件:
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Freemaker模板測試1</title>
    </head>
    <body>
    <h1>ID : ${blog.id}</h1>
    <h5>TITLE : ${blog.title}</h5>
    <p>DATETIME : ${blog.date?string("yyyy-MM-dd HH:mm")}</p>
    <div>CONTENT : <br />
    <p>${blog.content}</p>
    </div>
    </body>
    </html>
    請再次對比一下JSP 和 html文件的區別,您會選擇寫HTML還是JSP頁面呢 ?
    要想讓TemplateTest1Action轉向HTML頁面生效,您需要配置一個Freemarker的控制器,用以解析html頁面。
    這里控制器為:TemplateController.java 文件:
    @WebServlet(
    urlPatterns = {"*.html"}, // 需要定義Freemarker解析的頁面后綴類型
    asyncSupported = false,
    loadOnStartup = 0,
    name = "templateController",
    displayName = "TemplateController",
    initParams = {
    @WebInitParam(name = "TemplatePath", value = "/"),
    @WebInitParam(name = "NoCache", value = "true"),//定義是否緩存
    @WebInitParam(name = "ContentType", value = "text/html; charset=UTF-8"),// 定義內容類型
    @WebInitParam(name = "template_update_delay", value = "0"), // 開發環境中可設置為0
    @WebInitParam(name = "default_encoding", value = "UTF-8"),
    @WebInitParam(name = "number_format", value = "0.##########")
    }
    )
    public class TemplateController extends FreemarkerServlet {
    private static final long serialVersionUID = 8714019900490761087L;
    }
    這里僅僅需要繼承FreemarkerServlet即可,再加上一些注解即可,內容代碼不用寫。
    當然也可以省去TemplateController,直接在web.xml文件中配置:
      <servlet>
    <servlet-name>freemarker</servlet-name>
    <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
    <init-param>
    <param-name>TemplatePath</param-name>
    <param-value>/</param-value>
    </init-param>
    <init-param>
    <param-name>NoCache</param-name>
    <param-value>true</param-value>
    </init-param>
    <init-param>
    <param-name>ContentType</param-name>
    <param-value>text/html; charset=UTF-8</param-value>
    </init-param>
    <init-param>
    <param-name>template_update_delay</param-name>
    <param-value>0</param-value>
    </init-param>
    <init-param>
    <param-name>default_encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
    <param-name>number_format</param-name>
    <param-value>0.##########</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>freemarker</servlet-name>
    <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    記得把 freemarker-2.3.13.jar 文件扔進WEB-INF/lib 目錄下。
    項目源代碼下載地址:
    下載地址
    接下來一篇將體驗一下Servlet 3.0 的WebFragment功能,支持組件、功能的插拔,使之可以模塊化構造一個站點服務,大的跨越,一個變革,必將受益開發者社區。


    nieyong 2010-07-04 02:36 發表評論
    ]]>
    Servlet 3.0筆記之快速上手,快速體驗http://www.aygfsteel.com/yongboy/archive/2010/07/03/346225.htmlnieyongnieyongSat, 03 Jul 2010 11:22:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/07/03/346225.htmlhttp://www.aygfsteel.com/yongboy/comments/346225.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/07/03/346225.html#Feedback0http://www.aygfsteel.com/yongboy/comments/commentRss/346225.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346225.htmltomcat configure
    若不選擇JDK1.6或者JRE 1.6,會有提示錯誤信息。
    在Eclipse 3.6 新建一個“Dynamic Web Project”,主要配置如下:
    webapp configure
    然后就是新建一個Servlet : HomeAction.java 代碼如下:
    HomeAction.java@WebServlet("/home") //最簡單的注解方式
    public class HomeAction extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {

    response.setContentType("text/html; charset=UTF-8");

    PrintWriter out = response.getWriter();

    out.write("<html>");
    out.write("<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></head>");
    out.write("<body>");
    out.write("<h1>");
    out.write("Hello Servlet 3.0 !");
    out.write("</h1>");
    out.write("<div>");
    out.write("Welcome Here !");
    out.write("</div>");

    out.write("<div>");
    out.write("<a href=\"hello\">訪問無參數 hello </a>");
    out.write("<br/>");
    out.write("<a href=\"hello?user=xiaoI\">訪問參數為xiaoi </a>");

    out.write("</div>");


    out.write("</body>");

    out.write("</html>");

    out.flush();
    out.close();
    }

    protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    doGet(request, response);
    }
    }


    一個最簡單的Servlet注解 : @WebServlet(“/home”),簡單好記。

    下面看一個較為復雜的Servlet注解:

    @WebServlet(urlPatterns = {"/hello"}, asyncSupported = false, 
    loadOnStartup = -1, name = "HelloAction", displayName = "HelloAction",
    initParams = {@WebInitParam(name = "userName", value = "xiaomin")}
    )
    public class HelloAction extends HttpServlet {
    private static final long serialVersionUID = 9191552951446203732L;

    private static String defaultUserName = null;

    public void init() {
    defaultUserName = this.getInitParameter("userName");
    }

    @Override
    protected void doGet(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {

    String userName = request.getParameter("user");

    if (userName == null || userName.equals("")) {
    userName = defaultUserName;
    }

    request.setAttribute("userName", userName);

    // 轉向JSP進行處理
    request.getRequestDispatcher("/hello.jsp").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
    HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }



    再來學習一下WebServlet 的注解配置:



    屬性名 類型 描述
    name String 指定 Servlet 的 name 屬性,等價于 <servlet-name>。如果沒有顯式指定,則該 Servlet 的取值即為類的全限定名。
    value String[] 該屬性等價于 urlPatterns 屬性。兩個屬性不能同時使用。
    urlPatterns String[] 指定一組 Servlet 的 URL 匹配模式。等價于 <url-pattern> 標簽。
    loadOnStartup int 指定 Servlet 的加載順序,等價于 <load-on-startup> 標簽。
    initParams WebInitParam[] 指定一組 Servlet 初始化參數,等價于 <init-param> 標簽。
    asyncSupported boolean 聲明 Servlet 是否支持異步操作模式,等價于 <async-supported> 標簽。
    description String 該 Servlet 的描述信息,等價于 <description> 標簽。
    displayName String 該 Servlet 的顯示名,通常配合工具使用,等價于 <display-name> 標簽。

    再新建一個Filter:

    AccessFilter.java@WebFilter(urlPatterns = {"/hello"},
    dispatcherTypes = {DispatcherType.REQUEST,DispatcherType.INCLUDE, DispatcherType.FORWARD},
    initParams = {@WebInitParam(name = "encoding", value = "UTF-8")}
    )
    public class AccessFilter implements Filter {

    public void doFilter(ServletRequest req,
    ServletResponse rep, FilterChain filterChain)
    throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)req;

    System.out.println("datetime : " + new Date() + " ip : " + request.getRemoteAddr() + " url : " + request.getRequestURL());

    filterChain.doFilter(req, rep);
    }

    public void destroy() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }
    }


    最簡單注解方式 @WebFilter(“/hello”),默認為對URL的攔截。

    各個配置參數意義如下:



    屬性名 類型 描述
    filterName String 指定過濾器的 name 屬性,等價于 <filter-name>
    value String[] 該屬性等價于 urlPatterns 屬性。但是兩者不應該同時使用。
    urlPatterns String[] 指定一組過濾器的 URL 匹配模式。等價于 <url-pattern> 標簽。
    servletNames String[] 指定過濾器將應用于哪些 Servlet。取值是 @WebServlet 中的 name 屬性的取值,或者是 web.xml 中 <servlet-name> 的取值。
    dispatcherTypes DispatcherType 指定過濾器的轉發模式。具體取值包括:
    ASYNC、ERROR、FORWARD、INCLUDE、REQUEST。
    initParams WebInitParam[] 指定一組過濾器初始化參數,等價于 <init-param> 標簽。
    asyncSupported boolean 聲明過濾器是否支持異步操作模式,等價于 <async-supported> 標簽。
    description String 該過濾器的描述信息,等價于 <description> 標簽。
    displayName String 該過濾器的顯示名,通常配合工具使用,等價于 <display-name> 標簽。



    現在再看看運行效果:

    image

    image

    簡單幾下就試用了新版本的Servlet 3.0 的一些特性,很簡單吧,省去了很多配置的繁雜。當然在這很短的時間內,尚有很多東西尚未體驗,不著急,慢慢來。有時間,我會一并發送上來。

    項目打包下載地址:

    下載地址

    下一篇將講如何替代JSP,讓開發速度更快一些;最初的Servlet + JavaBean + JSP 模式,可能已經不太適合現在企業的開發需求。我們需要的是很輕的開發方式,具體是什么,下篇見 :))

    nieyong 2010-07-03 19:22 發表評論
    ]]>
    Servlet 3.0筆記之開發環境搭建http://www.aygfsteel.com/yongboy/archive/2010/07/02/346226.htmlnieyongnieyongFri, 02 Jul 2010 13:41:00 GMThttp://www.aygfsteel.com/yongboy/archive/2010/07/02/346226.htmlhttp://www.aygfsteel.com/yongboy/comments/346226.htmlhttp://www.aygfsteel.com/yongboy/archive/2010/07/02/346226.html#Feedback1http://www.aygfsteel.com/yongboy/comments/commentRss/346226.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/346226.htmlTomcat 7 終于羞羞答答出現在眾人面前,她讓我等了一年多的時間,那個期盼呦。
    為啥呢,因為 Tomcat 7 支持Servlet 3.0 規范啊。雖然GlassFish也早就宣布支持 Servlet 3.0,但那個稍大的家伙有些重;只好等待很輕的tomcat 7小姑娘了。等啊等啊,她就到了。
    前幾天 Eclipse 3.6 也出世了,Java EE版本支持Servlet 3.0和tomcat 7,很好。
    既然一切都準備好了,那讓我們馬上開始吧。
    工欲善其事,必先利其器。下面列出進行Servlet 3.0 開發的必須環境。

    1. JDK 1.6
    這個沒得說,Tomcat 7 運行要求,最簡單安裝JRE 1.6即可。
    下載地址:
    http://java.sun.com/javase/downloads/index.jsp
    2. Eclipse 3.6
    嗯,支持servlet 3.0 注解的IDE,目前是最受歡迎開發工具。
    官網地址:
    http://www.eclipse.org/downloads/
    記得要選擇
     Eclipse IDE for Java EE Developers
    32位下載地址:
    http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/helios/R/eclipse-jee-helios-win32.zip

    3. Tomcat 7
    tomcat 7 下載地址:
    http://tomcat.apache.org/download-70.cgi
    若電腦不是64位,則推薦下載
    32-bit Windows zip
    http://labs.renren.com/apache-mirror/tomcat/tomcat-7/v7.0.0-beta/bin/apache-tomcat-7.0.0-windows-x86.zip
    確保以上軟件都安裝好,正常運行。
    備注:電腦上安裝JDK 1.5 或者更老版本的XD,需要注意相關環境設置。
    下一節,我們將做一個簡單的示范應用。

    nieyong 2010-07-02 21:41 發表評論
    ]]>
    很輕的,Servlet + Freemarker 組合體,沒有那么硬~http://www.aygfsteel.com/yongboy/archive/2009/11/10/301860.htmlnieyongnieyongTue, 10 Nov 2009 08:52:00 GMThttp://www.aygfsteel.com/yongboy/archive/2009/11/10/301860.htmlhttp://www.aygfsteel.com/yongboy/comments/301860.htmlhttp://www.aygfsteel.com/yongboy/archive/2009/11/10/301860.html#Feedback1http://www.aygfsteel.com/yongboy/comments/commentRss/301860.htmlhttp://www.aygfsteel.com/yongboy/services/trackbacks/301860.html老調重彈。對SSH經典組合有些膩,不再那么輕,重返到若干年前的原始。

    Servlet的輕巧高效,Freemarker的強大簡便,兩者結合將是超輕的組合,即可避免丑陋的Java代碼和HTML代碼雜揉,又可高效基于模板的站點開發。

    閑話少說,項目需要:

    freemarker-2.3.13.jar

    servlet.jar

    定義兩個Servlet:

    HelloAction.java 對應 /hello,借助Freemarker硬編碼輸出

    public class HelloAction extends HttpServlet {
        private static final long serialVersionUID = -6082007726831320176L;

        private Configuration configuration;
        public void init() throws ServletException {
            configuration = new Configuration();
            configuration.setServletContextForTemplateLoading(getServletContext(), "WEB-INF/pages");
            configuration.setEncoding(Locale.CHINA, "UTF-8");
        }

        @SuppressWarnings("unchecked")
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 填充數據類型
            Map map = new HashMap();
            map.put("userName", "小敏");   
            Template template = configuration.getTemplate("hello.html");
            response.setContentType("text/html; charset=" + template.getEncoding());
            Writer out = response.getWriter();
            try{
                template.process(map, out);
            }catch (TemplateException e) {
                e.printStackTrace();
            }
        }

        public void destroy() {
            super.destroy();
            if(configuration != null){
                configuration = null;
            }
        }
    }

    對應模板:

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>使用Freemarker渲染2</title>
    </head>
    <body>
    你好, ${userName!} !
    </body>
    </html>

     

    HiAction.java 對應 /hi ,借助Freemrker Servlet的攔截功能,如以往寫代碼方式,感覺不到Freemarker的存在。

    public class HiAction extends HttpServlet {
        private static final long serialVersionUID = 518767483952153077L;

        public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

            request.setAttribute("thename", "小敏");
            request.getRequestDispatcher("/WEB-INF/pages/hi.html").forward(request, response);
        }
    }

    對應的模板:

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>使用Freemarker渲染</title>
    </head>
    <body>
    hi ${thename!}~<br />
    </body>
    </html>

    但需要在web.xml 配置文件中定義如下:

    <servlet>
        <servlet-name>freemarker</servlet-name>
        <servlet-class>
            freemarker.ext.servlet.FreemarkerServlet
        </servlet-class>

        <!-- FreemarkerServlet settings: -->
        <init-param>
            <param-name>TemplatePath</param-name>
            <param-value>/</param-value>
        </init-param>
        <init-param>
            <param-name>NoCache</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>ContentType</param-name>
            <param-value>text/html; charset=UTF-8</param-value>
            <!-- Forces UTF-8 output encoding! -->
        </init-param>

        <!-- FreeMarker settings: -->
        <init-param>
            <param-name>template_update_delay</param-name>
            <param-value>0</param-value><!-- 0 is for development only! Use higher value otherwise. -->
        </init-param>
        <init-param>
            <param-name>default_encoding</param-name>
            <param-value>UTF-8</param-value><!-- The encoding of the template files. -->
        </init-param>
        <init-param>
            <param-name>number_format</param-name>
            <param-value>0.##########</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>freemarker</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

    使用哪一種組合方式,看您喜好了。

    借助于Freemarker自身的Servlet工具,只是用于攔截Servlet中forward轉向使用到的HTML資源文件。

    很簡陋,但湊合著能看。

    項目源代碼已經打包如下:

    下載源文件



    nieyong 2009-11-10 16:52 發表評論
    ]]>
    主站蜘蛛池模板: 蓬安县| 北川| 大竹县| 巴林左旗| 信宜市| 邹平县| 体育| 富阳市| 凭祥市| 贵阳市| 邢台县| 文安县| 陈巴尔虎旗| 梧州市| 乐山市| 冷水江市| 平武县| 思茅市| 田阳县| 济源市| 松原市| 永宁县| 甘肃省| 东平县| 承德市| 雷波县| 高安市| 佛学| 龙口市| 宁蒗| 滁州市| 高平市| 桂平市| 泊头市| 唐河县| 大关县| 板桥市| 鸡东县| 桂平市| 旺苍县| 永德县|