Qunar大講堂
1.web項目結構
1.1 web項目結構
2.servlet
2.1 servlet
2.2 listener
2.3 filter
2.4 加載過程
2.5 路徑映射,轉發(fā)與重定向
2.6 cookie
一、Web 工程項目結構


二、servlet
1.Servlet / GenericServlet / HttpServlet

2.Listener
Listener 的設計對開發(fā) Servlet 應用程序提供了一種快捷的手段,能夠方便的從另一個縱向維度控制程序和數(shù)據(jù)。
目前 Servlet 中提供了 5 種兩類事件的觀察者接口,它們分別是:
- 4 個 EventListeners 類型的:由某個事件觸發(fā);當這些Listener的屬性被修改時,這些事件將被觸發(fā)
ServletContextAttributeListener
監(jiān)聽對ServletContext屬性的操作,比如增加、刪除、修改屬性
ServletRequestAttributeListener
ServletRequestListener
HttpSessionAttributeListener
- 2 個 LifecycleListeners 類型的:由生命周期的不同狀態(tài)觸發(fā)
ServletContextListener 創(chuàng)建StandardContext時contextInitialized方法被調(diào)用
當創(chuàng)建ServletContext時,激發(fā)contextInitialized(ServletContextEvent sce)方法;當銷毀ServletContext時,激發(fā)contextDestroyed(ServletContextEvent sce)方法。
HttpSessionListener 創(chuàng)建Session是sessionCreated方法被調(diào)用
監(jiān)聽HttpSession的操作。當創(chuàng)建一個Session時,激發(fā)session Created(HttpSessionEvent se)方法;當銷毀一個Session時,激發(fā)sessionDestroyed (HttpSessionEvent se)方法。

Listener是Servlet的監(jiān)聽器,它可以監(jiān)聽客戶端的請求、服務端的操作等。通過監(jiān)聽器,可以自動激發(fā)一些操作,比如監(jiān)聽在線的用戶的數(shù)量。
Listener Demo
下面我們開發(fā)一個具體的例子,這個監(jiān)聽器能夠統(tǒng)計在線的人數(shù)(一次“在線”定義為Session創(chuàng)建到被銷毀的時段)。在ServletContext初始化和銷毀時,在服務器控制臺打印對應的信息。當ServletContext里的屬性增加、改變、刪除時,在服務器控制臺打印對應的信息。
要獲得以上的功能,監(jiān)聽器必須實現(xiàn)以下3個接口:
- HttpSessionListener
ServletContextListener
ServletContextAttributeListener
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @author wanlong.ma
*
*/
public class OnlineUserListener implements HttpSessionListener,
ServletContextListener, ServletContextAttributeListener {
// 當前在線人數(shù)計數(shù)器
private long onlineUserCount = 0;
public long getOnlineUserCount() {
return onlineUserCount;
}
@Override
public void attributeAdded(ServletContextAttributeEvent arg0) {
}
@Override
public void attributeRemoved(ServletContextAttributeEvent arg0) {
}
@Override
public void attributeReplaced(ServletContextAttributeEvent attributeEvent) {
System.err.println("...attributeReplaced...");
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
// 當Session被創(chuàng)建時調(diào)用,在線人數(shù)+1,并調(diào)用本地toUpdateCount方法設置一個onlineUserCount屬性,以便被獲取
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
onlineUserCount ++;
toUpdateCount(httpSessionEvent);
}
// 當Session被銷毀時調(diào)用,在線人數(shù)-1;機制同上
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
onlineUserCount --;
toUpdateCount(httpSessionEvent);
}
private void toUpdateCount(HttpSessionEvent httpSessionEvent){
httpSessionEvent.getSession().setAttribute("onlineUserCount", onlineUserCount);
}
}
配置web.xml:
<listener>
<listener-class>com.ee.listener.OnlineUserListener</listener-class>
</listener>
jsp中通過request.getSession().getAttribute獲取我們設置的attribute.
3.Filter
-
概述
Filter是一種可以改變進入的請求(Request)和返回的響應(Response)的Header和內(nèi)容的Java組件。
Java中的Filter 并不是一個標準的Servlet ,它不能處理用戶請求,也不能對客戶端生成響應。主要用于對HttpServletRequest 進行預處理,也可以對HttpServletResponse 進行后處理,是個典型的處理鏈。

由上圖可以看出:在Request進入Servlet之前Filter對其進行預處理,在Response離開Servlet之后也會被Filter進行處理,要注意這個順序前后是相反的:
web.xml中元素執(zhí)行的順序listener -> filter -> struts攔截器 -> servlet
優(yōu)點
過濾鏈的好處是,執(zhí)行過程中任何時候都可以打斷,只要不執(zhí)行chain.doFilter()就不會再執(zhí)行后面的過濾器和請求的內(nèi)容。而在實際使用時,就要特別注意過濾鏈的執(zhí)行順序問題過濾器的作用描述
? 在HttpServletRequest 到達Servlet 之前,攔截客戶的HttpServletRequest 。
? 根據(jù)需要檢查HttpServletRequest ,也可以修改HttpServletRequest 頭和數(shù)據(jù)。
? 在HttpServletResponse 到達客戶端之前,攔截HttpServletResponse 。
? 根據(jù)需要檢查HttpServletResponse ,可以修改HttpServletResponse 頭和數(shù)據(jù)。Filter接口
1.如何驅(qū)動
在 web 應用程序啟動時,web 服務器將根據(jù) web.xml 文件中的配置信息來創(chuàng)建每個注冊的 Filter 實例對象,并將其保存在服務器的內(nèi)存中
2.方法介紹
init() init 方法在 Filter 生命周期中僅執(zhí)行一次,web 容器在調(diào)用 init 方法時
destory() 在Web容器卸載 Filter 對象之前被調(diào)用。該方法在Filter的生命周期中僅執(zhí)行一次。在這個方法中,可以釋放過濾器使用的資源。
doFilter() Filter 鏈的執(zhí)行
- Filter示例:實現(xiàn)編碼過濾器和請求url日志記錄過濾器
web.xml配置
<!-- 編碼過濾器 -->
<filter>
<filter-name>setCharacterEncoding</filter-name>
<filter-class>com.company.strutstudy.web.servletstudy.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>setCharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 請求url日志記錄過濾器 -->
<filter>
<filter-name>logfilter</filter-name>
<filter-class>com.company.strutstudy.web.servletstudy.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>logfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
編碼攔截器實現(xiàn)
public class EncodingFilter implements Filter {
private String encoding;
private Map<String, String> params = new HashMap<String, String>();
// 項目結束時就已經(jīng)進行銷毀
public void destroy() {
System.out.println("end do the encoding filter!");
params=null;
encoding=null;
}
// Filter處理方法:對request和response進行處理
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
//UtilTimerStack.push("EncodingFilter_doFilter:");
System.out.println("before encoding " + encoding + " filter!");
req.setCharacterEncoding(encoding);
// resp.setCharacterEncoding(encoding);
// resp.setContentType("text/html;charset="+encoding);
chain.doFilter(req, resp);
System.out.println("after encoding " + encoding + " filter!");
System.err.println("----------------------------------------");
//UtilTimerStack.pop("EncodingFilter_doFilter:");
}
// 項目啟動時就已經(jīng)進行讀取
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the encoding filter!");
encoding = config.getInitParameter("encoding");
for (Enumeration e = config.getInitParameterNames(); e
.hasMoreElements();) {
String name = (String) e.nextElement();
String value = config.getInitParameter(name);
params.put(name, value);
}
}
}
日志攔截器實現(xiàn)
public class LogFilter implements Filter {
FilterConfig config;
public void destroy() {
this.config = null;
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
// 獲取ServletContext 對象,用于記錄日志
ServletContext context = this.config.getServletContext();
System.out.println("before the log filter!");
// 將請求轉換成HttpServletRequest 請求
HttpServletRequest hreq = (HttpServletRequest) req;
// 記錄日志
System.out.println("Log Filter已經(jīng)截獲到用戶的請求的地址:"+hreq.getServletPath() );
try {
// Filter 只是鏈式處理,這里將請求轉發(fā)給下一個Filter,請求依然轉發(fā)到目的地址
chain.doFilter(req, res);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("after the log filter!");
}
public void init(FilterConfig config) throws ServletException {
System.out.println("begin do the log filter!");
this.config = config;
}
}
4.加載過程

- context-param 初始化鍵值對
- listener 有多個listener時,以在web.xml中的注冊順序加載
- filter 有多個filter時,以web.xml中<url-pattern>的順序加載
- servlet 同一時間web任期中只有一個servlet實體,servlet并發(fā)需要自己做
servlet并發(fā)處理
當請求來臨時,servlet容器會初始化對應的servlet。如果多個請求同時訪問的是同一個servlet,Servlet容器會創(chuàng)建多個線程同時調(diào)用servlet的service()方法來處理這些請求,而不是多個servlet實例。
如果給service方法設置了synchronized關鍵字,servlet容器則是序列化請求依次通過service方法。
但如果servlet實現(xiàn)了SingleThreadModel接口(此時,這個servlet只能一次處理一個請求),那么servlet容器會根據(jù)請求的數(shù)量創(chuàng)建多個servlet的實例(每個servlet實例相當于一個線程),并調(diào)用servlet的service方法來處理請求。
5.路徑映射、轉發(fā)與重定向
url-pattern類型
全路徑映射:如/aaa/bbb
路徑映射:以/開頭、/*結尾
擴展映射:以×.開頭
默認映射:/匹配順序
精確路徑匹配 -> 最長路徑匹配 -> 擴展匹配 -> Default ServletRequestDispatcher接口
RequestDispatcher接口,定義一個對象,從客戶端接收請求,然后將它發(fā)給服務器的可用資源(例如Servlet、CGI、HTML文件、JSP文件)。Servlet引擎創(chuàng)建request dispatcher對象,用于封裝由一個特定的URL定義的服務器資源。這個接口是專用于封裝Servlet的,但是一個Servlet引擎可以創(chuàng)建request dispatcher對象用于封裝任何類型的資源。request dispatcher對象是由Servlet引擎建立的,而不是由Servlet開發(fā)者建立的。
RequestDispatcher接口中定義了兩個方法:forward方法(頁面跳轉)和include方法(頁面包含)。
獲取RequestDispatcher對象的方法:
ServletContext.getRequestDispatcher (參數(shù)只能寫絕對路徑,以“/”開頭)
ServletRequest.getRequestDispatcher (參數(shù)可以是絕對路徑,也可以是相對路徑)
如:
request.getRequestDispacther("/test.jsp")
-
轉發(fā)(forward)與重定向(sendredirect)
都是跳轉到另一個路徑,不過二者有很大的區(qū)別。
簡單地說,請求轉發(fā)是當前servlet處理完Request之后,將服務資源(就是由上面的RequestDispatcher接口來負責的)轉發(fā)給下一個servlet接著處理,是在同一個請求里面完成的,二者共享的是同一個request,整個過程都是在服務端完成的。
重定向并不轉發(fā)這次請求的服務資源,而是直接向客戶端返回響應,響應告訴客戶端你必須要再發(fā)送一個請求,去訪問重定向的頁面,緊接著客戶端收到這個請求后,立刻發(fā)出一個新的請求,去請求重定向的頁面,這里兩個請求互不干擾,相互獨立,在前面request里面setAttribute()的任何東西,在后面的request里面都獲得不了??梢?,在sendRedirect()里面是兩個請求,兩個響應。
6.cookie
使用cookie來保存登錄信息。
會話:用戶開一個瀏覽器,訪問一個網(wǎng)站,只要不關閉該瀏覽器,不管該用戶點擊多少個超鏈接,訪問多少資源,直到用戶關閉瀏覽器,整個這個過程我們稱為一次會話。
為什么要使用cookie?
1.記錄用戶的事件
2.瀏覽歷史記錄
3.用戶名和密碼的記錄cookie特性
1.cookie是在服務端創(chuàng)建的;
2.cookie是保存在瀏覽器這端的;
3.cookie的生命周期可以通過cookie.setMaxAge(Int value)設置
value為負時,表示不保存cookie;
value為正時,表示最大生存秒數(shù);
value為0時,表示在客戶端刪除這個cookie.
如果不設置,默認為-1,關閉瀏覽器就destroy了;
4.cookie可以被多個瀏覽器共享;
5.一個WEB應用可以保存多個cookie
6.cookie存放的時候是以明文方式存放的,因此安全比較低,我們可以通過加密后保存,可以用md5(不可逆)算法加密(想起了base64),經(jīng)過md5加密后存放到數(shù)據(jù)庫,用戶輸入密碼后加密然后再和數(shù)據(jù)庫里面的匹配。
7.Servlet本身支不支持并發(fā)處理響應?
同一時間,在同一個應用的Web容器中,一個Servlet只存在一個實例,但是Servlet可以接受并發(fā)的請求。這是因為Servlet是無狀態(tài)的(Servlet中沒有保存狀態(tài)的可變變量),因此一般情況下Servlet中不會發(fā)生資源爭用,每次請求都會調(diào)用Servlet的service方法,在一個新的線程中去處理請求,Servlet容器會自動使用線程池等技術來支持系統(tǒng)的運行。
但是如果繼承的Servlet定義了可爭用的資源(可變變量等),需要自己做并發(fā)控制。
當客戶端第一次請求某個Servlet時,Servlet 容器將會根據(jù)web.xml配置文件實例化這個Servlet類。當有新的客戶端請求該Servlet時,一般不會再實例化該Servlet類,也就是有多個線程在使用這個實例。