Spring Web:Java 替代 web.xml 原理

  • 在 Servlet 3.0 環境中,服務器的 Servlet 容器會在類路徑中查找實現 javax.servlet.ServletContainerInitializer 接口的類,如果能發現的話,就調用它的 onStartup(*)
  • 這個實現類一般不需要我們寫,因爲 Spring 已經提供了:SpringServletContainerInitializer
//javax.servlet.annotation.HandlesTypes 指定那些需要處理的類型;
//指定了 WebApplicationInitializer 接口,所有實現了該接口的類型都會被收集到下面 onStartup() 的第一個參數
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    //WebApplicationInitializer 實現類類型都被注入到第一個參數,包括我們自定義的實現類
    //Spring 會依次調用這些類的 onStartup(ServletContext servletContext) ,無論我們定義了多少個
    //ServletContext 是服務器的容器,可以向它註冊 Listener 和 Servlet
    @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);
        }
    }

}

由此,我們需要新建一個類實現 WebApplicationInitializer 接口,並重寫 onStartup(ServletContext servletContext) ,然後在裏邊分別創建 Spring 的 IOC 容器和 WebMVC 容器,並且向 ServletContext(服務器的容器) 註冊 Spring 的 ContextLoaderListener 和 DispatcherServlet


但是!Spring 有一系列的實現類和子類幫我們做了很多工作,來看 Spring 提供的 WebApplicationInitializer 實現類:AbstractContextLoaderInitializer

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {


    //......


    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerContextLoaderListener(servletContext);
    }

    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            //創建一個 ContextLoaderListener
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            //向 Servlet 容器註冊 ContextLoaderListener
            servletContext.addListener(listener);
        }
        else {
            logger.debug("No ContextLoaderListener registered, as " +
                    "createRootApplicationContext() did not return an application context");
        }
    }

    //抽象模板方法,留給子類重寫:創建 IOC 容器
    protected abstract WebApplicationContext createRootApplicationContext();

    //如果子類不重寫,無任何參數提供給 IOC 容器
    protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
        return null;
    }

}

可以看得出來,AbstractContextLoaderInitializer 的貢獻就是定義了一套標準的創建 ContextLoaderInitializer 的流程,並以模板方法模式將細節定義開放給子類,但是這還沒完!Spring 進一步提供了它的子類:AbstractDispatcherServletInitializer

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

    //......

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //父類創建 Listener 的流程要走
        super.onStartup(servletContext);
        //自己創建 Servlet 的流程也要走
        registerDispatcherServlet(servletContext);
    }


    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 + "]");

        //創建一個 DispatcherServlet
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        //向 Servlet 容器註冊 DispatcherServlet
        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);
    }


    //......

    //抽象模板方法,留給子類重寫:創建 WebMVC 容器
    protected abstract WebApplicationContext createServletApplicationContext();

    //......

    //如果子類不重寫,無任何參數提供給 WebMVC 容器
    protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
        return null;
    }

    //抽象模板方法,留給子類重寫:設置 DispatcherServlet URL映射
    protected abstract String[] getServletMappings();

    //子類可重寫:可以提供多個 Filter,Spring 會將他們一一註冊到服務器的 Servlet 容器中
    protected Filter[] getServletFilters() {
        return null;
    }

    //向 Servlet 容器註冊 Filter
    protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
        String filterName = Conventions.getVariableName(filter);
        Dynamic registration = servletContext.addFilter(filterName, filter);
        if (registration == null) {
            int counter = -1;
            while (counter == -1 || registration == null) {
                counter++;
                registration = servletContext.addFilter(filterName + "#" + counter, filter);
                Assert.isTrue(counter < 100,
                        "Failed to register filter '" + filter + "'." +
                        "Could the same Filter instance have been registered already?");
            }
        }
        registration.setAsyncSupported(isAsyncSupported());
        registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
        return registration;
    }

    //......

    //子類可重寫:自定義設置 DispatcherServlet 的註冊表信息,比如設置初始化參數
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    }

}

一樣可以看得,AbstractDispatcherServletInitializer 的貢獻是定義了一套標準的創建 DispatcherServlet 的流程,並以模板方法模式將細節定義開放給子類,然而,這還沒完!Spring 依舊提供了它的子類:AbstractAnnotationConfigDispatcherServletInitializer

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    //創建 AnnotationConfigWebApplicationContext 類型的 IOC 容器
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(configClasses);
            return rootAppContext;
        }
        else {
            return null;
        }
    }

    //創建 AnnotationConfigWebApplicationContext 類型的 WebMVC 容器
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            servletAppContext.register(configClasses);
        }
        return servletAppContext;
    }

    //抽象模板方法,留給子類重寫:提供 IOC 容器需要的 JavaConfig
    protected abstract Class<?>[] getRootConfigClasses();

    //抽象模板方法,留給子類重寫:提供 WebMVC 容器需要的 JavaConfig
    protected abstract Class<?>[] getServletConfigClasses();

}

看到這裏已經很清楚了,這個類將兩個 Spring 容器的創建再度簡化,簡單到我們只需要提供 JavaConfig!所以我們開發者要做的就是,新建一個類繼承 AbstractAnnotationConfigDispatcherServletInitializer,並重寫以上幾個類的抽象方法,以下是一個簡單例子

public class GoWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        // IOC 容器配置
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // WebMvc 容器配置
        return new Class<?>[] { WebMvcConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        // DispatcherServlet URL映射
        return new String[] { "/" };
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章