Web工程結構與Servlet

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 工程項目結構

無子 Module 結構圖
有子 Module 的工程結構

二、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.加載過程
web容器加載過程
  • 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 Servlet

  • RequestDispatcher接口
    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類,也就是有多個線程在使用這個實例。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 本文包括:1、Filter簡介2、Filter是如何實現(xiàn)攔截的?3、Filter開發(fā)入門4、Filter的生命周期...
    廖少少閱讀 7,537評論 3 56
  • 這部分主要是與Java Web和Web Service相關的面試題。 96、闡述Servlet和CGI的區(qū)別? 答...
    雜貨鋪老板閱讀 1,507評論 0 10
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,901評論 11 349
  • 監(jiān)聽器(listener) 監(jiān)聽器簡介 :監(jiān)聽器就是一個實現(xiàn)特定接口的普通java程序,這個程序?qū)iT用于監(jiān)聽另一個...
    奮斗的老王閱讀 2,694評論 0 53
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,715評論 19 139

友情鏈接更多精彩內(nèi)容