servlet基礎淺談

本篇將介紹如下幾點java

  1. 介紹Servlet爲什麼物?
  2. servlet擁有什麼樣的生命週期,做爲開發者能夠利用生命週期作點什麼?
  3. 在整個應用的啓動、運行、銷燬時,servlet技術對外暴露了什麼樣的事件鉤子,讓開發者能夠干預利用這些事件?
  4. servlet、filter、listener如何經過配置讓servlet容器來感知並利用?
  5. spring是如何基於這些知識點構建起一個MVC框架的?

什麼是servlet?

Servlet是基於Java技術的web組件,容器託管的,用於生成動態內容。像其餘基於Java的組件技術同樣, Servlet也是基於平臺無關的Java類格式,被編譯爲平臺無關的字節碼,能夠被基於Java技術的webserver 動態加載並運行。容器(平時咱們所使用的tomcat就是其中一種servlet容器),有時候也叫作servlet引擎,是webserver爲支持servlet功能擴展的部分。客戶端 經過Servlet容器實現的請求/應答模型與Servlet交互。(引用自oracle官方servlet3.1規範文檔)web

servlet如何處理一個客戶端請求?

servlet的生命週期?

servlet按照一個嚴格定義的生命週期被管理,該生命週期包括:如何被加載?實例化?初始化?處理客戶端請求?什麼時候結束服務? 該生命週期能夠經過Servlet接口中的API來表示:init、service、destroyspring

加載和實例化階段

servlet容器負責加載和實例化servlet,加載和實例化能夠發生在容器啓動時,或者延遲初始化直到容器有請求須要處理時。(經過開發者配置來肯定)編程

初始化階段

servlet容器必須在處理客戶端請求以前,對servlet實例進行初始化(即調用Servlet.init接口)。能夠完成一些讀取持久化配置數據、初始化資源等一次性的動做。tomcat

處理客戶端請求

完成初始化以後,servlet容器可使用該servlet來處理客戶端請求。(容器經過開發者的配置,即servlet-mapping來尋找適合當前請求的servlet)客戶端請求由ServletRequest類型來封裝表示、Servlet響應由ServletResponse類型來封裝表示。這兩個類型的對象都由容器進行實例化,在調用Servlet處理客戶端請求時傳遞給Servlet的service方法。在Http請求的場景下,容器提供的實現對應爲HttpServletRequest、HttpServletResponse。一個servlet實例應對多個客戶端請求的狀況,致使了咱們須要在處理請求時保證線程安全。安全

servlet技術中的其餘組件?

在servlet技術中,除了Servlet接口用於處理請求這個組件接口外,還存在Filter、Listener這兩個重要的組件接口。 其中Filter是一種代碼重用的技術,運行運行過程當中改變進入資源的請求和資源返回的響應中的有效負載和header信息。便可以在分發請求給servlet處理以前對請求進行攔截,以後再servlet完成處理,返回響應後對響應進行攔截。能夠用於日誌記錄、驗證等需求。 和servlet生命週期同樣,應用一樣存在生命週期。監聽應用生命週期事件可讓開發人員更好的控制ServletContext、HTTPSession和ServletRequest的生命週期,能夠更好的進行代碼分解。Servlet事件監聽器支持在ServletContext、HTTPSession和ServletRequest狀態改變時進行事件通知。oracle

Filter

實現本身的Filter能夠經過實現接口javax.servlet.Filter來完成,以後經過web.xml或者註解配置到Servlet容器中,讓容器在處理請求時應用此時配置的Filterapp

public class FirstFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println(getClass().getName() + " init()");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println(getClass().getName() + " doFilter()");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}
複製代碼

Listener

Listener的種類能夠分爲:框架

ServletContext相關

  • ServletContextListener,spring中利用該監聽器初始化web應用
  • ServletContextAttributeListener

HttpSession相關

  • HttpSessionListener
  • HttpSessionAttributeListener
  • HttpSessionIdListener
  • HttpSessionActivationListener
  • HttpSessionBindingListener,這個監聽器跟HttpSessionAttributeListener有點相似,可是HttpSessionAttributeListener是針對全部HttpSession#setAttribute.setAttribute(key, value)而言的,value能夠是任意值,而且經過@WebListener註冊到容器。而HttpSessionBindingListener是針對HttpSession#setAttribute.setAttribute(key, value),當value爲HttpSessionBindingListener的實現類實例纔會調用事件方法,無需經過@WebListener配置到容器

ServletRequest相關

  • ServletRequestListener
  • ServletRequestAttributeListener
  • AsyncListener,監聽異步事件,超時、鏈接終止、完成異步處理

如何映射請求到servlet?

URL路徑映射優先級

在收到客戶端請求時,web容器肯定轉發到哪一個web應用(獲取servlet上下文路徑),以後用於映射到servlet的路徑是請求對象的請求URL減去上下文和路徑參數部分,以後應用如下步驟來找出servlet來處理請求,短路原則,一旦找到匹配的servlet,以後的步驟直接跳過異步

  1. 精確匹配
  2. 容器遞歸地嘗試匹配最長路徑前綴,用"/"字符做爲路徑分隔符,最長匹配肯定選擇的servlet
  3. 若是URL最後一部分包含一個擴展名,servlet容器將試圖匹配爲擴展名處理請求的servlet。
  4. 若是前三個原則都沒法找出一個servlet來處理請求,則交給"default"servlet來處理,即servlet-mapping爲"/"的servlet。

配置Listener、Filter、Servlet

使用註解的方式來配置容器,下面看看若是使用:

  1. @WebListener來配置Listener
  2. @WebServlet配置Servlet
  3. @WebFilter配置Filter

經過註解@WebListener,結合ServletContext編程式API來註冊Servlet、Filter、Listener

經過@WebListener註解配置ServletContextListener實現類,在容器初始化servlet上下文時,調用ServletContext的API來註冊

@WebListener
public class FirstServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("firstServlet", FirstServlet.class);
        dynamic.addMapping("/first", "/first/*");
        dynamic.setLoadOnStartup(1);

        ServletRegistration.Dynamic dynamic2 = servletContext.addServlet("secondServlet", SecondServlet.class);
        dynamic2.addMapping("/second", "/second/*");
        dynamic2.setLoadOnStartup(1);

        // 異步servlet
        ServletRegistration.Dynamic firstAsyncServlet = servletContext.addServlet("firstAsyncServlet", FirstAsyncServlet.class);
        firstAsyncServlet.setLoadOnStartup(1);
        firstAsyncServlet.setAsyncSupported(true);
        firstAsyncServlet.addMapping("/async/first", "/async/first/*");

        // TODO Filter調用順序???
        FilterRegistration.Dynamic filterRegistration = servletContext.addFilter("secondFilter", SecondFilter.class);
        filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD),
                true, "/first");

        FilterRegistration.Dynamic filterDynamic = servletContext.addFilter("firstFilter", FirstFilter.class);
        filterDynamic.addMappingForUrlPatterns(
                EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), false, "/first");


        // 非阻塞IO servlet
        ServletRegistration.Dynamic firstNoBIOServlet = servletContext.addServlet("firstNoBIOServlet", FirstNoBlockIOServlet.class);
        firstNoBIOServlet.setLoadOnStartup(1);
        firstNoBIOServlet.setAsyncSupported(true);
        firstNoBIOServlet.addMapping("/nobio/first", "/nobio/first/*");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}
複製代碼

利用基於SPI機制的ServletContainerInitializer來初始化容器

  1. 實現接口ServletContainerInitializer,用@HandlesTypes註解在類級別上指定初始化類
  2. 在META-INF/services/javax.servlet.ServletContainerInitializer文件中指定實現類 完成以上兩步以後,容器在啓動時,將回調實現類的public void onStartup(Set<Class<?>> c, ServletContext ctx)方法,參數是容器爲咱們收集的在classpath下全部HandlesTypes註解指定的類。具有了這些條件,咱們就能夠在該方法中,調用這些類來初始化容器了(映射servlet、配置filter、配置Listener等等)

爲何可使用註解直接來配置Listener、Filter、Servlet,又提供了ServletContainerInitializer這種初始化容器的機制?

  1. 使用註解的方式,不可避免的須要掃描全部classpath下的全部類,爲了提升啓動速度採用ServletContainerInitializer機制
  2. 若是部署的應用中,存在web.xml(部署描述文件,在servlet2.5以前必須存在)。若是web.xml文件中指定了metadata-complete="true"時,將不會啓用註解掃描的配置方式。爲了兼容性,提倡使用ServletContainerInitializer機制來初始化容器。(這也是spring的作法)

springMVC是如何應用servlet技術的

在spring中利用基於SPI機制的ServletContainerInitializer來初始化容器,具體實現方式是:

  1. 類SpringServletContainerInitializer實現了接口ServletContainerInitializer,並指定了WebApplicationInitializer做爲初始化類(AbstractAnnotationConfigDispatcherServletInitializer做爲其便利的抽象類,開發者能夠繼承該類做爲初始化容器的配置類)。如:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // 指定ROOT ApplicationContext的配置類
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // 指定Web ApplicationContext的配置類
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        // 指定DispatcherServlet的servlet-mapping,此處指定爲default servlet。任何未找到映射的請求都會由DispatcherServlet來處理請求
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        // 配置Filter
        return super.getServletFilters();
    }
}
複製代碼
  1. spring中利用監聽器ServletContextListener初始化web應用的父ApplicationContext

總結

這篇文章中梳理了Servlet中的經常使用技術,主要涉及Servlet、Filter、Listener的知識點和配置細節。以後引伸出springMVC是如何利用這些知識點來構建一個web框架的。servlet做爲java web開發中的基石是每一個開發者都必須掌握的技能。關於springMVC中的更多原理細節將在後續文章整理髮出,期待你的關注

相關文章
相關標籤/搜索