前言
本篇只是探究@HandlesTypes
这个注解的作用
@HandlesTypes,初始化servlet容器参数
位置: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<ServletContainerInitializer>
,再遍历Set<ServletContainerInitializer>
,与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<ServletContainerInitializer>
设置到StandardContext;之后执行应用程序初始化就是遍历这个集合
下面主要围绕这两个容器展开:
- initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet<Class<? exted WebApplicationInitializer>>
- 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());
}
}
}
}
步骤3内容
该步骤是添加了两个缓存
- initializerClassMap -> key = ServletContainerInitializer实现类, value = HashSet<Class<? exted WebApplicationInitializer>>,不过这里只是空集合
- typeInitializerMap -> key = HandlesTypes.value的class, value = ServletContainerInitializer实现类集合(需要传入当前key类型的实现类),
对应的功能:
- initializerClassMap :存储应用初始化程序类及需要的参数对象类型,待后续启动直接获取
- typeInitializerMap :缓存标注了
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<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
中,待后面启动时,直接获取。
具体步骤如下:
- 查找应用程序包下的文件,
/WEB-INF/classes
路径下,通过bcel技术,解析class文件,当前className为key,JavaClassCacheEntry为value存入javaClassCache, - 通过当前的class从javaClassCache中获取父类,再通过父类从
typeInitializerMap
获取sci - 找到sci后,设置到entry里,表明当前的class已经从父类(HandlesTypes.value里的类)继承了sci,
typeInitializerMap
在步骤3时,已经将填充了key=handlesTypes.value的class,value是Set<ServletContainerInitializer>
- 然后再遍历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();
}
重点看下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);
}
}
}
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);
}
这里的功能是将class对应的sci找出来:
- 当前class获取父类还有接口的class,然后都存入javaClassCache
- 通过当前class从javaClassCache,拿到父类class,再从
typeInitializerMap
获取到sci,然后设置到当前的entry - 再遍历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<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;
}
}
}
}
}
}
}