对于Tomcat来说,Catalina是其核心组件,所有基于JSP/Servlet的Java Web应用均需要依托Servlet容器运行并对外提供服务。
4.0版本后,Tomcat完全重新设计了其Servlet容器的架构,新版本的Servlet容器被命名为Catalina。
Catalina包含了前面讲到的所有容器组件。它通过松耦合的方式继承Coyote,以完成按照请求协议进行数据读写。同时还包括我们的启动入口、Shell程序等
1)Tomcat分层示意图
2)Digester
Catalina使用Digester解析XML配置文件并创建应用服务器
Digester是一款将XML转换为java对象的事件驱动型工具,是对SAX的高层次封装。通过流读取XML文件,当识别出XML节点后便执行特定的动作,或者创建java对象,或者执行对象的某个方法。
Digester路径为:tomcat-util-scan.jar包下 org.apache.tomcat.util.digester包路径下
3)Server
以下是有关Server的结构
1)%TOMCAT_HOME%/bin/startup.bat
作为tomcat的启动文件,可以看到,其直接调用了catalina.bat文件
set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
set SECURITY_POLICY_FILE=
set DEBUG_OPTS=
set JPDA=
可以看到主类为Bootstrap,执行方法为start
2)Bootstrap
反射的绝佳用例
/**
* Start the Catalina daemon.
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
其主要作用就是启动一个catalinaDaemon,在其init()方法中可以看到
Class> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
...
catalinaDaemon = startupInstance;
3)Catalina.start()
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
...
/**
* Start a new server instance.
*/
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
...
/**
* Create and configure the Digester we will be using for startup.
*/
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
HashMap, List> fakeAttributes = new HashMap<>();
ArrayList attrs = new ArrayList<>();
attrs.add("className");
fakeAttributes.put(Object.class, attrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
...
在初始化的时候根据server.xml配置来加载Server、Service等对象
Catalina使用Digester来解析XML(server.xml)配置文件并创建应用服务器。
Digester是一款用于将XML转换为java对象的事件驱动型工具。是对SAX的高层次封装。
现在Digester已经移到了Apache Commons项目,我们可以通过以下来获取对Digester的使用
org.apache.commons
commons-digester3
3.2
具体的使用可参考:Apache Commons Digester 一 (基础内容、核心API) 这篇文章
Web应用加载属于Server启动的核心处理过程。
Catalina对web应用的加载主要由StandardHost、HostConfig、StandardContext、ContextConfig、StandardWrapper这五个类来完成。时序图如下:
Catalina在解析server.xml时调用Catalina.createStartDigester()方法,具体内容如下:
// 1.关于Server的解析,创建
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
// 2.关于Service的解析创建
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
// 调用其Server.addService方法,将该service添加到Server中
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
默认会加载StandardServer类。
并且会创建Service实现类,并添加到Server中。
1)StandardServer.addService()如下所示
public void addService(Service service) {
service.setServer(this);
synchronized (servicesLock) {
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
if (getState().isAvailable()) {
try {
// 启动service
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// Report this property change to interested listeners
support.firePropertyChange("service", null, service);
}
}
2)StandardService.start()
默认实现在LifecycleBase类中,每个组件都使用到了这个方法,各自实现其startInternal()方法即可
Catalina在解析server.xml时调用Catalina.createStartDigester()方法,有关于Service具体内容如下:
// 1.创建Service
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
// 2.创建Listener 并添加到Service
digester.addObjectCreate("Server/Service/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 3.创建Executor,并添加到Service
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor");
// 4.创建Connector并添加到Service
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
1)StandardService.startInternal()
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// 1.启动engine
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 2.启动executor
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// 3.启动connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
同理,我们通过分析以上的方式来分析StandardEngine
StandardHost加载web应用(即StandardContext)的入口有两个:
* Catalina在构造Server实例时,如果在server.xml中Host元素存在Context子元素,那么Context元素就会作为Host容器的子容器添加到Host实例中
* HostConfig自动扫描部署目录,创建Context实例并启动。这是大多数Web应用的加载方式
1)StandardHost.startInternal()启动虚拟主机
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}
HostConfig类注释如下:
/**
* Startup event listener for a Host that configures the properties
* of that Host, and the associated defined contexts.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class HostConfig implements LifecycleListener {
主要用于监听Host的事件,实现了lifecycleEvent方法,具体内容如下:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// 定时扫描Web应用的变更,并进行重新加载
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
// 该事件在Host启动时触发,host启动后扫描web应用包进行部署
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
既然说是Host的监听器,那么我们就从源码的角度来分析一下HostConfig是如何监控的
1)寻找lifecycleEvent方法的被调用场景
发现只有LifecycleBase.fireLifecycleEvent有调用
2)寻找LifecycleBase.fireLifecycleEvent方法被调用场景
分析:既然是Host的监听器,那就应该是StandardHost的某个方法调用的,我们来看下StandardHost的类结构图
通过上面两个图结合来看,可以确定在以下两个方法中
而backgroundProcess方法中监听的是Lifecycle.PERIODIC_EVENT事件,所以我们最后就确定LifecycleBase.setStateInternal方法
LifecycleBase.setState方法有关于调用LifecycleBase.setStateInternal()
3)寻找LifecycleBase.setState方法被调用场景
我们直接去其子类ContainerBase中看setState被调用的场景
发现有两个方法startInternal、stopInternal调用了setState方法,这两个方法就很熟悉了,startInternal是抽象类LifecycleBase的抽象方法,需要子类实现的
startInternal方法在StandardHost中也有实现,内容如下:
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
// 调用ContainerBase.startInternal()
super.startInternal();
}
4)那么HostConfig的监听器是什么时候关联到StandardHost的呢?
Catalina.createStartDigester()方法是解析server.xml时被调用的,里面有一句
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
// addRuleSet方法
public void addRuleSet(RuleSet ruleSet) {
String oldNamespaceURI = getRuleNamespaceURI();
String newNamespaceURI = ruleSet.getNamespaceURI();
if (log.isDebugEnabled()) {
if (newNamespaceURI == null) {
log.debug("addRuleSet() with no namespace URI");
} else {
log.debug("addRuleSet() with namespace URI " + newNamespaceURI);
}
}
setRuleNamespaceURI(newNamespaceURI);
// 主要是这句,会调用HostRuleSet.addRuleInstances方法
ruleSet.addRuleInstances(this);
setRuleNamespaceURI(oldNamespaceURI);
}
// 我们来看下这个HostRuleSet.addRuleInstances方法
public void addRuleInstances(Digester digester) {
// 1.创建StandardHost
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
// 2.创建并添加HostConfig
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
//Cluster configuration start
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
}
所以:HostConfig是在使用Digester解析server.xml时就关联到StandardHost的
至此,我们就看到一条完整的调用链:
StandardHost.startInternal() -> ContainerBase.startInternal() -> LifecycleBase.setState() -> LifecycleBase.setStateInternal() -> LifecycleBase.fireLifecycleEvent() -> HostConfig.lifecycleEvent()
StandardContext包含了具体的web应用初始化及启动工作,该部分工作由组件Context完成
Tomcat提供的ServletContext实现类为ApplicationContext,该类仅为Tomcat服务器使用;Web应用使用的是其门面类ApplicationContextFacade。
1)StandardContext的启动过程(被StandardHost调用start方法)
实际调用为LifecycleBase.start(),真正StandardContext的实现方法为startInternal(),
具体内容有点长,可具体参考源码,博客参考:https://blog.csdn.net/w1992wishes/article/details/79499867
重点过程包括:
* 初始化当前Context使用的WebResourceRoot并启动,WebResourceRoot维护了web应用所有的资源集合(Class文件、jar包以及其他资源),主要用于类加载和按照路径查找资源文件
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
* 创建web应用类加载器(WebappLoader),并启动
* 创建会话管理器
* 创建实例管理器(InstanceManager),用于创建对象实例,如Servlet、Filter等
* 实例化应用监听器,分为事件监听器(ServletContextAttributeListener、ServletRequestAttributeListener、ServletRequestListener)和生命周期监听器(HttPSessionListener、ServletContextListener)
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
* 对于loadOnStartup>=0的Wrapper,调用wrapper.load(),该方法负责实例化Servlet,并调用Servlet.init进行初始化
注意:这个只是针对于启动就加载的Servlet
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
上述StandardContext的启动过程,并不包含web.xml中Servlet、请求映射、Filter等相关配置。
这部分的工作是由ContextConfig负责的。
至于ContextConfig是如何关联StandardContext的,可以参考上面 那么HostConfig的监听器是什么时候关联到StandardHost的呢?
1)ContextConfig.lifecycleEvent(用于监听各种事件)
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// 创建Wrapper(重要阶段)
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
// 在Context启动之前触发,用于更新Context的docBase属性和解决Web目录锁的问题
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
// Context初始化阶段
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
2)详细介绍CONFIGURE_START_EVENT事件触发的configureStart()方法
ContextConfig.configureStart()方法根据配置创建Wrapper(Servlet)、Filter、ServletContextListener,具体步骤如下:
protected synchronized void configureStart() {
...
// 重要方法
webConfig();
...
}
// webConfig()
/**
* Scan the web.xml files that apply to the web application and merge them
* using the rules defined in the spec. For the global web.xml files,
* where there is duplicate configuration, the most specific level wins. ie
* an application's web.xml takes precedence over the host level or global
* web.xml file.
*/
protected void webConfig() {
...
// Step 8. Convert explicitly mentioned jsps to servlets
if (ok) {
convertJsps(webXml);
}
// Step 9. Apply merged web.xml to Context
if (ok) {
configureContext(webXml);
}
}
// configureContext()解析web.xml并加载Servlet/Filter
private void configureContext(WebXml webxml) {
// 加载Servlet
for (ServletDef servlet : webxml.getServlets().values()) {
// 创建Wrapper,具体在3)中继续分析
Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
Map params = servlet.getParameterMap();
for (Entry entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(servlet.getRunAs());
...
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
// 加载servletMapping
for (Entry entry :
webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
...
}
总结:完成2)步骤的时候,ServletWrapper与Context的关系就建立起来了
3)论Wrapper与Servlet的关系
// StandardContext.createWrapper()
public Wrapper createWrapper() {
Wrapper wrapper = null;
if (wrapperClass != null) {
try {
wrapper = (Wrapper) wrapperClass.newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("createWrapper", t);
return (null);
}
} else {
wrapper = new StandardWrapper();
}
...
}
StandardWrapper具体维护了Servlet实例
StandardWrapper与Servlet的关系,下面来看下StandardWrapper源码:
1)StandardWrapper是Servlet的包装类,下面是一些主要成员变量
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
/**
* The (single) possibly uninitialized instance of this servlet.
*/
protected volatile Servlet instance = null;
/**
* Flag that indicates if this instance has been initialized
*/
protected volatile boolean instanceInitialized = false;
/**
* The load-on-startup order value (negative value means load on
* first call) for this servlet.
*/
protected int loadOnStartup = -1;
/**
* Mappings associated with the wrapper.
*/
protected final ArrayList mappings = new ArrayList<>();
/**
* The initialization parameters for this servlet, keyed by
* parameter name.
*/
protected HashMap parameters = new HashMap<>();
/**
* The security role references for this servlet, keyed by role name
* used in the servlet. The corresponding value is the role name of
* the web application itself.
*/
protected HashMap references = new HashMap<>();
2)StandardWrapper.load()
如果该servlet配置load-on-startup>=0,则需要调用其load方法,完成Servlet的加载(如果没有配置,则等到用户首次调用Servlet的时候,才会加载)
源码如下:
public synchronized void load() throws ServletException {
// 加载Servlet,创建instance实例,并调用Servlet.init方法
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
// jsp处理
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null)
.registerComponent(instance, jspMonitorON, null);
} catch( Exception ex ) {
log.info("Error registering JSP monitoring with jmx " +
instance);
}
}
}
// loadServlet()
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
...
Servlet servlet;
try {
...
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
// 1.创建对应的servletClass对象
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
...
}
...
// 2.调用servlet.init方法
initServlet(servlet);
// 3.触发load监听
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
...
}
return servlet;
}