SpringMvc(二)HandlesTypes源码
创始人
2024-04-04 14:32:29
0

文章目录

  • 前言
  • @HandlesTypes,初始化servlet容器参数
    • 步骤3内容
    • 步骤4内容

前言

本篇只是探究@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 defaults = new HashSet<>();defaults.add(getDefaultWebXmlFragment(webXmlParser));// 读取`/WEB-INF/tomcat-web.xml`Set 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.xmlMap fragments = processJarsForWebFragments(webXml, webXmlParser);// 步骤2. 对这些配置排序Set 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.xmlif (ok) {ok = webXml.merge(orderedFragments);}// 步骤7a// 合并 tomcat-web.xmlwebXml.merge(tomcatWebXml);// 步骤7b. 合并全局默认的web.xml(defaultServlet jspServlet)webXml.merge(defaults);// Step 8. Convert explicitly mentioned jsps to servletsif (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 JARsSet 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>> 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>,不过这里只是空集合
  • 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 detectedScis;try {WebappServiceLoader 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)`// 那么这里就是WebApplicationInitializerfor (Class type : types) {if (type.isAnnotation()) {handlesTypesAnnotations = true;} else {handlesTypesNonAnnotations = true;}// 这里就是存放一个映射,因为,后面我们需要对有HandlersTypes标注的类进行参数传入// key = HandlesTypes.value的class, value = ServletContainerInitializer实现集合Set 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
  4. 然后再遍历sci集合,将sci作为key,当前class作为value存入initializerClassMap,当前的class是实现类,因为sci是通过superClassName获取的
protected void processClasses(WebXml webXml, Set orderedFragments) {// 步骤4. Process /WEB-INF/classes for annotations and// @HandlesTypes matchesMap 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 itjavaClassCache.clear();}

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

 protected void processAnnotationsWebResource(WebResource webResource,WebXml fragment, boolean handlesTypesOnly,Map 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,简单看一下:

protected void processAnnotationsStream(InputStream is, WebXml fragment,boolean handlesTypesOnly, Map javaClassCache)throws ClassFormatException, IOException {// 解析classClassParser 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,
protected void checkHandlesTypes(JavaClass javaClass,Map 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=trueif (handlesTypesNonAnnotations) {// 这里比较简单,没贴代码,具体他会将该类的父类及父类的父类递归的找出来,然后放到javaClassCache里// javaClassCache: key = className, value = 父类, 接口populateJavaClassCache(className, javaClass, javaClassCache);JavaClassCacheEntry entry = javaClassCache.get(className);if (entry.getSciSet() == null) {// sciSet 指的是 Settry {// 在上面的`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 continuingreturn;}// initializerClassMap 在步骤3中放入的,value是空的集合// 这里是匹配到需要当前class(HandlesTypes.value里的class的实现类)的sci,然后将当前class放到initializerClassMapfor (ServletContainerInitializer sci : entry.getSciSet()) {Set> 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, Set> 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// continuingreturn;}}for (ServletContainerInitializer sci : entry.getValue()) {initializerClassMap.get(sci).add(clazz);}break;}}}}}}
}

相关内容

热门资讯

华谊兄弟深陷法律与财务漩涡 近日,中国影视巨头华谊兄弟及其创始人深陷法律与财务漩涡。 据天眼查官网显示,近日华谊兄弟旗下两家核心...
如何将制度优势变为“现金流”?... 2025年12月18日,海南全岛封关运作正式启动。这一天,被有意地与47年前——1978年12月18...
北京警方:5名犯罪嫌疑人均已抓... 导 读 “就剐蹭了一下,居然要我赔10万?” 近日,北京海淀警方经过侦查打掉一个做局“碰瓷”的敲诈勒...
锂矿龙头涉嫌内幕交易罪单位犯罪... 【大河财立方消息】12月29日,赣锋锂业发布公告,公司于当日收到宜春市公安局的移送起诉告知书,因涉嫌...
赣锋锂业:2025年涉内幕交易... 【赣锋锂业因涉嫌内幕交易罪被移送起诉】12月29日,赣锋锂业(002460.SZ)公告透露,2025...
严重职务违法、涉嫌受贿犯罪!许... 【导读】中国工商银行云南省分行原党委书记、行长许海被开除党籍 中国基金报记者 晨曦 又有国有大行干部...
《安顺市黄果树旅游景区建设促进... 人民网安顺12月29日电 (记者王秀芳)12月29日,记者从安顺市相关新闻发布会上获悉,《安顺市黄果...
涉嫌内幕交易罪单位犯罪,赣锋锂... 12月29日,赣锋锂业(002460.SZ)公告称,公司于2025年12月29日收到宜春市公安局的移...
学前儿童学籍管理出台新政策,教... 近日,教育部印发了《全国学前儿童学籍管理办法(试行)》(以下简称《办法》)。教育部基础教育司负责人就...
华测检测:关注国家区域发展战略... 有投资者在互动平台向华测检测提问:“请问海南封关后对贵公司在海南的进出口商品检验检疫订单增长是否有帮...