servlet 是一套规范, javax.servlet 包中提供了接口 或帮助类,具体的实现由servlet 容器提供商实现。
tomcat 包含的servlet 容器为 context 容器,context 容器初始化时会从 web.xml里读取servlet 元素及其他servlet相关配置。
tomcat 启动时将 servlet配置 包装成 wapper,wapper由 context管理。
一个srevlet 包装成一个wapper ,一个web 应用 WAR 包对应一个 context。
CONTEXT --servlet容器
context 实例创建时,默认的是standardcontext,会绑定一个监听者ContextConfig。
context 启动调用 startInternal() 时,会触发一个启动事件,ContextConfig 监听到该事件进行配置初始化。
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
CONFIGURE_START_EVENT事件ContextConfig做的事情如下:
调用 webConfig() 方法解析 context 级别的web.xml。
standardContext 触发事件时把自己传给了 ContextConfig 监听者,ContextConfig 在解析后将设置 standardcontext 的相关属性
1. 解析 webapp应用里的 web.xml, 调 WebXmlParser.parseWebXml() 方法, 解析生成配置的对象实例。
2. ContextConfig 接着调用 configureContext() 方法,顾名思义就是设置context ,设置 standardcontext 的相关属性 。
将上一步解析的对象包装成wapper,wapper 其实就是 servlet 的 tomcat 版的实现,Wrapper 作为 Context 的子容器。
---2.1 ContextConfig 会调用传过来的 standardcontext 容器的 createWrapper() 方法,为每一个解析过的 servlet 封装成 standardWapper 对象。
---2.2 然后将这个standardWapper 对象添加到 standardcontext 容器,这里就可以发现 standardContext 确实是 servlet 的管理容器。
---2.3 其他的配置如 servlet 的url-mapping 也会保存到 standardContext 容器。
这里 那么Context 触发的 ContextConfig 监听者的动作就完成了,接下来Context 继续启动,启动子容器,也就是Wrapper 容器。
Wrapper 容器负责具体的servlet 的实例化和初始化。
下面看看具体的servlet 初始化动作:
---------关于 servlet 体系------------------------
说到 servlet 容器 standardcontext , 是和 servlet 体系不可分的。
servlet 体系的核心是 Servlet 接口 ,位于 javax.servlet 包。
servlet规范中有这么一句话:
对于未托管在分布式环境中(默认)的servlet而言,servlet容器对于每一个Servlet声明必须且只能产生一个实例;
servlet 可以实现 SingleThreadModel 接口,这样容器就会产生多个servlet 实例
但是这个接口并不保证其他会话属性的线程安全性。该接口的说明如下:
* Note that SingleThreadModel does not solve all thread safety issues. For
* example, session attributes and static variables can still be accessed by
* multiple requests on multiple threads at the same time, even when
* SingleThreadModel servlets are used. It is recommended that a developer take
* other means to resolve those issues instead of implementing this interface,
* such as avoiding the usage of an instance variable or synchronizing the block
* of the code accessing those resources. This interface is deprecated in
* Servlet API version 2.4.
(那么开发者必须自己保证线程的安全性。Spring MVC 的 servletDispatcher 的前端控制器模式可以研究一下。)
通常是实现 HttpServlet 这个抽象类。
servlet 作为一个web 组件,是有生命周期的。
srevlet接口的生命周期分为 :
1. init()
实际调用的是 GenericServlet.init(ServletConfig config) 方法。
这里 ServletConfig 也是servlet 体系的一个接口。获取web.xml里解析后的servlet相关配置。
该方法由容器初始化servlet实例时调用:
也就是上文提到的wapper 进行初始化servlet 实例时调用,实际是standardWrapper 调用的。
最后一行的servlet.init(facade) 方法就是初始化调用。
standardWrapper传递给init() 的对象其实是 :
/**
* The facade associated with this wrapper.
*/
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
(这里就又用到了facade模式,先带过。)
这个StandardWrapperFacade 其实就是 servletConfig 接口的实现,封装了会话接口专门给 standardWrapper 初始化servlet时调用。
那么 StandardWrapperFacade 的配置对象this其实就是 standardWrapper ,因为standardWrapper 包装了 servlet的解析配置。
那么 StandardWrapper 必然实现了 ServletConfig 接口
由上图可以发现, standardContext 启动过程中当web.xml的配置读取封装完成之后,包括之前提到的servlet 的配置读取和封装实例化wrapper 之后,就会loadOnstartup 开始实例化和初始化servlet了。具体的实例化和初始化就由 StandardWrapper 自己完成。
实例化servlet代码如下:
这里使用了standardContext 容器绑定的类加载器加载;具体的是
这个classLoader 是初始化的时候由standardContext 传递的;
就是这个WebappLoader,这个loader是tomcat 自己定义的。实现了Loader接口,这个是tomcat 自定义的一个接口,这个接口提供了这个方法
这个WebappLoader 持有一个 classLoader 实例 ,这个实例就是 WebappClassLoader的实例。
因此,我们知道了Servlet是由 Tomcat 自定义的 WebappClassLoader 加载的。
那么 WebappClassLoader 的父类加载器是什么?,下面是webappClassLoader实例化的代码
使用反射来实例化,反射时调用了指定父类加载器的构造方法,这个父类加载器就是容器绑定的父类加载器。如果该容器没有,则寻找父容器绑定的类加载器,
最终找到了Engine容器的 父类加载器。
最终发现是这个类加载器:
是Catalina 类的加载器,下面是Bootstrap 启动类 加载Catalina的代码
发现是自定义的catalinaLoader 加载的
这个catalinaLoader是怎么回事呢?
默认情况下,commomLoader 定义的classPath 如下,加载Tomcat 下的lib文件
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
现在整理一下:
systemClassLoader (appClassLoader)
|
commonLoader(加载Tomcat 的lib)
|
serverLoader,catalinaLoader(默认情况下未指定加载类路径,就是commonLoader)
|
各容器的classLoader (同上)
|
WebappClassLoader (加载WAR包)
那么Engine 的父类加载器就是 commonLoader 了,容器是Tomcat 利用Digester 组件实例化的,
Digester实例化容器的classLoader是什么呢?catalina 设置了Digester 使用线程上下文的ClassLoader,而这个线程上下文的classLoader是在Bootstrap里设置的,就是
catalinaLoader,至此,真相大白,加载容器的classLoader也是CatalinaLoader,也就是 commonLoader。
所以,默认情况下commonLoader加载 Tomcat 下的Lib,和 容器组件。
WebappClassLoader 加载用户程序代码 ,也包括 web-inf/lib 。
因为 WebappClassLoader 的父类加载器是commonLoader,
Bootstrap 类创建 commonLoader的方法是 createClassLoader()没有父类加载器,那么是默认的父类加载器,调用这个工厂类
ClassLoaderFactory.createClassLoader(repositories, parent); 其中parent =null
当parent 为null时, new URLClassLoader(),默认指定systemClassLoader作为其父类加载器。也就是加载启动Tomcat时 在 setClassPath.bat 脚本里指定的 JAVA_OPTIONS -classPath="..." 。这些类和JAR的appClassLoader。
回到之前的servlet 的实例化问题上来,servlet 实例化是由webappLoader 持有的WebappClassLoader 加载和实例化的。
看看实际项目中的例子:
web.xml 中,spring MVC的配置如下:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/web-context.xml
</param-value>
</init-param>
<init-param>
<param-name>publishContext</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
这里 DispatcherServlet 初始化的时候,也就是之前提到的servlet的 init() 方法,在它的父类 FrameServlet 中默认创建一个IOC容器 ,XmlWebApplicationContext,这个容器由 DispatcherServlet 持有。
这个容器创建之后,调用 refresh() 初始化,由contextConfigLocation 指定bean 定义文件。
XmlWebApplicationContext 实现的 loadBeanDefinitions 方法如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException
{
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
默认从classPath读取xml资源:classLoader.getResourceAsStream(this.path); 或者 Class.getResourceAsStream(this.path);
第一步是创建解析过bean 定义的 beanFactory,调用 XmlbeanDefinitionReader 解析:
第二步然后根据解析过的bean定义 创建bean实例:
直接调用beanFacotory 的 getBean () 方法创建一个单例bean。
具体见 DefaultSingletonBeanRegistry