StandardContext是Context容器的一个标准实现,一般情况下,Tomcat都是使用StandardContext类来创建Context容器。前面讲过,Context容器代表了一个Web应用,Tomcat本身支持部署多个应用,相应的每个应用都会有一个StandardContext实例来对应。在使用SpringBoot的前提下,一个Tomcat也就仅创建一个StandardContext实例,来服务这个SpringBoot框架对应的应用。
StandardContext既然针对的是一整个Web应用,那这个Web应用的功能就需要靠它来支撑起来,StandardContext通过一系列组件来实现它的功能。
载入器
Tomcat规定了每个Context容器都只加载自己应用下的类,不能加载该Tomcat下其他应用的类,所以,StandardContext持有一个“载入器(Loader)”对象,用来加载该应用程序下的所有类(servlet及相关类)。Loader的标准实现为WebappLoader。
重载
StandardContext中有一个reloadable属性,通过 setReloadable 方法可以设置它的值,如果设为true的话,证明该Context容器支持重载,Tomcat会安排一个后台线程不断地检查该Context关联的class及jar包的最后修改日期,如果发现有变化,就会重新加载该Context容器下的所有类。如果reloadable设为false,那么该Context容器不支持重载。
会话(session)
会话也是维护在Context这一层的,所以StandardContext中持有“会话管理器(Manager)”对象,来管理针对该Web应用的会话。Manager的标准实现为StandardManager,单个session的标准实现为StandardSession(实现了HttpSession接口)。session只在需要时才会创建,Tomcat会安排一个后台线程不断检查session对象有没有过期,如果过期了就将其失效。
子容器与映射器
一个Web应用程序一般来说会有多个servlet实例,一个servlet就对应一个Wrapper容器实例,所以一个Context实例下就会管理多个Wrapper实例,这些Wrapper实例称为Context的“子容器”。当Context接收到一个servlet请求后,需要知道该把这个请求具体交给哪个Wrapper来处理,这其间就需要有个映射关系了,将servlet的请求uri映射到具体的Wrapper上,StandardContext就持有一个“映射器(Mapper)”对象来实现这个映射逻辑。
StandardContext中维护了 servletMappings 与 children 两个map集合,通过这两个map,很容易就可以根据请求uri找到对应的Wrapper实例。
来看一下“映射器”是如何实现此映射逻辑的,StandardContextMapper是映射器的标准实现,其中的map方法就是通过request找Wrapper的方法,下面是缩减版的代码
public Container map(Request request, boolean update) {
// 获取相对路径uri (假设请求的资源路径为 /myapp/animal/tom, /myapp 代表部署在Tomcat中的应用名为myapp)
String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath(); //结果示例: “/myapp”
String requestURI = ((HttpRequest) request).getDecodedRequestURI(); //结果示例: “/myapp/animal/tom”
String relativeURI = requestURI.substring(contextPath.length()); //结果示例: “/animal/tom”
Wrapper wrapper = null;
if (!(relativeURI.equals("/")))
name = context.findServletMapping(relativeURI);
if (name != null)
wrapper = (Wrapper) context.findChild(name);
return wrapper;
}
上面映射器的逻辑是Tomcat4的逻辑,自Tomcat5之后,获取Wrapper的代码被放到了Request对象中,StandardContextMapper这个类也没有了,但是整体逻辑大致想通,明白思想即可。
start() 与 invoke()
上面列举了StandardContext中持有的比较重要的几个组件,各个组件也为StandardContext提供了相应的功能实现。而StandardContext具体应用这些组件的逻辑则主要集中在两个方法中:start()与invoke()。
start()方法是一个生命周期方法,Tomcat启动时会调用所有组件的start()方法,在StandardContext中,start()方法主要完成的功能是
- 配置资源,识别到该Context容器对应的应用目录,将class及jar纳入自己的资源库中。
- 创建各个组件实例,并调用各组件的start()方法完成组件的初始化。
- 创建各个子容器(Wrapper)实例,并调用子容器的start()方法完成子容器的初始化。
start()方法主要是完成Context的一系列初始化操作,start()方法执行成功后,该StandardContext就可以对外工作了。
invoke()方法是StandardContext处理具体请求的方法,当一个servlet请求打进来后,就会进入到invoke()方法中,StandardContext#invoke()方法逻辑很简单,就是调用了一下它的Pipeline组件的invoke方法,其实真正对请求进行处理的对象是该Pipeline的基础阀:StandardContextValve。StandardContextValve#invoke() 方法会调用到映射器的map方法,根据请求uri找到指定Wrapper对象,然后调用Wrapper对象的invoke()方法,等Wrapper#invoke()方法执行结束后,StandardContext对本次servlet请求的处理也就基本完成了。(Wrapper#invoke()方法的逻辑请参考上一篇文章)。
在servlet的service方法中,如果有获取session的逻辑,那么StandardContext就会为其去寻找session,如果没有找到就会创建一个session给servlet用。具体逻辑可以参考第九篇文章。
StandardContext的基本逻辑就讲到这里,这篇文章串联了前些篇文章的知识,将StandardContext的完整脉络呈现了一下。下篇文章来研究下Host与Engine两种容器。