先放上一张初始化时的时序图,后面将逐一讲解
1.初始化环境BootStrap
首先从上篇main函数调用bootstrap.init()初始化BootStrap看起:
public void init() throws Exception {
// 初始化commonLoader、catalinaLoader、sharedLoader,关于ClassLoader的后面再单独分析
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 反射方法实例化Catalina,后面初始化Catalina也用了很多反射,不知道意图是什么
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 反射调用setParentClassLoader方法,设置其parentClassLoader为sharedLoader
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);
// 引用Catalina实例
catalinaDaemon = startupInstance;
}
initClassLoaders()
Tomcat自定义了类加载器,打破了jvm的双亲委派机制
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);
}
}
2.初始化加载组件的Catalina
由前面的分析,可知Bootstrap中的load逻辑实际上是交给Catalina去处理的,下面我们对Catalina的初始化过程进行分析
- 首先初始化jmx的环境变量
- 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类,如果我们要改变server.xml的某个属性值(比如优化tomcat线程池),直接查看对应实现类的setXXX方法即可
- 解析conf/server.xml或者server-embed.xml,并且实例化对应的组件并且赋值操作,比如Server、Container、Connector等等
- 为Server设置catalina信息,指定Catalina实例,设置catalina的home、base路径
- 调用StarndServer#init()方法,完成各个组件的初始化,并且由parent组件初始化child组件,一层套一层,这个设计真心牛逼
public void load() {
initDirs();
// 1.初始化jmx的环境变量
initNaming();
// 2.定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
// Digester是利用jdk提供的sax解析功能,将server.xml的配置解析成对应的Bean,并完成注入
// 比如往Server中注入Service
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
// 3.1首先尝试加载conf/server.xml,省略部分代码......
// 3.2如果不存在conf/server.xml,则加载server-embed.xml(该xml在catalina.jar中)
// 省略部分代码......
// 3.3如果还是加载不到xml,则直接return,省略部分代码......
try {
inputSource.setByteStream(inputStream);
// 把Catalina作为一个顶级实例
digester.push(this);
// 3.4解析过程会实例化各个组件,比如Server、Container、Connector等
digester.parse(inputSource);
} catch (SAXParseException spe) {
// 处理异常......
}
} finally {
// 关闭IO流......
}
// 4.给Server设置catalina信息
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 5.调用server的init()方法
try {
getServer().init();
} catch (LifecycleException e) {
// ......
}
// ......
}
注:这里需要明白两点
- 所有调用 init 方法的地方,比如 server.init 或者后面的 service.init 实际都是调用StandardServer、StandardService的 init 方法,因为 Server 和 Service 都是接口,Standard~~是实现类
- Standard~~中的 init 方法实际调用的是父类的 initInternal 方法
3.初始化Server
- 先是调用super.initInternal(),把自己注册到jmx
- 然后注册StringCache和MBeanFactory
- 初始化NamingResources,就是server.xml中指定的GlobalNamingResources
- 调用Service子容器的init方法,让Service组件完成初始化,注意:在同一个Server下面,可能存在多个Service组件
protected void initInternal() throws LifecycleException {
super.initInternal();
// 1.往jmx中注册全局的String cache,尽管这个cache是全局听,但是如果在同一个jvm中存在多个Server,
// 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
onameStringCache = register(new StringCache(), "type=StringCache");
// 2.注册MBeanFactory,用来管理Server
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// 3.往jmx中注册全局的NamingResources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared class loaders
if (getCatalina() != null) {
// 忽略ClassLoader操作
}
// 4.初始化内部的Service
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
- JMX:Java Management Extensions,是通过MBean来监控,管理和远程连接Tomcat的一个框架
4.初始化Service
StandardService和StandardServer都是继承至LifecycleMBeanBase,因此公共的初始化逻辑都是一样的,这里不做过多介绍,我们直接看下initInternal。
- 首先,往jmx中注册StandardService
- 初始化Engine,而Engine初始化过程中会去初始化Realm(权限相关的组件)
- 如果存在Executor线程池,还会进行init操作,这个**Excecutor是tomcat的接口,继承至java.util.concurrent.Executor、**org.apache.catalina.Lifecycle
- 待初始化完Engine后,再初始化Connector连接器,默认有http1.1、ajp连接器,而这个Connector初始化过程,又会对ProtocolHandler进行初始化,开启应用端口的监听,后面会详细分析
protected void initInternal() throws LifecycleException {
// 1.往jmx中注册自己
super.initInternal();
// 2.初始化Engine
if (engine != null) {
engine.init();
}
// 3.存在Executor线程池,则进行初始化,默认是没有的
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 暂时不知道这个MapperListener的作用
mapperListener.init();
// 4.初始化Connector,而Connector又会对ProtocolHandler进行初始化,开启应用端口的监听
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
// 省略部分代码,logger and throw exception
}
}
}
}
5.初始化Engine
protected void initInternal() throws LifecycleException {
getRealm();
super.initInternal();
}
- StandardEngine在init阶段,需要获取Realm,这个Realm是干嘛用的?
- Realm(域)是用于对单个用户进行身份验证的底层安全领域的只读外观,并标识与这些用户相关联的安全角色。
- 域可以在任何容器级别上附加,但是通常只附加到Context,或者更高级别的容器。
public Realm getRealm() {
Realm configured = super.getRealm();
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
- StandardEngine继承至ContainerBase,而ContainerBase重写了initInternal()方法;用于初始化start、stop线程池
- 在start的时候,如果发现有子容器,则会把子容器的start操作放在线程池中进行处理
- 在stop的时候,也会把stop操作放在线程池中处理
// 默认是一个线程
private int startStopThreads = 1;
protected ThreadPoolExecutor startStopExecutor;
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
// 允许core线程超时未获取任务时退出
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
private int getStartStopThreadsInternal() {
int result = getStartStopThreads();
if (result > 0) {
return result;
}
result = Runtime.getRuntime().availableProcessors() + result;
if (result < 1) {
result = 1;
}
return result;
}
在前面的文章中我们介绍了Container组件,StandardEngine作为顶层容器,它的直接子容器是StardandHost,但是对StandardEngine的代码分析,我们并没有发现它会对子容器StardandHost进行初始化操作,StandardEngine不按照套路出牌,将子容器的初始化过程放在start阶段。可能Host、Context、Wrapper这些容器和具体的webapp应用相关联了,初始化过程会更加耗时,因此在start阶段用多线程完成初始化以及start生命周期,否则,像顶层的Server、Service等组件需要等待Host、Context、Wrapper完成初始化才能结束初始化流程,整个初始化过程是具有传递性的。
6.初始化Connector
Connector也是继承至LifecycleMBeanBase,公共的初始化逻辑都是一样的。
Connector容器的主要作用:初始化ProtocolHandler,选择合适的协议
- 我们先来看下Connector的默认配置,大部分属性配置都可以在Connector类中找到,tomcat默认开启了HTTP/1.1、AJP/1.3,其实AJP的用处不大,可以去掉。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
- Connector定义了很多属性,比如port、redirectPort、maxCookieCount、maxPostSize等等,比较有意思的是竟然找不到connectionTimeout的定义,全文搜索后发现使用了属性名映射,估计是为了兼容以前的版本
initInternal
- 注册jmx
- 实例化Coyote适配器,这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的,后续的博客会进行深入分析
- 为ProtocolHander指定CoyoteAdapter用于处理请求
- 初始化ProtocolHander
protected void initInternal() throws LifecycleException {
// 1.注册jmx
super.initInternal();
// 2.初始化Coyote适配器
// 这个适配器是用于Coyote的Request、Response与HttpServlet的Request、Response适配的
adapter = new CoyoteAdapter(this);
// 3.protocolHandler需要指定Adapter用于处理请求
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
// apr支持,忽略部分代码......
// 4.初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
7.初始化protocolHandler
首先,我们来认识下ProtocolHandler,它是一个抽象的协议实现,它不同于JNI这样的Jk协议,它是单线程、基于流的协议。ProtocolHandler是一个Cycote连接器实现的主要接口,而Adapter适配器是由一个CoyoteServlet容器实现的主要接口,定义了处理请求的抽象接口。
ProtocolHandler的子类如下所示,AbstractProtocol(org.apache.coyote)是基本的实现,而NIO默认使用的是Http11NioProtocol
public abstract class AbstractProtocol<S> implements ProtocolHandler,
MBeanRegistration {
public void init() throws Exception {
// 1.完成jmx注册
if (oname == null) {
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// 2.初始化endpoint
endpoint.init();
}
}
8.初始化EndPoint
NioEndpoint初始化过程,最重要的是完成端口和地址的绑定监听工作,即进行最基本的Socket操作,封装Request,Response;即实现TCP/IP协议
// org.apache.tomcat.util.net.NioEndpoint
public class NioEndpoint extends AbstractJsseEndpoint<NioChannel> {
public void bind() throws Exception {
// 实例化ServerSocketChannel,并且绑定端口和地址
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = (getAddress()!=null?new
InetSocketAddress(getAddress(),getPort()):new
InetSocketAddress(getPort()));
// 设置最大连接数,原来是在这里设置的
serverSock.socket().bind(addr,getAcceptCount());
serverSock.configureBlocking(true); //mimic APR behavior
// 初始化acceptor、poller线程的数量
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// 如果有必要的话初始化ssl
initialiseSsl();
// 初始化selector
selectorPool.open();
}
}
总结
至此,初始化流程结束!
默认情况下,Server只有一个Service组件,Service组件先后对Engine、Connector进行初始化。而Engine组件并不会在初始化阶段对子容器进行初始化,Host、Context、Wrapper容器的初始化是在start阶段完成的。tomcat默认会启用HTTP1.1和AJP的Connector连接器,这两种协议默认使用Http11NioProtocol、AJPNioProtocol进行处理。