• SpringMvc(二)HandlesTypes源码


    前言

    本篇只是探究@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,再遍历Set,与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设置到StandardContext;之后执行应用程序初始化就是遍历这个集合

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

    1. initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet>

    2. typeInitializerMap -> key = HandlesTypes注解里设置的class, value = ServletContainerInitializer实现集合,

        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());
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    步骤3内容

    该步骤是添加了两个缓存

    • initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet>,不过这里只是空集合
    • 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
        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);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    步骤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
    4. 然后再遍历sci集合,将sci作为key,当前class作为value存入initializerClassMap,当前的class是实现类,因为sci是通过superClassName获取的
    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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

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

     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);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

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

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    1. 当前class获取父类还有接口的class,然后都存入javaClassCache
    2. 通过当前class从javaClassCache,拿到父类class,再从typeInitializerMap获取到sci,然后设置到当前的entry
    3. 再遍历sci,匹配当前class,匹配上就设置到initializerClassMap,而这时匹配的key是实现类并不是接口,因为它是通过superClassName查找的sci,
    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
                try {
                    // 在上面的`populateJavaClassCache`只是将`/WEB-INF/classes`下的所有class加载了,并没有对sci进行设置
                    // 所以这里的步骤就是将`populateJavaClassCache`找出的class,设置对应的sci,如果是HandlesTypes.value里的类,那么就会有sci
                    // 同时,它将class作为key,从javaClassCache获取到父类,然后再通过父类,从typeInitializerMap获取到Set设置到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;
                            }
                        }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
  • 相关阅读:
    flask 框架web开发视频笔记
    C++ list容器的实现及讲解
    redis面试题收集
    Dubbo——初识RPC、Dubbo框架、使用直连方式实现Dubbo
    PyQt5(一) PyQt5安装及配置,从文件夹读取图片并显示,模拟生成素描图像
    Java中HashMap类简介说明
    mac如何创建mysql数据库
    Elasticsearch单机部署(Linux)
    Azure DevOps (三) 实现和Jenkins的联动
    react小记/基本语法/reactjs整理(持续更新)
  • 原文地址:https://blog.csdn.net/qq_28911061/article/details/127715708