SpringMvc(二)HandlesTypes源码

2023-10-24 18:25:45 浏览数 (2)

前言

本篇只是探究@HandlesTypes这个注解的作用

@HandlesTypes,初始化servlet容器参数

位置:org.apache.catalina.startup.ContextConfig#webConfig

首先我们要知道注解@HandlesTypes的作用是为启动程序确定入参类型,记住这一点,后面的操作都是围绕这个展开。

webConfig这个方法的主要是解析web.xml,为servlet容器做准备,主要有以下步骤:

  1. **查找web.ml:**包含tomcat-web.xml, Tomcat/conf/web.xml, 应用的web.xml,还有jar包里的web.xml
  2. **找到应用中所有的ServletContainerInitializer实现类:**将应用目录下META-INF/services/javax.servlet.ServletContainerInitializer加载,并将加载的实现类作为key放入initializerClassMap,这是还没有添加value; initializerClassMap 可以把它看做是应用启动程序参数映射集合 **HandlesTypes标注的实现类生成映射:**如果该实现类有@HandlesTypes注解,则将注解里的value作为key,该实现类(ServletContainerInitializer实现类)添加到value,存入typeInitializerMap
  3. **匹配启动程序需要的参数(查找HandlesTypes.value的实现类):**扫描并解析应用下的class文件,通过当前class匹配typeInitializerMap里的key(实际是通过superClassName匹配的),拿到需要传入参数的Set<ServletContainerInitializer>,再遍历Set<ServletContainerInitializer>,与initializerClassMap的key匹配,匹配上就把当前class(也就是HandlesTypes.value的实现类)添加到initializerClassMap的value中; 到这里,typeInitializerMap的工作也就完成了,所以,它负责的工作就是:缓存标注了@HandlesTypesServletContainerInitializer实现类,可以直接通过key(class)取到需要传入该class的ServletContainerInitializer实现类,然后和initializerClassMap匹配, 将匹配上的class添加到initializerClassMap.value
  4. 将读取的web.xml配置合并
  5. 将webxml配置设置到StandardContext
  6. 读取jar包下META-INF/resources/里的静态资源并设置到StandardContext
  7. 将找的Set<ServletContainerInitializer>设置到StandardContext;之后执行应用程序初始化就是遍历这个集合

下面主要围绕这两个容器展开:

  1. initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet<Class<? exted WebApplicationInitializer>>
  2. typeInitializerMap -> key = HandlesTypes注解里设置的class, value = ServletContainerInitializer实现集合,
代码语言:javascript复制
    protected void webConfig() {
        // 创建web.xml解析器
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

        // 添加默认的web.xml(作为缺省配置,tomcat/conf/web.xml,也就是defaultServlet和JspServlet)
        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment(webXmlParser));

        // 读取`/WEB-INF/tomcat-web.xml`
        Set<WebXml> tomcatWebXml = new HashSet<>();
        tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));

        // 创建之后我们需要webxml对象
        WebXml webXml = createWebXml();

        // 解析应用的web.xml(也就是我们war包下的web.xml)
        InputSource contextWebXml = getContextWebXmlSource();
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }
		// 获取servlet上下文对象
        ServletContext sContext = context.getServletContext();

		// 步骤1:这里是读取应用打包后的那些jar里的web.xml
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // 步骤2. 对这些配置排序
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // 步骤3. 查找实现了`ServletContainerInitializer`的实现类
        if (ok) {
            processServletContainerInitializers();
        }

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // 步骤 4 & 5:查找`/WEB-INF/classes`下的class文件,看有没有handlerTypes标注的类
            processClasses(webXml, orderedFragments);
        }

        if (!webXml.isMetadataComplete()) {
            // 步骤6.合并 web-fragment.xml
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // 步骤7a
            // 合并 tomcat-web.xml
            webXml.merge(tomcatWebXml);

            // 步骤7b. 合并全局默认的web.xml(defaultServlet jspServlet)
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. 将web.xml 配置添加到context容器(StandardContext)
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(tomcatWebXml);
            webXml.merge(defaults);
            convertJsps(webXml);
            configureContext(webXml);
        }

        if (context.getLogEffectiveWebXml()) {
            log.info(sm.getString("contextConfig.effectiveWebXml", webXml.toXml()));
        }

        // Always need to look for static resources
        // 步骤 10. 查找jar包里的资源
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<>(orderedFragments);
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }

        // 步骤 11. 这里将servlet容器需要的参数都添加到standardContext(Tomcat上下文)
        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());
                }
            }
        }
    }

步骤3内容

该步骤是添加了两个缓存

  • initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet<Class<? exted WebApplicationInitializer>>,不过这里只是空集合
  • typeInitializerMap -> key = HandlesTypes.value的class, value = ServletContainerInitializer实现类集合(需要传入当前key类型的实现类),

对应的功能:

  • initializerClassMap :存储应用初始化程序类及需要的参数对象类型,待后续启动直接获取
  • typeInitializerMap :缓存标注了HandlesTypesServletContainerInitializer实现类,可以直接通过key(class)取到需要传入该class的ServletContainerInitializer实现类,然后和initializerClassMap匹配, 将匹配上的class添加到initializerClassMap.value

具体步骤:

  1. 加载META-INF/services/javax.servlet.ServletContainerInitializer下的文件类
  2. 遍历ServletContainerInitializer实现类,实现类作为key put到initializerClassMap, 待后面步骤使用
  3. 判断是否有HandlesTypes注解,没有注解直接过,有注解,就遍历注解的value,然后以value里的class为key,ServletContainerInitializer实现类为value,存入typeInitializerMap
代码语言:javascript复制
    protected void processServletContainerInitializers() {

        List<ServletContainerInitializer> detectedScis;
        try {
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
            // 加载`META-INF/services/javax.servlet.ServletContainerInitializer`下的文件类
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.servletContainerInitializerFail",
                    context.getName()),
                e);
            ok = false;
            return;
        }
		
        for (ServletContainerInitializer sci : detectedScis) {
            // 将实现类放到`initializerClassMap`
            // 这里的key = ServletContainerInitializer实现类
            initializerClassMap.put(sci, new HashSet<>());

            // 查找实现类上是否有`@HandlesTypes`,这个就是在springMvc里又看到`org.springframework.web.SpringServletContainerInitializer`
            // 查找这个注解的原因是,它要通过注解将对应的参数注入
            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;
            }
            // 获取注解里的value(class)
            Class<?>[] types = ht.value();
            if (types == null) {
                continue;
            }
			// 我们springMvc的注解上标注的是`@HandlesTypes(WebApplicationInitializer.class)`
            // 那么这里就是WebApplicationInitializer
            for (Class<?> type : types) {
                if (type.isAnnotation()) {
                    handlesTypesAnnotations = true;
                } else {
                    handlesTypesNonAnnotations = true;
                }
                // 这里就是存放一个映射,因为,后面我们需要对有HandlersTypes标注的类进行参数传入
				// key = HandlesTypes.value的class, value = ServletContainerInitializer实现集合
                Set<ServletContainerInitializer> scis =
                        typeInitializerMap.get(type);
                if (scis == null) {
                    scis = new HashSet<>();
                    typeInitializerMap.put(type, scis);
                }
                scis.add(sci);
            }
        }
    }

步骤4内容

改步骤是通过当前class,获取superClassName,从typeInitializerMap获取需要传入参数的ServletContainerInitializer实现类,并把当前class作为参数类型存到initializerClassMap中,待后面启动时,直接获取。

具体步骤如下:

  1. 查找应用程序包下的文件,/WEB-INF/classes路径下,通过bcel技术,解析class文件,当前className为key,JavaClassCacheEntry为value存入javaClassCache,
  2. 通过当前的class从javaClassCache中获取父类,再通过父类从typeInitializerMap获取sci
  3. 找到sci后,设置到entry里,表明当前的class已经从父类(HandlesTypes.value里的类)继承了sci,typeInitializerMap在步骤3时,已经将填充了key=handlesTypes.value的class,value是Set<ServletContainerInitializer>
  4. 然后再遍历sci集合,将sci作为key,当前class作为value存入initializerClassMap,当前的class是实现类,因为sci是通过superClassName获取的
代码语言:javascript复制
protected void processClasses(WebXml webXml, Set<WebXml> orderedFragments) {
        // 步骤4. Process /WEB-INF/classes for annotations and
        // @HandlesTypes matches

        Map<String, JavaClassCacheEntry> javaClassCache;

        if (context.getParallelAnnotationScanning()) {
            javaClassCache = new ConcurrentHashMap<>();
        } else {
            javaClassCache = new HashMap<>();
        }

        if (ok) {
            WebResource[] webResources =
                    context.getResources().listResources("/WEB-INF/classes");

            for (WebResource webResource : webResources) {
                // Skip the META-INF directory from any JARs that have been
                // expanded in to WEB-INF/classes (sometimes IDEs do this).
                if ("META-INF".equals(webResource.getName())) {
                    continue;
                }
                processAnnotationsWebResource(webResource, webXml,
                        webXml.isMetadataComplete(), javaClassCache);
            }
        }

        // 步骤 5. 处理web.xml里配置的类,是否有被handlesTypes标注的
        if (ok) {
            processAnnotations(
                    orderedFragments, webXml.isMetadataComplete(), javaClassCache);
        }

        // Cache, if used, is no longer required so clear it
        javaClassCache.clear();
    }

重点看下processAnnotationsWebResource,它递归的查找应用程序目录下的class文件,/WEB-INF/classes/xxxx.class这个路径是我们打成包后的路径;

代码语言:javascript复制
 protected void processAnnotationsWebResource(WebResource webResource,
            WebXml fragment, boolean handlesTypesOnly,
            Map<String,JavaClassCacheEntry> javaClassCache) {

     // 是一个文件夹,就获取文件夹下的资源,然后递归
     // 这里明显的就是查找class文件,直接看else if 就可以了
        if (webResource.isDirectory()) {
            WebResource[] webResources =
                    webResource.getWebResourceRoot().listResources(
                            webResource.getWebappPath());
            if (webResources.length > 0) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString(
                            "contextConfig.processAnnotationsWebDir.debug",
                            webResource.getURL()));
                }
                for (WebResource r : webResources) {
                    // 递归
                    processAnnotationsWebResource(r, fragment, handlesTypesOnly, javaClassCache);
                }
            }
        } else if (webResource.isFile() &&
                webResource.getName().endsWith(".class")) {
            try (InputStream is = webResource.getInputStream()) {
                processAnnotationsStream(is, fragment, handlesTypesOnly, javaClassCache);
            } catch (IOException | ClassFormatException e) {
                log.error(sm.getString("contextConfig.inputStreamWebResource",
                        webResource.getWebappPath()),e);
            }
        }
    }

processAnnotationsStream这个方法是解析class文件的,spring解析class文件是asm技术,这里是bcel,简单看一下:

代码语言:javascript复制
protected void processAnnotationsStream(InputStream is, WebXml fragment,
        boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
        throws ClassFormatException, IOException {

    // 解析class
    ClassParser parser = new ClassParser(is);
    JavaClass clazz = parser.parse();
    // 检查`HandlesTypes`
    checkHandlesTypes(clazz, javaClassCache);

    if (handlesTypesOnly) {
        return;
    }

    processClass(fragment, clazz);
}

这里的功能是将class对应的sci找出来:

  1. 当前class获取父类还有接口的class,然后都存入javaClassCache
  2. 通过当前class从javaClassCache,拿到父类class,再从typeInitializerMap获取到sci,然后设置到当前的entry
  3. 再遍历sci,匹配当前class,匹配上就设置到initializerClassMap,而这时匹配的key是实现类并不是接口,因为它是通过superClassName查找的sci,
代码语言:javascript复制
protected void checkHandlesTypes(JavaClass javaClass,
        Map<String,JavaClassCacheEntry> javaClassCache) {

    // 这个是步骤3里进行添加的,
    // typeInitializerMap -> key =  HandlesTypes.value里的class, value = ServletContainerInitializer实现集合
    if (typeInitializerMap.size() == 0) {
        return;
    }
   // 这里是这个类的可访问判断
    if ((javaClass.getAccessFlags() &
            org.apache.tomcat.util.bcel.Const.ACC_ANNOTATION) != 0) {
        // Skip annotations.
        return;
    }

    String className = javaClass.getClassName();

    Class<?> clazz = null;
    // 这个属性是步骤3进行设置的,在解析class后,判断不是注解,handlesTypesNonAnnotations=true
    if (handlesTypesNonAnnotations) {
        // 这里比较简单,没贴代码,具体他会将该类的父类及父类的父类递归的找出来,然后放到javaClassCache里
        // javaClassCache: key = className, value = 父类, 接口
        populateJavaClassCache(className, javaClass, javaClassCache);
        JavaClassCacheEntry entry = javaClassCache.get(className);
        if (entry.getSciSet() == null) {
            // sciSet 指的是 Set<ServletContainerInitializer>
            try {
                // 在上面的`populateJavaClassCache`只是将`/WEB-INF/classes`下的所有class加载了,并没有对sci进行设置
                // 所以这里的步骤就是将`populateJavaClassCache`找出的class,设置对应的sci,如果是HandlesTypes.value里的类,那么就会有sci
                // 同时,它将class作为key,从javaClassCache获取到父类,然后再通过父类,从typeInitializerMap获取到Set<ServletContainerInitializer>设置到entry里
                // 要注意的是,这里判断null,是因为有可能在加载别的类的时候,加载过,这里的步骤属于懒加载,
                // 当第一次加载,会扫描,还有=null时扫描
                populateSCIsForCacheEntry(entry, javaClassCache);
            } catch (StackOverflowError soe) {
                throw new IllegalStateException(sm.getString(
                        "contextConfig.annotationsStackOverflow",
                        context.getName(),
                        classHierarchyToString(className, entry, javaClassCache)));
            }
        }
        if (!entry.getSciSet().isEmpty()) {
            clazz = Introspection.loadClass(context, className);
            if (clazz == null) {
                // Can't load the class so no point continuing
                return;
            }
			// initializerClassMap 在步骤3中放入的,value是空的集合
            // 这里是匹配到需要当前class(HandlesTypes.value里的class的实现类)的sci,然后将当前class放到initializerClassMap
            for (ServletContainerInitializer sci : entry.getSciSet()) {
                Set<Class<?>> classes = initializerClassMap.get(sci);
                if (classes == null) {
                    classes = new HashSet<>();
                    initializerClassMap.put(sci, classes);
                }
                classes.add(clazz);
            }
        }
    }

    if (handlesTypesAnnotations) {
        // 同样,这里遍历的集合是步骤3放入的
        // typeInitializerMap -> key = 有HandlersTypes注解类名, value = ServletContainerInitializer实现集合,并且,它的value不为空
        // 可以使这里有一个判断`isAnnotation`,所以一般不会走到里面
        AnnotationEntry[] annotationEntries = javaClass.getAnnotationEntries();
        if (annotationEntries != null) {
            for (Map.Entry<Class<?>, Set<ServletContainerInitializer>> entry :
                    typeInitializerMap.entrySet()) {
                if (entry.getKey().isAnnotation()) {
                    String entryClassName = entry.getKey().getName();
                    for (AnnotationEntry annotationEntry : annotationEntries) {
                        if (entryClassName.equals(
                                getClassName(annotationEntry.getAnnotationType()))) {
                            if (clazz == null) {
                                clazz = Introspection.loadClass(
                                        context, className);
                                if (clazz == null) {
                                    // Can't load the class so no point
                                    // continuing
                                    return;
                                }
                            }
                            for (ServletContainerInitializer sci : entry.getValue()) {
                                initializerClassMap.get(sci).add(clazz);
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
}

0 人点赞