SpringMVC之五:自定義DispatcherServlet配置及配置額外的 servlets 和 filters


相關文章

Servlet3.0之四:動態注冊和Servlet容器初始化

SpringBoot中通過SpringBootServletInitializer如何實現組件加載

SpringMVC之五:自定義DispatcherServlet配置及配置額外的 servlets 和 filters

一、web容器如何初始化第三方組件(servlet、filters)

在web容器啟動時為提供給第三方組件機會做一些初始化的工作,例如注冊servlet或者filtes等,servlet規范中通過ServletContainerInitializer實現此功能。每個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄創建一個名為javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類,那么,當web容器啟動時就會運行這個初始化器做一些組件內的初始化工作。一般伴隨着ServletContainerInitializer一起使用的還有HandlesTypes注解,通過HandlesTypes可以將感興趣的一些類注入到ServletContainerInitializerde的onStartup()方法作為參數傳入。

ServletContainerInitializer的源碼:

package javax.servlet;
public interface ServletContainerInitializer {

    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;

}

容器啟動時會自動掃描當前服務中ServletContainerInitializer的實現類,並調用其onStartup方法,其參數Set<Class<?>> c,可通過在實現類上聲明注解進行注入。(例如:可通過在實現類上聲明注解javax.servlet.annotation.HandlesTypes(WebApplicationInitializer.class)注解自動注入,@HandlesTypes會自動掃描項目中所有的WebApplicationInitializer.class的實現類,並將其全部注入Set。)

Tomcat容器的ServletContainerInitializer機制的實現,主要交由Context容器和ContextConfig監聽器共同實現,ContextConfig監聽器負責在容器啟動時讀取每個web應用的WEB-INF/lib目錄下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目錄下的META-INF/services/javax.servlet.ServletContainerInitializer,通過反射完成這些ServletContainerInitializer的實例化,然后再設置到Context容器中,最后Context容器啟動時就會分別調用每個ServletContainerInitializer的onStartup方法,並將感興趣的類作為參數傳入。

  圖-1

基本的實現機制如圖,首先通過ContextConfig監聽器遍歷每個jar包或web根目錄的META-INF/services/javax.servlet.ServletContainerInitializer文件,根據讀到的類路徑實例化每個ServletContainerInitializer;然后再分別將實例化好的ServletContainerInitializer設置進Context容器中;最后Context容器啟動時分別調用所有ServletContainerInitializer對象的onStartup方法。

假如讀出來的內容為com.seaboat.mytomcat.CustomServletContainerInitializer,則通過反射實例化一個CustomServletContainerInitializer對象,這里涉及到一個@HandlesTypes注解的處理,被它標明的類需要作為參數值傳入到onStartup方法。如下例子:

@HandlesTypes({ HttpServlet.class,Filter.class }) 
public class CustomServletContainerInitializer implements 
    ServletContainerInitializer { 
  public void onStartup(Set<Class<?>> classes, ServletContext servletContext) 
      throws ServletException {
      for(Class c : classes) 
         System.out.println(c.getName());
  } 
}

其中@HandlesTypes標明的HttpServlet和Filter兩個class被注入到了onStartup方法。所以這個注解也是需要在ContextConfig監聽器中處理。前面已經介紹了注解的實現原理,由於有了編譯器的協助,我們可以方便地通過ServletContainerInitializer的class對象中獲取到HandlesTypes對象,進而再獲取到注解聲明的類數組,如

HandlesTypes ht =servletContainerInitializer.getClass().getAnnotation(HandlesTypes.class);
Class<?>[] types = ht.value();

即可獲取到HttpServlet和Filter的class對象數組,后面Context容器調用CustomServletContainerInitializer對象的onStartup方法時作為參數傳入。至此,即完成了servlet規范的ServletContainerInitializer初始化器機制。

二、Spring中初始化組件(servlet、filters)的過程

2.1、Spring中是如何初始化組件(servlet、filters)--SpringServletContainerInitializer

根據一所述在 Servlet 3.0 的環境中,容器會在 classpath 中尋找繼承了 javax.servlet.ServletContainerInitializer 接口的類,用它來配置 servlet 容器。 Spring 提供了一個繼承這個接口的類 SpringServletContainerInitializer,在這個類中,它會尋找任何繼承了 WebApplicationInitializer 接口的類並用其來配置 servlet 容器

先看看SpringServletContainerInitializer.java的源碼:

package org.springframework.web;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer { //Spring框架中繼承
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                // Be defensive: Some servlet containers provide us with invalid classes,
                // no matter what @HandlesTypes says...
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
            return;
        }

        servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

}

2.2、Spring中是如何初始化組件(servlet、filters)--WebApplicationInitializer

  Spring將Servlet配置相關的轉給了WebApplicationInitializer,Spring 3.2 提供了一個繼承了 WebApplicationInitializer 接口的基類 AbstractAnnotationConfigDispatcherServletInitializer。所以,你的 servlet 配置類只需要繼承 AbstractAnnotationConfigDispatcherServletInitializer,就會被發現而用於 servlet 容器的配置。

圖-2

***DispatcherServlet VS ContextLoaderListener

在 Spring MVC 中存在兩種應用上下文:DispatcherServlet 創建的和攔截器 ContextLoaderListener 創建的上下文:
1、DispatcherServlet:加載包含 web 組件的 bean,比如 controllers,view resolvers 和 hanlder mappings。
2、ContextLoaderListener:加載其他 bean,通常是一些中間層和數據層的組件(比如數據庫配置 bean 等)。

回到AbstractAnnotationConfigDispatcherServletInitializer ,在 AbstractAnnotationConfigDispatcherServletInitializer 中 DispatcherServlet 和 ContextLoaderListener 都會被創建,而基類中的方法就可用來創建不同的應用上下文:

  • getServletConfigClasses():定義 DispatcherServlet 應用上下文中的 beans
  • getRootConfigClasses():定義攔截器 ContextLoaderListener 應用上下文中的 beans

Note:為了使用 AbstractAnnotationConfigDispatcherServletInitializer 必須保證 web 服務器支持 Servlet 3.0 標准(如 tomcat 7 或更高版本) 。

圖-3

三、自定義 DispatcherServlet 配置

因為我們使用 AbstractAnnotationConfigDispatcherServletInitializer 來配置 DispatcherServlet,同時可以通過 customizeRegistration() 方法來對DispatcherServlet進行額外的配置

 

通過 ServletRegistration.Dynamic 參數配置 DispatcherServlet 的 load-on-startup 優先級 setLoadOnStartup(int loadOnStartup),設置初始化參數 setInitParameters() 等。具體查看文檔 ServletRegistration.Dynamic

在AbstractAnnotationConfigDispatcherServletInitializer將DispatcherServlet注冊到Servlet容 器中之后,就會調用customizeRegistration(),並將Servlet注冊后得到的 Registration.Dynamic傳遞進來。通過重載customizeRegistration()方法,我們可 以對DispatcherServlet進行額外的配置。

看AbstractDispatcherServletInitializer的部分源碼

    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return empty or null");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext,
                "createServletApplicationContext() did not return an application " +
                "context for servlet [" + servletName + "]");

        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        Assert.notNull(registration,
                "Failed to register servlet with name '" + servletName + "'." +
                "Check if there is another servlet registered under the same name.");

        registration.setLoadOnStartup(1);
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());

        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);     }

例如,在本章稍后的內容中(7.2節),我們將會看到如何在Spring MVC中處理multipart請求和文 件上傳。如果計划使用Servlet 3.0對multipart配置的支持,那么需要使 用DispatcherServlet的registration來啟用multipart請求。我們可以重 載customizeRegistration()方法來設置MultipartConfigElement,如下所示:

    @Override
    protected void customizeRegistration(Dynamic registration) {
        registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
        super.customizeRegistration(registration);
    }

借助customizeRegistration()方法中的ServletRegistration.Dynamic,我們能 夠完成多項任務,包括通過調用setLoadOnStartup()設置load-on-startup優先級,通過 setInitParameter()設置初始化參數,通過調用setMultipartConfig()配置Servlet 3.0對multipart的支持。在前面的樣例中,我們設置了對multipart的支持,將上傳文件的臨時存 儲目錄設置在“/tmp/spittr/uploads”中。

四、配置額外的 servlets 和 filters

按照AbstractAnnotationConfigDispatcherServletInitializer的定義,它會創 建DispatcherServlet和ContextLoaderListener。如上圖-3所示。但是,如果你想注冊其他的 Servlet、Filter或Listener的話,那該怎么辦呢?

基於Java的初始化器(initializer)的一個好處就在於我們可以定義任意數量的初始化器類。因 此,如果我們想往Web容器中注冊其他組件的話,只需創建一個新的初始化器就可以了。最簡 單的方式就是實現Spring的WebApplicationInitializer接口。 例如,如下的程序清單展現了如何創建WebApplicationInitializer實現並注冊一個 Servlet。

使用 java 配置 servlet 的一個好處(不同於 web.xml)就是:可以定義任意數量的初始化類。所以,如果需要定義額外的 servlets 或 filters,只需要創建額外的初始化類。在 Spring MVC 中可以通過繼承 WebApplicationInitializer 接口來實現。

接下來,我們定義一個新的 servlet:

package com.dxz.mvcdemo1.config.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;

public class MyServletInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        //定義額外的    servlet
        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
        myServlet.addMapping("/custom/**");
        
        //定義filter
        javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class); filter.addMappingForUrlPatterns(null, false, "/custom/*");
    }

}

當然,你也可以用來定義 filters 和 listeners:

如上面的紅底部分。

AbstractAnnotationConfigDispatcherServletInitializer 中還有一種快捷方式:

如果你需要為 DispatcherServlet 添加 filter 的話,就不用這么麻煩了,你只要重寫 AbstractAnnotationConfigDispatcherServletInitializer 類的 getServletFilters() 方法就行了:

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new MyFilter() };
    }

不需要 mapping,因為會自動 mapping 到 DispatcherServlet 上,通過返回多個 filter,可以添加多個 filter。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM