Tomcat源码解析:初始化
文章目录
Bootstrap
启动Tomcat
只需要执行Bootstrap
中的#main
方法即可。
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();//实例化Catalina,并设置classloader
//...
if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);//解析xml,实例化组件,并执行Lifecycle的init阶段
daemon.start();//start阶段
}
}
init
初始化classloader ,实例化Catalina
类,并且反射调用setParentClassLoader将sharedLoader设置进去。
public void init() throws Exception {
//初始化commonLoader catalinaLoader sharedLoader
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
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;
}
load
load方法会反射调用Catalina#load
方法进行解析xml,实例化组件,并执行Lifecycle的init阶段。
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
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(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);
}
Catalina
load
public void load() {
//...
initDirs();
// Before digester - it may be needed
initNaming();
// Set configuration source //获取server.xml的存放位置
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
// Create and execute our Digester
//将需要的class,属性及需要执行的方法封装存入Digester
// 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
Digester digester = createStartDigester();
//解析server.xml
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);// 把Catalina作为一个*实例
digester.parse(inputSource);//解析xml,会实例化各个组件,比如Server、Container、Connector等
} catch (Exception e) {
//...
}
// 给Server设置catalina信息
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();//初始化System.out和err
// Start the new server
// 调用Lifecycle的init阶段
//...
getServer().init();
}
- 首先找到server.xml
- 使用Digester来解析xml,并按执行提前配置好的规则
- 调用Lifecycle的init阶段
因为Server、Container、Connector
等的实例化是在解析xml时完成的,所以需要先了解一下Digester
是什么。
Digester
Digester是一款用于将XML转换成Java对象的事件驱动型工具,是对SAX的高层次封装。它提供一套对象栈机制用于构建Java对象,这是因为XML是分层结构,所以创建的Java对象也应该是分层级的树状结构,而且还要根据XML内容组织各层级Java对象的内部结构以及设置相关属性。
这里主要对Digester在Tomcat中使用的处理规则进行说明。
规则类 | 描述 |
---|---|
ObjectCreateRule | 当begin()方法调用时,会将指定的java class实例化,并将其放入对象栈。具体的java类可由该规则的构造方法传入,也可以通过当前处理XML节点的某个属性指定,属性名称通过构造方法传入。当end()方法调用时,创建的对象将从栈中取出 |
SetPropertiesRule | 当begin()方法调用时,Digester使用setter方法将XML节点属性值设置到栈顶的对象中。 |
SetNextRule | 当end()方法调用时,Digester会栈顶对象实例作为属性设置到父class(即栈顶对象后一个对象,index为1)中 |
这里拿ObjectCreateRule来做个示范
ObjectCreateRule
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
String realClassName = getRealClassName(attributes);
//...
// Instantiate the new object and push it on the context stack
Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
Object instance = clazz.getConstructor().newInstance();
digester.push(instance);
}
public void end(String namespace, String name) throws Exception {
Object top = digester.pop();
//...
}
而层级结构在createStartDigester()
配置,这里同时会初始化监听器,包括定义在server.xml
里的和createStartDigester()
里的。
Catalina#createStartDigester
protected Digester createStartDigester() {
Digester digester = new Digester();
//...
//创建server实例,默认实现为StandardServer
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
//...
}
- 首先实例化StandardServer。
- 然后将xml里配置的Server的属性,如port和shutdown等调用set方法设置到server
<Server port="8095" shutdown="SHUTDOWN">
...
</Server>
- 最后调用setServer将当前StandardServer设置到Catalina中。
需要注意的是:EngineConfig
,HostConfig
,ContextConfig
都是在这里作为生命周期监听器实例化并设置到对应的节点中去的。
这里放一张xml解析完成后的图,方便理解
init
解析完成后,会进行初始化,这里会调用LifecycleBase#init
方法,从server开始调用init方法,进行初始化。
getServer().init();
LifecycleBase#init
这里先由LifecycleBase
改变当前状态值为INITIALIZING
,并发出事件通知,即执行当前事件的监听器,然后执行各个节点的初始化方法,最后在将状态设置为初始化完成INITIALIZED
,并发出事件通知。这里的server,即StandardServer
只会改变自己的状态值,而不会改变子容器service的状态值。而且考虑到并发修改状态值的问题,init方法使用了synchronized锁,并用volatile修饰state值。
public final synchronized void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
//将状态设置为初始化中,并执行监听器
setStateInternal(LifecycleState.INITIALIZING, null, false);
//真正的初始化方法
initInternal();
//将状态设置为初始化完成,并执行监听器
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
然后会执行server的初始化方法。
Server初始化
StadardServer#initInternal
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize utility executor
reconfigureUtilityExecutor(getUtilityThreadsInternal(utilityThreads));
register(utilityExecutor, "type=UtilityExecutor");
// 往jmx中注册全局的String cache,尽管这个cache是全局听,但是如果在同一个jvm中存在多个Server,
// 那么则会注册多个不同名字的StringCache,这种情况在内嵌的tomcat中可能会出现
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory // 注册MBeanFactory,用来管理Server
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources // 初始化NamingResources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// lass loaders
if (getCatalina() != null) {
//...
}
// Initialize our defined Services // 初始化内部的Service
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
- 先是将自己注册到jmx
- 然后注册StringCache和MBeanFactory
- 初始化/NamingResources。
- 调用service的init
Service初始化
service的初始化也调用了lifecycleBase的init的方法,与server一样,这里主要关注本身的service方法。
StandardService#initInternal
protected void initInternal() throws LifecycleException {
super.initInternal();
// 初始化Engine
if (engine != null) {
engine.init();
}
// Initialize any Executors // 存在Executor线程池,则进行初始化,默认是没有的
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors // 初始化Connector
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
}
- 注册jms
- 初始化Engine。
- 初始化线程池
- 初始化Connector
Engine初始化
StandardEgine#initInternal
protected void initInternal() throws LifecycleException {
getRealm();
super.initInternal();
}
public Realm getRealm() {
Realm configured = super.getRealm();
// If no set realm has been called - default to NullRealm
// This can be overridden at engine, context and host level
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
- 如果当前Engine没有显示配置Relam,则会获取默认的Relam实现,即NullRealm。这里在这个版本的
server.xml
里配置了LockOutRealm
,用于防止通过蛮力攻击猜测用户密码的尝试。
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
- 调用父类
ContainerBase
的initInternal
。
ContainerBase#initInternal
做了两个操作,首先创建start、stop线程池,而这个线程池默认情况下,线程数是1,然后注册jms。代码如下:
private int startStopThreads = 1;
protected ExecutorService startStopExecutor;
protected void initInternal() throws LifecycleException {
reconfigureStartStopExecutor(getStartStopThreads());
super.initInternal();
}
private void reconfigureStartStopExecutor(int threads) {
if (threads == 1) {
// Use a fake executor
if (!(startStopExecutor instanceof InlineExecutorService)) {
startStopExecutor = new InlineExecutorService();
}
} else {
// Delegate utility execution to the Service
Server server = Container.getService(this).getServer();
server.setUtilityThreads(threads);
startStopExecutor = server.getUtilityExecutor();
}
}
public int getStartStopThreads() {
return startStopThreads;
}
那么这个startStopExecutor
的作用是什么呢?这部分代码在当前类的startInternal
和stopInternal
方法中。
- 在start时,如果有子容器,将子容器的start方法放到线程池来操作
- 在stop时,同上。
到这里Engine的初始化就结束了,而他的子容器Host则是在start阶段才进行初始化。
Connector初始化
Connector的初始化和上面的步骤也一样,这里默认会对HTTP/1.1和AJP/1.3做初始化。这个配置来源于server.xml
中。
<Connector port="8091" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
Connector#initInternal
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
// 初始化Coyote适配器
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
if (service != null) {
protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());
}
//...
// 初始化ProtocolHandler,这个init不是Lifecycle定义的init,而是ProtocolHandler接口的init
try {
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
-
实例化CoyoteAdapter,并将其作为protocolHandler的适配器。
-
执行protocolHandler的初始化,这里的初始化并不是生命周期的init阶段,而是protocolHandler的init方法,而protocolHandler的配置是再Connector的构造方法里。
private int maxCookieCount = 200;
protected int maxParameterCount = 10000;
protected int maxPostSize = 2 * 1024 * 1024;
protected int maxSavePostSize = 4 * 1024;
public Connector(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";
} else {
protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";
}
} else {
protocolHandlerClassName = protocol;
}
// Instantiate protocol handler
ProtocolHandler p = null;
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
// Default for Connector depends on this system property
setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));
}
这里主要对Http11NioProtocol
做分析,可以看到在实例化Connector时会初始化一些连接参数,但却没有connectTimeout
,那么connectTimeout
是在哪里被设置进去呢?,从Connector的构造函数中,看到实例化了一个默认的 Http11NioProtocol
,而实例化Http11NioProtocol
时,在其父类的构造方法里发现,会设置一个connectTimeout的默认值,默认值是60000,如果在server.xml
里配置了connectTimeout,会在解析xml时覆盖这个默认值。
public Http11NioProtocol() {
super(new NioEndpoint());
}
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S,?> endpoint) {
super(endpoint);
}
public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
ProtocolHandler初始化
Http11NioProtocol
的init方法的主要实现是在AbstractProtocol
里,首先完成jmx的注册,然后对NioEndpoint进行初始化。
AbstractProtocol#init
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
logPortOffset();
}
if (oname == null) {
// Component not pre-registered so register it
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);
// 初始化endpoint
endpoint.init();
}
Endpoint初始化
Endpoint的初始化主要是完成对端口的绑定和监听。
AbstractEndpoint.java
public final void init() throws Exception {
if (bindOnInit) {
bindWithCleanup();//绑定端口,连接socket
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
//...
}
}
private void bindWithCleanup() throws Exception {
try {
bind();//这里是重点
} catch (Throwable t) {
// ...
}
}
而端口的绑定是在NioEndpoint里。
NioEndpoint#bind
public void bind() throws Exception {
initServerSocket();
// Initialize thread count defaults for acceptor, poller // 初始化acceptor、poller线程的数量
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed // 如果有必要的话初始化ssl
initialiseSsl();
// 初始化selector
selectorPool.open(getName());
}
protected void initServerSocket() throws Exception {
if (!getUseInheritedChannel()) {// 实例化ServerSocketChannel,并且绑定端口和地址
serverSock = ServerSocketChannel.open();//创建socket
socketProperties.setProperties(serverSock.socket());//将超时时间等设置到socket中
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());//设置最大连接数和地址
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
serverSock.configureBlocking(true); //mimic APR behavior
}
- 首先创建socket,然后将连接属性设置到socket中。
- 初始化acceptor、poller线程的数量。
- 初始化selector,并启动BlockPoller线程,这个poller暂时不知道是干啥的。
至此,整个初始化过程便告一段落。整个初始化过程,由parent组件控制child组件的初始化,一层层往下传递,直到最后全部初始化。
参考:https://blog.csdn.net/Dwade_mia/article/details/79051444
《Tomcat架构解析》刘光瑞
上一篇: Python从入门到实践习题答案(第十五章 生成数据)
下一篇: Python:关于爬虫(1)
推荐阅读
-
Spring源码分析之IoC容器初始化
-
Mybaits 源码解析 (八)----- 全网最详细,没有之一:结果集 ResultSet 自动映射成实体类对象(上篇)
-
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析
-
asp.net abp模块化开发之通用树2:设计思路及源码解析
-
Mybaits 源码解析 (六)----- 全网最详细:Select 语句的执行过程分析(上篇)(Mapper方法是如何调用到XML中的SQL的?)
-
Java中的容器(集合)之ArrayList源码解析
-
vuex 源码解析(四) mutation 详解
-
严蔚敏数据结构源码及习题解析
-
死磕 java同步系列之CyclicBarrier源码解析——有图有真相
-
spring源码深度解析— IOC 之 默认标签解析(上)