本篇只是探究@HandlesTypes这个注解的作用
位置:org.apache.catalina.startup.ContextConfig#webConfig
首先我们要知道注解@HandlesTypes的作用是为启动程序确定入参类型,记住这一点,后面的操作都是围绕这个展开。
webConfig这个方法的主要是解析web.xml,为servlet容器做准备,主要有以下步骤:
**查找web.ml:**包含tomcat-web.xml, Tomcat/conf/web.xml, 应用的web.xml,还有jar包里的web.xml
**找到应用中所有的ServletContainerInitializer实现类:**将应用目录下META-INF/services/javax.servlet.ServletContainerInitializer加载,并将加载的实现类作为key放入initializerClassMap,这是还没有添加value;
initializerClassMap 可以把它看做是应用启动程序参数映射集合
**HandlesTypes标注的实现类生成映射:**如果该实现类有@HandlesTypes注解,则将注解里的value作为key,该实现类(ServletContainerInitializer实现类)添加到value,存入typeInitializerMap,
**匹配启动程序需要的参数(查找HandlesTypes.value的实现类):**扫描并解析应用下的class文件,通过当前class匹配typeInitializerMap里的key(实际是通过superClassName匹配的),拿到需要传入参数的Set,再遍历Set,与initializerClassMap的key匹配,匹配上就把当前class(也就是HandlesTypes.value的实现类)添加到initializerClassMap的value中;
到这里,typeInitializerMap的工作也就完成了,所以,它负责的工作就是:缓存标注了@HandlesTypes的ServletContainerInitializer实现类,可以直接通过key(class)取到需要传入该class的ServletContainerInitializer实现类,然后和initializerClassMap匹配, 将匹配上的class添加到initializerClassMap.value里
将读取的web.xml配置合并
将webxml配置设置到StandardContext
读取jar包下META-INF/resources/里的静态资源并设置到StandardContext
将找的Set设置到StandardContext;之后执行应用程序初始化就是遍历这个集合
下面主要围绕这两个容器展开:
initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet
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());}}}}
该步骤是添加了两个缓存
对应的功能:
HandlesTypes的ServletContainerInitializer实现类,可以直接通过key(class)取到需要传入该class的ServletContainerInitializer实现类,然后和initializerClassMap匹配, 将匹配上的class添加到initializerClassMap.value里具体步骤:
META-INF/services/javax.servlet.ServletContainerInitializer下的文件类ServletContainerInitializer实现类,实现类作为key put到initializerClassMap, 待后面步骤使用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);}}}
改步骤是通过当前class,获取superClassName,从typeInitializerMap获取需要传入参数的ServletContainerInitializer实现类,并把当前class作为参数类型存到initializerClassMap中,待后面启动时,直接获取。
具体步骤如下:
/WEB-INF/classes路径下,通过bcel技术,解析class文件,当前className为key,JavaClassCacheEntry为value存入javaClassCache,typeInitializerMap获取scitypeInitializerMap在步骤3时,已经将填充了key=handlesTypes.value的class,value是SetinitializerClassMap,当前的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找出来:
typeInitializerMap获取到sci,然后设置到当前的entryinitializerClassMap,而这时匹配的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;}}}}}}
}