Tomcat 9 源码分析(1)— 启动与停止
Tomcat 9 源码分析
前言
本文分析的Tomcat版本为Tomcat9.0,该版本与Tomcat8,Tomcat7大体一致,仅在部分地方有所改动,而目前最新的Tomcat10则与之前的版本相差较大。
这是本人第一次阅读主流技术的源码,碍于个人能力原因,无法独自完成源码的阅读,因此本文参考了许多大佬们的文章,在本次学习中主要参考了泰山不老生博主的博客,很感谢能够翻阅到大佬的文章,所学甚多,受益匪浅。
愿编码半生,如一生老友,你也加油。
Tomcat模块
Tomcat的模块如图,接下来便从Tomcat的启动开始分析
启动
Tomcat的启动往往是调用tomcat/bin目录下的startup脚本,而startup.sh将会调用catalina.sh脚本
tomcat/bin/startup.sh
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
if $os400; then
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
主要变量:
- PRGDIR:当前shell脚本所在的路径
- EXECUTABLE:脚本catalina.sh
startup.sh内最后会调用catalina.sh,初始化catalina_base,catalina_home等参数,并传递参数start,catalina.sh脚本则初传递各种参数调用Bootstrap.jar执行其main方法且传递传输start
tomcat/bin/catalina.sh
elif [ "$1" = "start" ] ; then
#省略参数校验脚本
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
由此可知,最后使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是start,在Bootstrap.main()中,当传递参数start时变量command等于start
Bootstarp.main()内先调用init()初始化,利用反射机制,正式载入Catalina类,创建其实例返回并赋值给Bootstrap类的catalinaDaemon对象
org.apche.catalina.startup.Bootstrap.main()
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
// 省略后续操作,在后文分析
}
init()
org.apache.catalina.startup.Bootstrap.init()
public void init() throws Exception {
// 初始化catalina.properties中的commonLoader,serverLoader,sharedLoader
// 如果catalina.properties中的server.loader和sharedLoader为空则以common.Loader替代
// 查找各个loader中的系统变量,其中包含了以","分隔的文件路径(URL、JAR、目录等),称之为repository
// 调用ClassLoaderFactory.createClassLoader()产生classLoader
initClassLoaders();
// 把生成的catalinaLoader设置为当前线程上下文的classloader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 利用反射机制,正式载入Catalina类,创建其实例返回
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
// 调用setParentClassLoader()将sharedLoader设置为父类加载器
// 将catalinaLoader实例赋值给Bootstrap类的catalinaDaemon对象
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
initClassLoaders()将初始化catalina.properties中的commonLoader,serverLoader,sharedLoader,且以commonLoader为父类加载器
org.apache.catalina.startup.Bootstrap.initClassLoaders()
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
//common.loader:tomcat自身和各web服务都会用到的类
//server.loader:tomcat自身会用到的类 如果为空则由common.loader替代
//shared.loader:各web服务会用到的类 如果为空则由common.loader替代
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
这三个类加载器都是UrlClassLoader
org.apache.catalina.startup.ClassLoaderFactory.createClassLoader()
return AccessController.doPrivileged(
(PrivilegedAction<URLClassLoader>) () -> {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
});
至此org.apache.catalina.startup.Bootstrap.init()方法执行完毕,接下来main方法继续执行,在后续代码中根据catalina.sh最后的%ACTION%参数进行不同的操作,而main方法内的setAwait、load、start均为使用反射调用catalinaDaemon(即Catalina类)对应的方法
org.apache.catalina.startup.Bootstrap.main()
public static void main(String args[]) {
// 省略初始化代码,前文已分析
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else if (command.equals("configtest")) {
daemon.load(args);
if (null == daemon.getServer()) {
System.exit(1);
}
System.exit(0);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
// Unwrap the Exception for clearer error reporting
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
setAwait()
当启动tomcat时传递的参数command为start,此时首先会调用Bootstrap.setAwait(),在setAwait方法内会使用反射调用Catalina类中的setAwait方法设置启动flag
org.apache.catalina.startup.Catalina.setAwait()
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
load()
在执行完setWait后便调用load正式初始化载入
org.apache.catalina.startup.Catalina.load()
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Parse main server.xml
// 处理server.xml并初始化实例化部分模块
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
在load方法中调用了parseServerXml()处理server.xml并初始化实例化部分模块
org.apache.catalina.startup.Catalina.parseServerXml()
// server.xml
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// Create and execute our Digester
// 根据inputSource和createStartDigester创建的digester对象所包含的解析规则生成相应对象
// 并调用相应方法将对象之间关联起来
Digester digester = start ? createStartDigester() : createStopDigester();
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
if (generateCode) {
digester.startGeneratingCode();
generateClassHeader(digester, start);
}
digester.parse(inputSource);
}
parseServerXml()核心代码为以上代码,其内的crateStartDigester()中对Server,Service,Executor,Connector, Engine,Host,Context模块进行初始化实例化
org.apache.catalina.startup.Catalina.crateStartDigester()
protected Digester createStartDigester() {
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
// Ignore className on all elements
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
// Ignore Connector attribute used internally but set on Server
List<String> connectorAttrs = new ArrayList<>();
connectorAttrs.add("portOffset");
fakeAttributes.put(Connector.class, connectorAttrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
// 创建Server实例 创建StandardServer对象,设置其对象的属性
// 调用父节点Catalina的setServer方法将Server添加到Catalina中
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
// 为Server添加生命周期监听器
// 当匹配到"Server.Listener"节点,使用ListenerCreateRule解析
// 第一个参数为className,即默认类名,为null。所以Listener不会有默认值类型
// 因为Listener本来就是自定义的
digester.addRule("Server/Listener",
new ListenerCreateRule(null, "className"));
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 创建Service实例
// 创建StandardService实例,调用StandardService实例的set方法设置属性
// 最后调用父节点Server的addService方法,将service添加到Server
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
// 为Service添加生命周期监听器
// 创建生命周期监听器,className上送值为null,所以不存在默认类型
// 比如有"Server/Service/Listener"节点的className指定类型
// 随后调用父节点Service的addLifecycleListener方法,将监听器添加到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");
// Executor
// 创建StandardThreadExecutor实例,并调用父节点Service的addExecutor方法添加到Service
// 通过配置克制,Tomcat桐乡Executor的级别为Service,Catalina默认情况下未配置Executor,即不共享
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");
// 创建Connector实例,并调用父组建Service的addConnector方法添加到Service
// 设置相关属性时,添加设置executor,sslImplementationName,protocol
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addSetProperties("Server/Service/Connector",
new String[]{"executor", "sslImplementationName", "protocol"});
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
// 为Connector添加虚拟主机SSL配置
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new CertificateCreateRule());
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/Certificate", new String[]{"type"});
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"setOpenSslConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"addCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
// 为Connector添加生命周期监听器
digester.addObjectCreate("Server/Service/Connector/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 为Connector添加升级协议(用户支持HTTP/2)
digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol");
// Add RuleSets for nested elements
// 容器组件解析
// 指定Servlet容器相关的各级嵌套子节点的解析规则,而且没类嵌套子节点的规则封装为一个RuleSet
// 包括Server,Service,Engine,Host,Context以及Cluster解析
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// 到这里通过如上Digester机制,实现server.xml解析过程
// 并将相关的Tomcat组件实例化出来,并且维护好了组件的上下文关系
// 后续的init和start也是针对这些实例化后的组件展开
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
return digester;
}
在crateStartDigester方法的最后执行了容器组件解析
// 容器组件解析
// 指定Servlet容器相关的各级嵌套子节点的解析规则,而且没类嵌套子节点的规则封装为一个RuleSet
// 包括Server,Service,Engine,Host,Context以及Cluster解析
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
org.apache.catalina.startup.EngineRuleSet.addRuleInstances()
public void addRuleInstances(Digester digester) {
// 创建实例,并通过setContainer添加到Service中
// 为Engine添加一个生命周期的监听器EngineConfig,这个监听器是代码写死,不是通过server.xml配置的
// 为Engine添加集群配置
// 为Engine添加生命周期监听器,这里的监听器是server.xml文件中指定的
// 为Engine添加Value,具体的拦截器由className属性指定,不指定默认值
digester.addObjectCreate(prefix + "Engine",
"org.apache.catalina.core.StandardEngine",
"className");
digester.addSetProperties(prefix + "Engine");
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
digester.addSetNext(prefix + "Engine",
"setContainer",
"org.apache.catalina.Engine");
//Cluster configuration start
digester.addObjectCreate(prefix + "Engine/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Cluster");
digester.addSetNext(prefix + "Engine/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
digester.addObjectCreate(prefix + "Engine/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Listener");
digester.addSetNext(prefix + "Engine/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));
digester.addObjectCreate(prefix + "Engine/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Valve");
digester.addSetNext(prefix + "Engine/Valve",
"addValve",
"org.apache.catalina.Valve");
}
org.apache.catalina.startup.HostRuleSet.addRuleInstances()
public void addRuleInstances(Digester digester) {
// 创建Host实例,通过addChild()添加到Engine上
// 为Host添加了一个生命周期监听器HostConfig,这个监听器是代码里写死,不是通过server.xml配置的
// 为Host配置集群,所以集群的配置即可在Engine级别,也可以在Host级别
// 为Host添加生命周期监听器,这里的监听器是server.xml文件中配置的
// 为Host添加Value,具体的拦截器由className属性指定,不指定默认值
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
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");
}
org.apache.catalina.startup.ContextRuleSet.addRuleInstances()
public void addRuleInstances(Digester digester) {
// 多数情况下,并不需要再server.xml中配置Context
// 而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建
// 1.创建Context实例,通
// 过server.xml配置Context时,create=true,需要创建Context
// 通过HostConfig创建Context时,create为false,此时仅需解析节点即可
if (create) {
digester.addObjectCreate(prefix + "Context",
"org.apache.catalina.core.StandardContext", "className");
digester.addSetProperties(prefix + "Context");
} else {
digester.addSetProperties(prefix + "Context", new String[]{"path", "docBase"});
}
if (create) {
digester.addRule(prefix + "Context",
new LifecycleListenerRule
("org.apache.catalina.startup.ContextConfig",
"configClass"));
digester.addSetNext(prefix + "Context",
"addChild",
"org.apache.catalina.Container");
}
// 2.为Context添加生命周期监听器
digester.addObjectCreate(prefix + "Context/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Listener");
digester.addSetNext(prefix + "Context/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 3.为Context指定类加载器,默认为org.apache.catalina.loader.WebappLoader
digester.addObjectCreate(prefix + "Context/Loader",
"org.apache.catalina.loader.WebappLoader",
"className");
digester.addSetProperties(prefix + "Context/Loader");
digester.addSetNext(prefix + "Context/Loader",
"setLoader",
"org.apache.catalina.Loader");
// 4.为Context添加会话管理器,默认实现为StandardManager
digester.addObjectCreate(prefix + "Context/Manager",
"org.apache.catalina.session.StandardManager",
"className");
digester.addSetProperties(prefix + "Context/Manager");
digester.addSetNext(prefix + "Context/Manager",
"setManager",
"org.apache.catalina.Manager");
digester.addObjectCreate(prefix + "Context/Manager/Store",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Manager/Store");
digester.addSetNext(prefix + "Context/Manager/Store",
"setStore",
"org.apache.catalina.Store");
digester.addObjectCreate(prefix + "Context/Manager/SessionIdGenerator",
"org.apache.catalina.util.StandardSessionIdGenerator",
"className");
digester.addSetProperties(prefix + "Context/Manager/SessionIdGenerator");
digester.addSetNext(prefix + "Context/Manager/SessionIdGenerator",
"setSessionIdGenerator",
"org.apache.catalina.SessionIdGenerator");
// 5.为Context添加初始化参数,通过该配置,为Context添加初始化参数
digester.addObjectCreate(prefix + "Context/Parameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
digester.addSetProperties(prefix + "Context/Parameter");
digester.addSetNext(prefix + "Context/Parameter",
"addApplicationParameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
// 6.为Context添加安全配置以及web资源配置
digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));
digester.addObjectCreate(prefix + "Context/Resources",
"org.apache.catalina.webresources.StandardRoot",
"className");
digester.addSetProperties(prefix + "Context/Resources");
digester.addSetNext(prefix + "Context/Resources",
"setResources",
"org.apache.catalina.WebResourceRoot");
digester.addObjectCreate(prefix + "Context/Resources/PreResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PreResources");
digester.addSetNext(prefix + "Context/Resources/PreResources",
"addPreResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/JarResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/JarResources");
digester.addSetNext(prefix + "Context/Resources/JarResources",
"addJarResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/PostResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PostResources");
digester.addSetNext(prefix + "Context/Resources/PostResources",
"addPostResources",
"org.apache.catalina.WebResourceSet");
// 7.为Context添加资源连接,默认为ContextResourceLink,用户J2EE命名服务
digester.addObjectCreate(prefix + "Context/ResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink");
digester.addSetProperties(prefix + "Context/ResourceLink");
digester.addRule(prefix + "Context/ResourceLink",
new SetNextNamingRule("addResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink"));
// 8.为Context添加Value
digester.addObjectCreate(prefix + "Context/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Valve");
digester.addSetNext(prefix + "Context/Valve",
"addValve",
"org.apache.catalina.Valve");
// 9.为Context添加守护资源配置
digester.addCallMethod(prefix + "Context/WatchedResource",
"addWatchedResource", 0);
digester.addCallMethod(prefix + "Context/WrapperLifecycle",
"addWrapperLifecycle", 0);
digester.addCallMethod(prefix + "Context/WrapperListener",
"addWrapperListener", 0);
digester.addObjectCreate(prefix + "Context/JarScanner",
"org.apache.tomcat.util.scan.StandardJarScanner",
"className");
digester.addSetProperties(prefix + "Context/JarScanner");
digester.addSetNext(prefix + "Context/JarScanner",
"setJarScanner",
"org.apache.tomcat.JarScanner");
digester.addObjectCreate(prefix + "Context/JarScanner/JarScanFilter",
"org.apache.tomcat.util.scan.StandardJarScanFilter",
"className");
digester.addSetProperties(prefix + "Context/JarScanner/JarScanFilter");
digester.addSetNext(prefix + "Context/JarScanner/JarScanFilter",
"setJarScanFilter",
"org.apache.tomcat.JarScanFilter");
// 10.为Context添加Cookie处理器
digester.addObjectCreate(prefix + "Context/CookieProcessor",
"org.apache.tomcat.util.http.Rfc6265CookieProcessor",
"className");
digester.addSetProperties(prefix + "Context/CookieProcessor");
digester.addSetNext(prefix + "Context/CookieProcessor",
"setCookieProcessor",
"org.apache.tomcat.util.http.CookieProcessor");
}
start()
Bootstrap.main()利用invoke调用的catalina.load()结束后继续往下执行,往后执行的便是start方法
BootStrap.main()节选
daemon.setAwait(true);
daemon.load(args);
daemon.start();
org.apache.catalina.startup.Catalina.start()
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
if (log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
if (generateCode) {
generateLoader();
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}
Catalina的start方法执行步骤如下:
-
验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer
-
启动Server容器,可以参考Tomcat生命周期管理
-
设置关闭勾子(shutdownHook)。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要再退出的时候偶做一些清理工作,比如:内部才能清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递个Runtime的addShutdownHook方法即可
-
容器启动最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令
-
如果Tomcat运行正常且没有收到shutdown命令,是不会想下执行stop方法的,当收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat
await()
Catalina的await方法实际只是代理执行了Server容器的await方法
org.apache.catalina.startup.Catalina
/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
以Server的默认实现StandardServer为例:其await方法执行步骤如下:
- 创建socket连接的服务端对象ServerSocket
- 循环等待接收客户端发出的命令,如果收到的命令与SHUTDOWN匹配(严格匹配大写),那么退出循环等待
org.apache.catalina.core.StandardServer.await()
/**
* Wait until a proper shutdown command is received, then return.
* This keeps the main thread alive - the thread pool listening for http
* connections is daemon threads.
*/
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if (getPortWithOffset() == -2) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if (getPortWithOffset() == -1) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(getPortWithOffset(), 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error(sm.getString("standardServer.awaitSocket.fail", address,
String.valueOf(getPortWithOffset()), String.valueOf(getPort()),
String.valueOf(getPortOffset())), e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn(sm.getString("standardServer.accept.security"), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error(sm.getString("standardServer.accept.error"), e);
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn(sm.getString("standardServer.accept.readError"), e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
至此,Tomcat启动完毕
停止
Tomcat正常停止是调用tomcat/bin/shutdown.sh脚本
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" stop "$@"
主要变量:
- PRGDIR:当前shell脚本所在的路径
- EXECUTABLE:脚本catalina.sh
最后一行 exec "$PRGDIR"/"$EXECUTABLE" stop "$@" 可知道执行了shell脚本catalina.sh,并且传递参数stop,当catalina.sh中接收到stop参数后的执行的脚本分支如下
elif [ "$1" = "stop" ] ; then
#省略参数校验脚本
eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
-Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" stop
由此可知,最后使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是stop。
在Bootstrap.main()中,当传递参数stop时变量command等于stop,此时main方法的执行步骤如下:
步骤一 初始化Bootstrap
步骤二 停止服务
调用Bootstrap的stopServer()停止Tomcat,其实质是用反射调用catalinaDaemon(类型是Catalina)的stopServer()
org.apache.catalina.startup.Bootstrap.stopServer()
public void stopServer(String[] arguments) throws Exception {
Object param[];
Class<?> paramTypes[];
if (arguments == null || arguments.length == 0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
method.invoke(catalinaDaemon, param);
}
org.apache.catalina.startup.Catalina.stopServer()
public void stopServer(String[] arguments) {
if (arguments != null) {
arguments(arguments);
}
Server s = getServer();
if (s == null) {
parseServerXml(false);
if (getServer() == null) {
log.error(sm.getString("catalina.stopError"));
System.exit(1);
}
} else {
// Server object already present. Must be running as a service
try {
s.stop();
s.destroy();
} catch (LifecycleException e) {
log.error(sm.getString("catalina.stopError"), e);
}
return;
}
// Stop the existing server
s = getServer();
if (s.getPortWithOffset() > 0) {
try (Socket socket = new Socket(s.getAddress(), s.getPortWithOffset());
OutputStream stream = socket.getOutputStream()) {
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));
}
stream.flush();
} catch (ConnectException ce) {
log.error(sm.getString("catalina.stopServer.connectException", s.getAddress(),
String.valueOf(s.getPortWithOffset()), String.valueOf(s.getPort()),
String.valueOf(s.getPortOffset())));
log.error(sm.getString("catalina.stopError"), ce);
System.exit(1);
} catch (IOException e) {
log.error(sm.getString("catalina.stopError"), e);
System.exit(1);
}
} else {
log.error(sm.getString("catalina.stopServer"));
System.exit(1);
}
}
Catalina的stopServer方法执行步骤如下:
- 创建Digester解析server.xml(此处只解析
标签),以构造Server容器(此时Server容器的子容器没有被实例化) - 从实例化的Server容器获取Server的socket监听端口地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令,ServerScoket循环等到接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat
stop方()
org.apache.catalina.startup.Catalina.stop()
public void stop() {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
// If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Shut down the server
try {
Server s = getServer();
LifecycleState state = s.getState();
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error(sm.getString("catalina.stopError"), e);
}
}
stop方法的步骤如下:
- 将启动过程中添加的shutdownHook移除,shutdownHook的作用是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以将其remove
- 停止Server容器。有关容器停止的内容可以参考Tomcat生命周期管理
注:
Tomcat中ClassLoader关系,SharedClassLoader为用户自定义类加载器,保证了Tomcat应用的加载隔离
web.xml加载的配置在context.xml中
总结
Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了sockeet的通信
浙公网安备 33010602011771号