SpringMvc如何通过零配置起一个DispatcherServlet的

1:拿经常使用Spring+springmvc举例子,

通常我们会配置下面的配置文件。

web.xml: 里面配置监听器,DispatcherServlet 等。 零配置之后就是靠tomcat启动通过SPI机制找ServletContainerInitializer实现类,调用onStartup(servlet上下文参数),在这个方法里通过servlet上下文.add(new DispatcherServlet())。对于监听器现在已经不需要了。

applicationContext.xml: 配置一些bean 和包扫描路径等。 零配置是通过@Bean +@Configuration  @ComponentScan注解等。

springmvc.xml:  配置springmvc相关的一些配置。 零配置通过WebMvcConfigurer接口实现。


但是在spring官网上,对于DispatcherServlet的零配置写法,却是用的另外一个接口。

 为什么使用WebApplicationInitializer接口也可以加入Servlet呢?上面不是说tomcat通过ServletContainerInitializer实现类加载的吗?这里spring-web做了一层转换。

在spring-web的jar包里面也有一个SPI机制。

 

 

 javax.servlet.ServletContainerInitializer 的实现类,通过SPI机制加载,这里的实现类是SpringServletContainerInitializer,里面也有一个onStartup方法。这个加载执行就是在tomcat中执行的。

public void onStartup(@Nullable 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 中WebApplicationInitializer接口的实例 initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass,
new Class[0]).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接口实例的onStartup方法 WebApplicationInitializer initializer
= (WebApplicationInitializer)var4.next(); initializer.onStartup(servletContext); } } }

 

 既然找到了接口实现类在哪里调用的了,那onStartup方法参数中的webAppInitializerClasses 在哪里被传过来的呢?

其实在 SpringServletContainerInitializer类上有一个注解:HandlesTypes。

@HandlesTypes({WebApplicationInitializer.class})注解其实是个字节码技术,类似于javasist,asm,就是分析jar包中的class文件。

它的作用就是通过字节码技术解析jar包和程序中的class文件,就可以得知这个class实现了那些接口,继承了那些类,比如对比接口是否是WebApplicationInitializer,就相当于找出了这个接口的所有实现类,tomcat在使用的时候就找出的所有实现类赋值给了

onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) 方法的第一个参数。


 

那这个HandleTypes机制在哪里解析查找的呢?在tomcat启动过程中有一个ContextConfig的类。它里面webConfig()方法中调用一个 processServletContainerInitializers 方法,

 根据注释也可以知道,它是扫描jar包中 ServletContainerInitializer 接口实现类

/**
     * Scan JARs for ServletContainerInitializer implementations.
     */
    protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
// 自定义一个类加载器进行加载,加载之后放到集合里面 WebappServiceLoader
<ServletContainerInitializer> loader = new WebappServiceLoader<ServletContainerInitializer>( context); detectedScis = loader.load(ServletContainerInitializer.class); } catch (IOException e) { log.error(sm.getString( "contextConfig.servletContainerInitializerFail", context.getName()), e); ok = false; return; } for (ServletContainerInitializer sci : detectedScis) {
// 这个集合中存的key是找到的sci,value暂时是空的集合,下一个步骤里面会填充 initializerClassMap.put(sci,
new HashSet<Class<?>>()); // 处理带有@HandlesTypes注解的类 HandlesTypes ht; try { ht = sci.getClass().getAnnotation(HandlesTypes.class); } catch (Exception e) { if (log.isDebugEnabled()) { log.info(sm.getString("contextConfig.sci.debug", sci.getClass().getName()), e); } else { log.info(sm.getString("contextConfig.sci.info", sci.getClass().getName())); } continue; }
//如果不带那个注解就跳过
if (ht == null) { continue; } Class<?>[] types = ht.value(); if (types == null) { continue; } // @HandlesTypes 里面可能有很多class ,这里只有一个WebApplicationInitializer for (Class<?> type : types) { if (type.isAnnotation()) { handlesTypesAnnotations = true; } else { handlesTypesNonAnnotations = true; } Set<ServletContainerInitializer> scis = typeInitializerMap.get(type); if (scis == null) { scis = new HashSet<ServletContainerInitializer>(); typeInitializerMap.put(type, scis); } scis.add(sci); } } }

 

 上面方法执行完,这个typeInitializerMap中存的是<WebApplicationInitializer.class,SpringServletContainerInitializer的实现类集合>。

  initializerClassMap 这个集合中存的是<SpringServletContainerInitializer实现类,空的集合>

在webConfig()方法中这个方法执行完,下一步就会填充initializerClassMap中的value值

  // Step 3. Look for ServletContainerInitializer implementations
        // 处理ServletContainerInitializers
        if (ok) {
            processServletContainerInitializers();
        }

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Steps 4 & 5.
            processClasses(webXml, orderedFragments);
        }

 

 在processClasses中会把initializerClassMap 中的value填充,之后它里面就是<SpringServletContainerInitializer实现类,WebApplicationInitializer的实现类集合>

 在webConfig()最后一个步骤,把initializerClassMap中的数据传到下一个容器中StandardContext中的 initializers属性中。

  // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }

 

 然后在StandardContext 中的startInternal()方法中遍历SpringServletContainerInitializer实现类调用onStartup方法传入WebApplicationInitializer的实现类集合。

 到这一步,就走到spring-web中的那个 SpringServletContainerInitializer实现类了。


 

posted @ 2021-08-14 16:10  蒙恬括  阅读(115)  评论(0编辑  收藏  举报