1. WebApplicationInitializer

随着JavaConfig配置方式逐步替代配置,WebApplicationInitializer可以看做Web.xml的替代,此接口在Web容器启动的时候会记载这个接口的实现类,从而起到Web.xml的作用,从而我们可以通过其自定义配置Web组件.

首先我们进入这个接口,它仅有一个方法#onStartup方法,我们并不容易看出其用意何在?而此方法又是何时被调用的?

public interface WebApplicationInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

但是在这个类所属的包下有另一个类- - -SpringServletContainerInitializer

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if(webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if(!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)waiClass.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if(initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }
        }
    }
}
SpringServletContainerInitializer#onStartup方法中,首先判断参数WebAppInitializerClasses这个Set集合是否为空.若不为空并且是WebApplicationInitializer接口的实现,就将其转存到另一个集合中,之后对集合进行排序并遍历调用其#onStartup方法,从而我们可以知晓之前所说的WebApplicationInitializer是如何在Web流程中被调用的.

但是另外两个问题又了:
1.SpringServletContainerInitializer#onStartup方法又是如何被调用的呢?
2.其类上注解@HandlesType又有什么作用呢?

1.


官方声明中指出:为了支持无xml配置化,提供了ServletContainerInitializer,通过SPI机制,当启动Web容器的时候,会自动到相应的jar包下找到META-INF/servicesServletContainerInitializer的全路径名的文件,并根据其内容的全路径进行实例化.而我们可以发现文件中的名称就是SpringServletContainerInitializer,所以SpringServletContainerInitializer作为ServletContainerInitializer的实现,通过SPI机制在Web容器加载的时候会被自动调用.

image.png

2.


  • 而对于@HandlesType注解,其作用为:读取@HandlesType注解value的值,根据其值将扫描路径下所有该值所表示的接口实现,并将其生成一个Set集合提供给#onStartup方法使用.
  • ApplicationContext内部启动时会通知ServletContainerInitializer#onStartup方法,而这个方法的第一个参数Set集合就是注解Value值指定的Class集合,而在这里即为WebApplicationInitializer实现的集合
  • 在下列代码中所示,在Tomcat所有容器的抽象类中ContainerMBean#addChild方法中,Tomcat在为Host容器添加Context子容器时,会为其分配一个ContextConfig类,其中通过#processServletContainerInitializer方法来获取@HandlesType注解
public class ContainerMBean extends BaseModelMBean {
    public void addChild(String type, String name) throws MBeanException {
        //........
        try {
            if(contained instanceof StandardHost) {
                HostConfig config = new HostConfig();
                contained.addLifecycleListener(config);
            } else if(contained instanceof StandardContext) {
                ContextConfig config = new ContextConfig();
                contained.addLifecycleListener(config);
            }
        }
    }
}

-------------------------------------------------------------------------------------

public class ContextConfig implements LifecycleListener {
    protected void webConfig() {
            //.........
            if(this.ok) {
                this.processAnnotations(orderedFragments, webXml.isMetadataComplete(), javaClassCache);
            }
        }


    protected void processServletContainerInitializers() {
            //.........
             ServletContainerInitializer sci = (ServletContainerInitializer)var13.next();
            this.initializerClassMap.put(sci, new HashSet());

            HandlesTypes ht;
            try {
                ht = (HandlesTypes)sci.getClass().getAnnotation(HandlesTypes.class);
            } 
        }
}

2.AbStractAnnotationConfigDispatcherServletInitializer

  • 官方文档声明中指出:Spring MVC中我们其实是可以创建多个DispatcherServlet的,只需要创建多个继承自AbstractAnnotationConfigDispatcherServletInitializer的子类即可,而每个DispatcherServlet都有自己的应用上下文(servlet application context),这个应用上下文只针对当前的DispatcherServlet有用,这也就是#getServletConfigClasses的作用,用来获取这个DispatcherServlet的应用上下文的配置类
  • 而除了每个DispatcherServlet配置类的应用上下文,还有一个公共的跟应用上下文(root application context),这个应用上下文的作用时为了在多个DispatcherServlet之间共享,这也就是#getRootConfigClasses的作用,用于返回根应用上下文的配置类,Spring框架的机制会保证如果当前DispatcherServlet的应用上下文中没有找到想要的Bean,就会去根应用上下文中找.
public class SpittrWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
    //将DispatcherServlet映射到"/"
    @Override
    protected String[] getServletMappings(){
        return new String[]{"/"};
    }

    //配置root applicationContext应用上下文的bean
    @Override
    protected Class<?>[] getRootConfigClasses(){
        return new Class<?>[]{RootConfig.class};
    }

    //配置servlet applicationContext应用上下文的bean
    @Override
    protected Class<?>[] getServletConfigClasses(){
        return new Class<?>[]{Webconfig.class};
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------------------
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web")
public class WebConfig implements WebMvcConfigurer{//Spring5中此接口方法均为默认方法,替代之前适配器类
    //配置jsp视图解析器.否则会使用默认的BeanNameViewResolver
    @Bean
    public viewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF.views");
        resolver.setSuffix(".jsp");
        return resolver;
    }
    //配置静态资源处理
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------------------
@Configuration
@EnableWebMvc
@ComponentScan(basePackage = {"spitter"},excludeFilters = {
    @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
})
public class RootConfig{
}

3.总结

AbStractAnnotationConfigDispatcherServletInitializer就是Spring给我们通过javaConfig方式自定义DispatcherServlet组件的实现方法,如果需要了解其他相关内容:

  1. Spring容器的相关知识可以参考:Spring容器及其初始化
  2. SpringMvc自定义配置化相关知识可以参考:

NTFA
24 声望3 粉丝

自我约束,刻意练习