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);
public static WritableByteChannel newChannel(final OutputStream out) {因為輸入的方法參數為ServletOutputStream類型實例,因此只能返回一個新構建的WritableByteChannelImpl對象。具體構建:
if (out == null) {
throw new NullPointerException();
}
if (out instanceof FileOutputStream &&
FileOutputStream.class.equals(out.getClass())) {
return ((FileOutputStream)out).getChannel();
}
return new WritableByteChannelImpl(out);
}
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;
}
}
不過那是一個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中:
獲取用戶的timeline時,redis的LRANGE命令提供對list類型數據提供分頁操作:
很顯然,LRANGE取出了Status對象的ID,然后我們需要再次根據ID獲取對應的Status對象二進制數據,然后反序列化:
以上使用JDK內置的序列化支持;更多序列化,可參考hessian、google protobuf等序列化框架,后者提供業界更為成熟的跨平臺、更為高效的序列化方案。更多代碼請參見附件。
一些總結和思考:
另:
在線版,請參考 http://retwisrb.danlucraft.com/。那個誰誰,要運行范例,保證redis運行才行。
參考資料:
Writing a simple Twitter clone with PHP and Redis
https://github.com/xetorthio/jedis
若一個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、多線程情況下保證數據線程同步的幾個方法。
/**我們在為volatile修飾屬性賦值時,還是加把鎖的。
* 使用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 ++;
}
}
@WebServlet("/threadLocalServlet")若創建一個對象較為昂貴,但又是非線程安全的,在某種情況下要求僅僅需要在線程中獨立變化,不會影響到其它線程。選擇使用ThreadLocal較好一些,嗯,還有,其內部使用到了WeakHashMap,弱引用,當前線程結束,意味著創建的對象副本也會被垃圾回收。
public class ThreadLocalServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static ThreadLocalthreadLocal = 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();
}
}
@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();
}
}
}
@WebServlet("/atomicServlet")包裝類提供了很多的快捷方法,比如上面的incrementAndGet方法,自身增加1,然后返回結果值,并且還是線程安全的,省缺了我們很多手動、笨拙的編碼實現。
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();
}
}
非線程安全 | 工具版 | JUC版本 |
---|---|---|
HashMap | Collections.synchronizedMap | ConcurrentHashMap |
ArrayList | Collections.synchronizedList | CopyOnWriteArrayList |
HashSet | Collections.synchronizedSet | synchronizedSet |
Queue | ConcurrentLinkedQueue |
Servlet線程安全有很太多的話題要說,以上僅僅為蜻蜓點水,真正需要學習和實踐的還有一段長路要學習。另,話說,這里已和Servlet版本無關,是知識積累和分享。
<script>comet.showMsg(“這是新的消息”)</script>
<script src='js/yourCometProvider.js' type='text/javascript'></script>使用跨域的JS腳本,可能會有安全方面隱患,或許這個間接風險是可控的。
瀏覽器 | 流(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秒,則會出現連接中斷情況;會出現正在加載中的標志 |
BigPipe是一個重新設計的基礎動態網頁服務體系。大體思路是,分解網頁成叫做Pagelets的小塊,然后通過Web服務器和瀏覽器建立管道并管理他們在不同階段的運行。這是類似于大多數現代微處理器的流水線執行過程:多重指令管線通過不同的處理器執行單元,以達到性能的最佳。雖然BigPipe是對現有的服務網絡基礎過程的重新設計,但它卻不需要改變現有的網絡瀏覽器或服務器,它完全使用PHP和JavaScript來實現。
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下載和執行。
腳本阻滯:
我們知道直接在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年的時間才形成完備的解決方案.生成方案需要時間,而解決隨之而來的腳本阻滯,保障最快交互時間等等問題也會消耗大量時間.
$.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"/>雖然這一切看起來很好,很貼心,但也存在著不少問題:
<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"> </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>
if ( browser.msie ) {使用了IE瀏覽器onreadystatechange加載事件等特性。
script.event = STR_ONCLICK;
script.htmlFor = script.id;
script[ STR_ONREADYSTATECHANGE ] = function() {
/loaded|complete/.test( script.readyState ) && callback();
};
// All others: standard handlers
}
script[ STR_ONERROR ] = script[ STR_ONLOAD ] = callback;看來我當前瀏覽器中3.6版本的火狐支持腳本文件async屬性,非阻塞加載,再添加上onload,onerror事件支持,頁面不刷新顯示,也不難辦到了。
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;
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">基于jQuery 1.5,事件鏈,比以往更簡潔明了,尤其是在done方法中又一次調用自身,棒極了。
<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>
/**
* 簡單模擬每秒輸出一次當前系統時間,精細到毫秒
*
* @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();
}
}
<!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();
}
}
http://192.168.0.99:8080/servlet3/getNextTime2?callback=jQuery150832738454006945_1297761629067&_=1297761631777我們不用指定,jquery會自動生成一個隨機的函數名。
jQuery150832738454006945_1297761629067('2011-02-15 17:20:33 921');從下面截圖可以看到這一點。
<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"> </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);
}
}
/**
* 監聽器單獨線程推送到客戶端
*
* @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 BlockingQueueBLOG_QUEUE = new LinkedBlockingQueue ();
public static final QueueASYNC_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) {
Mapinfo = 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();
}
}
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做實際意義上的開發了。
/**
* 自定義會話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 {
}
}
/** 全局設置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 !");
}
}
YONGBOYID=601A6C82D535343163B175A4FD5376EA; JSESSIONID=AA78738AB1EAD1F9C649F705EC64D92D; AJSTAT_ok_times=6; JSESSIONID=abcpxyJmIpBVz6WHVo_1s; BAYEUX_BROWSER=439-1vyje1gmqt8y8giva7pqsu1
<Context useHttpOnly="true", sessionCookieName="YONGBOYID", sessionCookieDomain="/servlet3" … >
...
</Context>
<div style="margin: 40px; paddding: 10px">
<div><a href="sessionCookieTest">正常連接</a></div>
<div><a href="<%=response.encodeURL("sessionCookieTest") %>">重定向連接</a></div>
</div>
http://localhost/servlet3/sessionCookieTest;YONGBOYID=19B94935D50245270060E49C9E69F5B6
ServletRequest.getRequestDispatcher(String),
算是一個快捷方法。 /**
* 異步上下文的轉向分發
*
* @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();
}
}
}
}
http://localhost/servlet3/asyncDispatch2Async?disUrl=self
一個視圖:
屬性名 | 類型 | 是否可選 | 描述 |
---|---|---|---|
fileSizeThreshold | int | 是 | 當數據量大于該值時,內容將被寫入文件。 |
location | String | 是 | 存放生成的文件地址。 |
maxFileSize | long | 是 | 允許上傳的文件最大值。默認值為 -1,表示沒有限制。 |
maxRequestSize | long | 是 | 針對該 multipart/form-data 請求的最大數量,默認值為 -1,表示沒有限制。 |
/**
* 上傳文件測試 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=\"", "\"");
}
/**
* 多文件上傳支持
* @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");
Collectionparts = 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對象
ListfileNames = 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);
}
}
嚴重: Servlet.service() for servlet [com.learn.servlet3.async.DemoAsyncLinkServlet] in context with path [/servlet3] threw exceptionjava.lang.IllegalStateException: Not supported.
<div>2</div>
/**
* 模擬長連接實現,每秒輸出一些信息
*
* @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();
}
}
}
<!--marked filter-->
<div>2</div>
/**
* 異步攔截器
*
* @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 {
}
}
/**
* 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);
}
}
}
/**
* 一個典型的長連接實現
*
* @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();
}
}
<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>
當然后臺需要一個內容發布的頁面:/**
* 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);
}
}
response.setHeader("Access-Control-Allow-Origin", "*");
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>
<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>
/**
* 客戶端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"> </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);
}
/**
* 負責客戶端的推送
* @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);
}
}
/**
* 監聽器單獨線程推送到客戶端
* @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();
}
}
http://www.elliotswan.com/postable/暫時就是用到這么多了,若有新的,順手的,會不定期更新。
http://www.khurshid.com/i-make-postable/
/**
* 簡單示范
* @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);
}
}
<%@ 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>
/**
* 演示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!
/** |
public void contextInitialized(ServletContextEvent sce) { |
com.learn.servlet3.jardync.CustomServletContainerInitializer
@HandlesTypes({ JarWelcomeServlet.class }) |
CustomServletContainerInitializer
可以處理的類,在onStartup
方法中,可以通過Set<Class<?>> c
獲取得到。@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相應位置。
說這個很輕、很微小的框架,一點都不過分,只有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 ... } }
那么怎么在項目中使用呢 ?
在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源代碼。
/**
* 這次,我們把模板文件放在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;
}
}
}
}
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);
}
}
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>
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);
}
}
<!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>
@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功能,支持組件、功能的插拔,使之可以模塊化構造一個站點服務,大的跨越,一個變革,必將受益開發者社區。
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);
}
}
@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);
}
}
屬性名 | 類型 | 描述 |
---|---|---|
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> 標簽。 |
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 {
}
}
屬性名 | 類型 | 描述 |
---|---|---|
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> 標簽。 |
這個沒得說,Tomcat 7 運行要求,最簡單安裝JRE 1.6即可。2. Eclipse 3.6
下載地址:
http://java.sun.com/javase/downloads/index.jsp
嗯,支持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
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
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資源文件。
很簡陋,但湊合著能看。
項目源代碼已經打包如下: