基于Tomcat 8.5.30
解析 Servlet
Web 应用的初始化工作是在 org.apache.catalina.startup.ContextConfig
的 configureStart()
方法中实现的,应用的初始化主要是要解析web.xml
文件,这个文件描述了一个 Web 应用的关键信息,也是一个 Web 应用的入口。
web.xml
中的配置会被解析为一个org.apache.tomcat.util.descriptor.web.WebXml
对象。
接下去将会将 WebXml 对象中的属性设置到 Context 容器中,这里包括创建 Servlet 对象、filter、listener 等等。这段代码在org.apache.catalina.startup.ContextConfig
的configureContext(WebXml webxml)
方法中。
WebXml 中private final Map<String,ServletDef> servlets = new HashMap<>();
即为保存 Servlet 的容器。(包括web.xml中和注解中的Servlet)
另外 ServletDef 保存的仅仅是 Servlet 的相关信息,并不是一个 Servlet 实例。
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// ...
context.addChild(wrapper);
}
这段代码清楚的描述了如何将 Servlet 包装成 Context 容器中的 StandardWrapper
,这里有个疑问,为什么要将 Servlet 包装成 StandardWrapper
而不直接是 Servlet 对象。
这里 StandardWrapper 是 Tomcat 容器中的一部分,它具有容器的特征,而 Servlet 是一个独立的 web 开发标准,不应该强耦合在 Tomcat 中。
除了将 Servlet 包装成 StandardWrapper 并作为子容器添加到 Context 中,其它的所有 web.xml 属性都被解析到 Context 中,所以说 Context 容器才是真正运行 Servlet 的 Servlet 容器。一个 Web 应用对应一个 Context 容器,容器的配置属性由应用的 web.xml 指定,这样我们就能理解 web.xml 到底起到什么作用了。
StandardWrapper
保存了 Servlet 实例以及配置信息。
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
// ...
protected volatile Servlet instance = null;
// ...
}
创建 Servlet 对象
load-on-startup >= 0
如果 Servlet 的 load-on-startup 配置项大于 0,那么在 Context 容器启动的时候就会被实例化。
在 conf 下的 web.xml 文件中定义了一些默认的配置项,其定义了两个 Servlet,分别是:org.apache.catalina.servlets.DefaultServlet 和 org.apache.jasper.servlet.JspServlet 它们的 load-on-startup 分别是 1 和 3,也就是当 Tomcat 启动时这两个 Servlet 就会被启动。
org.apache.catalina.core.StandardContext#loadOnStartup
org.apache.catalina.core.StandardWrapper#load
public synchronized void load() throws ServletException {
instance = loadServlet();
// 核心代码
if (!instanceInitialized) {
initServlet(instance);
}
// ...
}
org.apache.catalina.core.StandardWrapper#loadServlet
public synchronized Servlet loadServlet() throws ServletException {
if (!singleThreadModel && (instance != null))
return instance;
// ...
Servlet servlet;
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
servlet = (Servlet) instanceManager.newInstance(servletClass);
initServlet(servlet);
}
创建 Servlet 对象的相关类结构:
load-on-startup < 0
也就是我们访问时才创建 该 Servlet
此时调用的是org.apache.catalina.core.StandardWrapper#allocate
public Servlet allocate() throws ServletException {
instance = loadServlet();
// ...
if (!instanceInitialized) {
initServlet(instance);
}
// ...
return instance;
}
初始化 Servlet
org.apache.catalina.core.StandardWrapper#initServlet
private synchronized void initServlet(Servlet servlet) throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// ...
servlet.init(facade);
instanceInitialized = true;
// ...
}
servlet.init(facade);
调用的就是 Servlet 接口中的 public void init(ServletConfig config) throws ServletException;
方法。
也即完成了 Servlet 的初始化工作。
StandardWrapperFacade 则是 StandardWrapper 的装饰类。
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
package org.apache.catalina.core;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
public final class StandardWrapperFacade
implements ServletConfig {
private final ServletConfig config;
private ServletContext context = null;
public StandardWrapperFacade(StandardWrapper config) {
super();
this.config = config;
}
@Override
public String getServletName() {
return config.getServletName();
}
@Override
public ServletContext getServletContext() {
if (context == null) {
context = config.getServletContext();
if (context instanceof ApplicationContext) {
context = ((ApplicationContext) context).getFacade();
}
}
return (context);
}
@Override
public String getInitParameter(String name) {
return config.getInitParameter(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
而我们常说的 Tomcat 是一个 Servlet 容器,其实更准确的讲是 StandardContext
。
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {}
ContainerBase 中有:
protected final HashMap<String, Container> children = new HashMap<>();