前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringMvc(二)HandlesTypes源码

SpringMvc(二)HandlesTypes源码

作者头像
用针戳左手中指指头
修改2023-10-24 18:25:45
7300
修改2023-10-24 18:25:45
举报
文章被收录于专栏:学习计划

前言

本篇只是探究@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;
                        }
                    }
                }
            }
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • @HandlesTypes,初始化servlet容器参数
    • 步骤3内容
      • 步骤4内容
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档