Tomcat源码分析-启动分析(四) webapp
创始人
2025-05-30 02:40:33
0

前言

上一篇文章中我们分析了 Service、Engine、Host、Pipeline、Valve 组件的启动逻辑,在 HostConfig 中会实例化 StandardContext,并启动 Context 容器,完成 webapp 应用程序的启动,这一块是最贴近我们开发的应用程序。在这一篇文章中,我们将要分析 tomcat 是如何解析并初始化应用程序定义的 Servlet、Filter、Listener 等

首先我们思考几个问题:
1、 tomcat 如何支持 servlet3.0 的注解编程,比如对 javax.servlet.annotation.WebListener 注解的支持?

如果 tomcat 利用 ClassLoader 加载 webapp 下面所有的 class,从而分析 Class 对象的注解,这样子肯定会导致很多问题,比如 MetaSpace 出现内存溢出,而且加载了很多不想干的类,我们知道 jvm 卸载 class 的条件非常苛刻,这显然是不可取的。因此,tomcat 开发了字节码解析的工具类,位于 org.apache.tomcat.util.bcel,bcel 即 :Byte Code Engineering Library,专门用于解析 class 字节码,而不是像我们前面猜测的那样,把类加载到 jvm 中

1、 假如 webapp 目录有多个应用,使用的开源框架的 jar 版本不尽一致,tomcat 是怎样避免出现类冲突?

不同的 webapp 使用不同的 ClassLoader 实例加载 class,因此 webapp 内部加载的 class 是不同的,自然不会出现类冲突,当然这里要排除 ClassLoader 的 parent 能够加载的 class。关于 ClassLoader 这一块,后续会专门写一篇博客进行分析

1、Context 容器

首先,我们来看下StandardContext重要的几个属性,包括了我们熟悉的 ServletContext、servlet容器相关的Listener(比如 SessionListener 和 ContextListener)、FilterConfig

protected ApplicationContext context:即ServletContext上下文private InstanceManager instanceManager:根据 class 实例化对象,比如 Listener、Filter、Servlet 实例对象private List applicationEventListenersList:SessionListener、ContextListner 等集合private HashMap filterConfigs:filer 名字与 FilterConfig 的映射关系private Loader loader:用于加载class等资源private final ReadWriteLock loaderLock:用于对loader的读写操作protected Manager manager:Session管理器private final ReadWriteLock managerLock:用于对manager的读写操作private HashMap servletMappings:url与Servlet名字的映射关系private HashMap statusPages:错误码与错误页的映射private JarScanner jarScanner:用于扫描jar包资源
 

StandardContext 和其他 Container 一样,也是重写了 startInternal 方法。由于涉及到 webapp 的启动流程,需要很多准备工作,比如使用 WebResourceRoot 加载资源文件、利用 Loader 加载 class、使用 JarScanner 扫描 jar 包,等等。因此StandardContext 的启动逻辑比较复杂,这里描述下几个重要的步骤:
1、 创建工作目录,比如$CATALINA_HOME\work\Catalina\localhost\examples;实例化 ContextServlet,应用程序拿到的是 ApplicationContext的外观模式
2、 实例化 WebResourceRoot,默认实现类是 StandardRoot,用于读取 webapp 的文件资源
3、 实例化 Loader 对象,Loader 是 tomcat 对于 ClassLoader 的封装,用于支持在运行期间热加载 class
4、 发出 CONFIGURE_START_EVENT 事件,ContextConfig 会处理该事件,主要目的是从 webapp 中读取 servlet 相关的 Listener、Servlet、Filter 等
5、 实例化 Sesssion 管理器,默认使用 StandardManager
6、 调用 listenerStart,实例化 servlet 相关的各种 Listener,并且调用
ServletContextListener
7、 处理 Filter
8、 加载 Servlet

下面,将分析下几个重要的步骤

1.1 触发 CONFIGURE_START_EVENT 事件

ContextConfig 它是一个 LifycycleListener,它在 Context 启动过程中是承担了一个非常重要的角色。StandardContext 会发出 CONFIGURE_START_EVENT 事件,而 ContextConfig 会处理该事件,主要目的是通过 web.xml 或者 Servlet3.0 的注解配置,读取 Servlet 相关的配置信息,比如 Filter、Servlet、Listener 等,其核心逻辑在 ContextConfig#webConfig() 方法中实现。下面,我们对 ContextConfig 进行详细分析

1、 首先,是通过 WebXmlParser 对 web.xml 进行解析,如果存在 web.xml 文件,则会把文件中定义的 Servlet、Filter、Listener 注册到 WebXml 实例中

WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),context.getXmlValidation(), context.getXmlBlockExternal());
Set defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment(webXmlParser));// 创建 WebXml实例,并解析 web.xml 文件
WebXml webXml = createWebXml();
InputSource contextWebXml = getContextWebXmlSource();
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {ok = false;

1、 接下来,会处理 javax.servlet.ServletContainerInitializer,把对象实例保存到 ContextConfig 的 Map 中,待 Wrapper 子容器添加到 StandardContext 子容器中之后,再把 ServletContainerInitializer 加入 ServletContext 中。ServletContainerInitializer 是 servlet3.0 提供的一个 SPI,可以通过 HandlesTypes 筛选出相关的 servlet 类,并可以对 ServletContext 进行额外处理,下面是一个自定义的 ServletContainerInitializer,实现了 ServletContainerInitializer 接口,和 jdk 提供的其它 SPI 一样,需要在 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定该类名 net.dwade.tomcat.CustomServletContainerInitializer

@HandlesTypes( Filter.class )
public class CustomServletContainerInitializer implements ServletContainerInitializer {@Overridepublic void onStartup(Set> c, ServletContext ctx) throws ServletException {for ( Class type : c ) {System.out.println( type.getName() );}}

1、 如果没有 web.xml 文件,tomcat 会先扫描 WEB-INF/classes 目录下面的 class 文件,然后扫描 WEB-INF/lib 目录下面的 jar 包,解析字节码读取 servlet 相关的注解配置类,这里不得不吐槽下 serlvet3.0 注解,对 servlet 注解的处理相当重量级。tomcat 不会预先把该 class 加载到 jvm 中,而是通过解析字节码文件,获取对应类的一些信息,比如注解、实现的接口等,核心代码如下所示:

protected void processAnnotationsStream(InputStream is, WebXml fragment,boolean handlesTypesOnly, Map javaClassCache)throws ClassFormatException, IOException {// is 即 class 字节码文件的 IO 流ClassParser parser = new ClassParser(is);// 使用 JavaClass 封装 class 相关的信息JavaClass clazz = parser.parse();checkHandlesTypes(clazz, javaClassCache);if (handlesTypesOnly) {return;}AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();if (annotationsEntries != null) {String className = clazz.getClassName();for (AnnotationEntry ae : annotationsEntries) {String type = ae.getAnnotationType();if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {processAnnotationWebServlet(className, ae, fragment);}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {processAnnotationWebFilter(className, ae, fragment);}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {fragment.addListener(className);} else {// Unknown annotation - ignore}}}

tomcat 使用自己的工具类 ClassParser 通过对字节码文件进行解析,获取其注解,并把 WebServlet、WebFilter、WebListener 注解的类添加到 WebXml 实例中,统一由它对 ServletContext 进行参数配置。tomcat 对字节码的处理是由
org.apache.tomcat.util.bcel 包完成的,bcel 即 Byte Code Engineering Library,其实现比较繁锁,需要对字节码结构有一定的了解,感兴趣的童鞋可以研究下底层实现。

1、 配置信息读取完毕之后,会把 WebXml 装载的配置赋值给 ServletContext,在这个时候,ContextConfig 会往 StardardContext 容器中添加子容器(即 Wrapper 容器),部分代码如下所示:

private void configureContext(WebXml webxml) {// 设置 Filter 定义for (FilterDef filter : webxml.getFilters().values()) {if (filter.getAsyncSupported() == null) {filter.setAsyncSupported("false");}context.addFilterDef(filter);}// 设置 FilterMapping,即 Filter 的 URL 映射 for (FilterMap filterMap : webxml.getFilterMappings()) {context.addFilterMap(filterMap);}// 往 Context 中添加子容器 Wrapper,即 Servletfor (ServletDef servlet : webxml.getServlets().values()) {Wrapper wrapper = context.createWrapper();// 省略若干代码。。。wrapper.setOverridable(servlet.isOverridable());context.addChild(wrapper);}// ......

1、 tomcat 还会加载 WEB-INF/classes/META-INF/resources/、WEB-INF/lib/xxx.jar/META-INF/resources/ 的静态资源,这一块的作用暂时不清楚,关键代码如下所示:

// fragments 包括了 WEB-INF/classes、WEB-INF/lib/xxx.jar
protected void processResourceJARs(Set fragments) {for (WebXml fragment : fragments) {URL url = fragment.getURL();if ("jar".equals(url.getProtocol()) || url.toString().endsWith(".jar")) {try (Jar jar = JarFactory.newInstance(url)) {jar.nextEntry();String entryName = jar.getEntryName();while (entryName != null) {if (entryName.startsWith("META-INF/resources/")) {context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR,"/", url, "/META-INF/resources");break;}jar.nextEntry();entryName = jar.getEntryName();}}} else if ("file".equals(url.getProtocol())) {File file = new File(url.toURI());File resources = new File(file, "META-INF/resources/");if (resources.isDirectory()) {context.getResources().createWebResourceSet(WebResourceRoot.ResourceSetType.RESOURCE_JAR,"/", resources.getAbsolutePath(), null, "/");}}}

相关内容

热门资讯

贵阳最新学区划分,最新或202... 贵阳公办小学招生范围按照义务教育免试就近入学原则,市区公办小学实行依街道划片招生。本文为您介绍贵阳小...
遵义最新学区划分,最新或202... 遵义公办小学招生范围按照义务教育免试就近入学原则,市区公办小学实行依街道划片招生。本文为您介绍遵义小...
安顺最新学区划分,最新或202... 安顺公办小学招生范围按照义务教育免试就近入学原则,市区公办小学实行依街道划片招生。本文为您介绍安顺小...
六盘水最新学区划分,最新或20... 百年教育网小编为您整理了关于六盘水市幼升小学区划分详情的相关信息,希望对您有帮助,想了解更多请继续关...
遍历二叉树线索二叉树 遍历二叉树 遍历定义 顺着某一条搜索路径寻访二叉树中的每一个结点,使得每个节点均被依次...
springboot简介和项目... Java知识点总结:想看的可以从这里进入 目录SpringBoot1、简介和原理1....
最新或2023(历届)嘉祥教育... 信息时报讯 面临中考,初三学生陈黎的父母十分发愁。一是孩子成绩并不拔尖,另外,父母虽然有心让儿子出...
“牛孩儿”“每天一题”助你提升... “小升初”的战鼓越擂越响,你准备好了吗?不要着急,自4月29日起,中原网教育频道官方微信“中原教育”...
这是一封发给西安小升初家长的邀... 秦学·伊顿交大校区4月9日晚上举办的小升初讲座圆满结束了,回顾讲座现场的瞬间,小编有一些小小的感动。...
四大法宝护航“528冲刺班”巨... 又是一个四月,春风扑面,鲜花盛开。又是一届小考,竞争激烈,埋头伏案。又是一轮冲刺,全力以赴,舍我其谁...
小升初数学面谈题型归纳 小升初... 数学在小升初择校中的重要性可以说是毋庸置疑的。很多一线名校例如二中应元、六中珠江、广大附等都对数学情...
vue2+3 pinia v... 1. 为什么要学习vue1.官网https://v3.cn.vuejs.org/guide/migr...
防雷设计、防雷检测为什么选同为... 随着现代科技的不断发展,电子设备得到广泛应用,而雷电等自然灾害也越来越频...
最新或2023(历届)快乐的下...  今天下午,我去了隋唐遗址。那里好美丽;有小河;有草地,小河里有鱼,有虾。  我先说河,有的河水清澈...
最新或2023(历届)6年级数...  篇一  今天,妈妈给我出了一道题,题目是这样的:“一头牛可换6头猪,2头猪可换10只羊,三只羊可换...
本次小升初直升考试试卷分析这就... 还记得前几天预告的小升初直升考试吗?这次的考试对于小学六年级的孩子们来说,是非常重要的。家长朋友们也...
西安小升初528预录来了! 西... 相信大家这几天除了被各种各样的学校参观弄得有点晕,到底这参观是几个意思呢!是有暗示还是没暗示,其实这...
最新或2023(历届)认真积极...   今天妈妈带我去学英语,上课我认真听盘,积极的举手回答问题,下课后妈妈表扬了我,我很高兴。回到家我...
【js】多分支语句练习(2) 个人名片: 😊作者简介:一名大一在校生,w...
Git 的 Cherry-Pi... 1、什么是 Cherry-Pickcherry-pick 是 Git 版本控制工具中的一个命令&#x...