Tomcat源码分析(3)— Bootstrap启动流程
共 48226字,需浏览 97分钟
·
2021-03-02 10:16
继上篇讲解完Tomcat源码分析(2)—整体结构与组件后,笔者接下来将分析下tomat的启动过程,层层地剖开tomcat启动时做了哪些事情、如何加载资源等等。
Bootstrap启动类
在分析tomcat启动过程之前,我们需要先了解它的组件有哪些周期状态,这对于后续学习组件的初始化、启动等流程是非常有帮助的。
实现Lifecycle接口的组件有12种状态:
NEW:刚创建组件实例时处于NEW状态。
INITIALIZING:组件正在初始化。
INITIALIZED:组件顺利完成初始化。
STARTING_PREP:组件处于预启动状态。
STARTING:组件正处于启动中状态。
STARTED:组件已经成功启动。
STOPPING_PREP:组件在预停止状态。
STOPPING:组件正在停止中。
STOPPED:组件已经正常停止。
DESTROYING:组件正在销毁中。
DESTROYED:组件已顺利地销毁。
FAILED:组件在初始化、启动、停止、销毁过程中出现失败则置为失败状态。
public enum LifecycleState {
NEW(false, null),
INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
STARTING(true, Lifecycle.START_EVENT),
STARTED(true, Lifecycle.AFTER_START_EVENT),
STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
STOPPING(false, Lifecycle.STOP_EVENT),
STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
FAILED(false, null);
}
Bootstrap是tomcat的启动入口类,该类将会构造一个类加载器用于加载Catalina内部的类(通过查找catalina.home的server目录下的所有jar包),加载完后便会执行容器的初始化,通过此种方法将Catalina内部类保持在系统类路径下,使应用程序类不可见、保证安全性。
运行Bootstrap类的main(String[])方法就可以启动tomcat实例,其中主要步骤:
创建Bootstrap实例并调用init()方法。
初始化类加载器catalinaLoader并将其设为当前线程的父加载器。
预加载tomcat、javax包等自定义类。
反射构建catalinaDaemon后台实例。
调用catalinaDaemon实例的load()方法。
创建server.xml解析器digester实例。
digester实例解析server.xml文件内容、并创建standardServer实例。
standardServer实例绑定catalina容器、设置catalina所在路径。
初始化standardServer实例及所有组件Service、Connector、Engine等。
调用catalinaDaemon实例的start()方法,启动服务器、监听端口。
判断catalinaDaemon后台实例是否完成初始化,若没有则重新实例化。
反射调用catalinaDaemon的start()方法,依序地启动server、service、engine容器、connector连接器等组件,若启动过程中出现异常则调用destory()方法销毁所有组件。
catalinaDaemon注册钩子函数,保证standardServer、logManager实例在关闭时能被关闭、销毁。
Bootstrap#main()
Bootstrap启动类的main()函数部分代码逻辑:
public final class Bootstrap {
//省略部分代码...
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);
}
}
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);
}
}
}
观察代码发现逻辑是非常简单的,在main方法中创建Bootstrap对象,再调用它的init()方法,然后根据启动时传入的参数调用boostrap对象的load()、start()方法。
Bootstrap#init()
接下来我们看看init()方法的代码逻辑:
public void init() throws Exception {
//1、初始化类加载器
initClassLoaders();
//2、设置当前线程的上下文加载器为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
//3、预先加载tomcat、javax包的自定义类
SecurityClassLoad.securityClassLoad(catalinaLoader);
//4、加载启动类以及调用setParentClassLoad()方法
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
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;
}
它对应这以下序列图中的红色部分:
init()初始化的方法也相对的简单,首先调用initClassLoaders()初始化类加载器,使得tomcat可以加载应用程序类,接着设置当前线程的上下文加载器为CatalinaLoader。
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
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);
}
}
在initClassLoaders()初始化方法中可发现会创建三种类加载器并赋予成员变量,其中catalinaLoader与sharedLoader加载器的父加载器都是commonLoader。
在完成初始化类加载器之后,通过调用SecurityClassLoad.securityClassLoad方法预加载tomcat、javax包下的自定义类,防止出现AccessControlException访问权限异常。
public static void securityClassLoad(ClassLoader loader) throws Exception {
securityClassLoad(loader, true);
}
static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception {
if (requireSecurityManager && System.getSecurityManager() == null) {
return;
}
loadCorePackage(loader);
loadCoyotePackage(loader);
loadLoaderPackage(loader);
loadRealmPackage(loader);
loadServletsPackage(loader);
loadSessionPackage(loader);
loadUtilPackage(loader);
loadJavaxPackage(loader);
loadConnectorPackage(loader);
loadTomcatPackage(loader);
}
随后通过使用catalinaLoader加载器加载org.apache.catalina.startup.Catalina类,通过反射的方式实例化其对象,同时把sharedLoader实例对象作为入参反射调用setParentClassLoader方法,最后把实例化的Catalina实例赋予catalinaDaemon成员变量。
Catalina#load()
load()方法主要完成StardardServer及子组件的初始化,下图是该方法的主要序列流程:
其中Bootstrap.load()方法实际上通过反射调用Catalina的load()方法,而Catalina的load()方法对应于序列图中的红色部分逻辑,代码如下:
public void load() {
if (loaded) {
return;
}
loaded = true;
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
//省略部分代码
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
//忽略部分代码
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
//standardServer初始化
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("Catalina.start", e);
}
}
}
从上面代码中可看到首先创建、初始化server.xml文件解析器Digester实例,使用digester解析配置文件、构建standardServer实例。
接着standardServer实例绑定当前catalina实例,并设置当前catalina的根路径位置,最后调用init()方法完成standardServer的初始化。
在tomcat中的容器或组件中,使用了模板方法的设计模式,子类通过重写LifecycleBase抽象类的模板方法initInternal(),因此在执行getServer().init()代码时会先调用父类的LifecycleBase#init()方法,而实际的初始化逻辑则交由子类完成,以下为LifecycleBase类的init()方法:
@Override
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());
}
}
protected abstract void initInternal() throws LifecycleException;
从代码中可看到init()方法主要完成两件事情:
更新当前standardServer实例生命周期状态为初始化进行中即INITIALIZING,触发生命周期监听器事件。
执行initInternal()方法完成初始化。
更新当前standardServer实例生命周期状态为已完成即INITIALIZED,并触发生命周期监听器事件。
在执行LifecycleBase#initInternal()抽象方法时,将由standerServer#initInternal()的方法完成初始化。
@Override
protected void initInternal() throws LifecycleException {
//1、调用LifecycleMBeanBase#initInternal方法
super.initInternal();
//2、注册全局字符串缓存,如果有多个Server则也会注册多个字符串缓存对象
onameStringCache = register(new StringCache(), "type=StringCache");
//3、注册MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
//4、注册全局命名资源并初始化,该全局命名资源是配置在server.xml中
globalNamingResources.init();
//5、使用catalina的父加载器校验系统JAR包是否包含MANIFEST文件等,此处忽略部分代码
//6、初始化自定义的service
for (Service service : services) {
service.init();
}
}
代码的逻辑主要有以下几大步骤:
注册全局字符串缓存、MBeanFactory实例、全局命名资源实例。
使用catalina的父加载器校验系统JAR包是否包含MANIFEST文件、添加该JAR文件到Manifest资源池。
遍历server实例绑定的service组件并调用init()方法初始化service。
而service组件的init()方法主要做了以下事情:
初始化StandardEngine容器。
创建指定或默认的Realm。
初始化绑定的StandardThreadExecutor线程执行器。
初始化绑定的MapperListener监听器。
更新mapper监听器的生命周期状态为LifecycleState.STARTING。
查找并绑定默认的虚拟主机Host。
绑定该监听器到Engine及子容器。
注册Engine容器到绑定的虚拟主机Host以及上下文Context、Wrapper。
初始化绑定的所有connector连接器。
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (engine != null) {
engine.init();
}
// 初始化关联的Executor
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 初始化mapper监听器
mapperListener.init();
// 初始化所有关联的connector连接器
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
在整个service初始化过程中,connector连接器的初始化尤其重要,以下为connector连接器初始化代码:
创建、初始化Coyote适配器,并绑定APR或HTTP1.1协议处理器。
检查parseBodyMethodsSet是否有值,若没则设置为POST方法。
判断APR的native库是否必须且协议处理器实例是否已创建,若都不满足则抛出异常。
判断APR本地库是否必须且APR协议处理器是否可用,若都不满足则抛出异常。
判断APR协议处理器是否可用且Apr协议处理器是否使用OpenSSL且协议处理器是否AbstractHttp11JsseProtocol类型。
判断是否启用SSL且其类名为空,若满足则使用OpenSSLImplementation类名(OpenSSL与JSSE的配置兼容)。
否则执行步骤6。
初始化协议处理器(该协议处理器实例时在server.xml解析时创建的)。
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// 1、创建并初始化Coyote适配器,并关联至协议处理器中
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
//2、确认parseBodyMethodsSet有默认值,parsetBodyMethods默认值为POST方法
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
//3、判断是否需要APR本地库 AND 判断Apr协议处理器实例是否被创建,若是Apr本地路且没创建Apr协议处理器则抛出异常
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",
getProtocolHandlerClassName()));
}
//4、判断是否需要APR/native库 AND Apr协议处理器是否可用,若是需要Apr本地库且没有Apr协议处理器可用则抛出异常
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",
getProtocolHandlerClassName()));
}
//5、判断Apr协议处理器是否可用 AND Apr协议处理器是否使用OpenSSL AND 协议处理器是否AbstractHttp11JsseProtocol类型
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
//5.1、如果SSL启用了且SSL实现类名为空,则使用OpenSSLImplementation类名
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// 如果APR可用,可以使用OpenSSL,因为OpenSSL与JSSE的配置兼容。
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
//协议处理器执行初始化(在server.xml解析时创建protocolHandler)
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
另外关于connector连接器中不同协议处理器AjpAprProtocol、Http11NioProtocol的初始化流程,笔者将在后续的文章中单独讲解。
Catalina#start()
在Bootstrap类的main()方法中,通过反射执行catalina实例的start()方法,而start()方法主要作用是启动standardServer实例,让服务器能监听端口、接收新的请求。
对应的代码实现逻辑如下:
public void start() {
//判断server是否为空,为空则执行初始化
if (getServer() == null) {
load();
}
//若server为空,说明未能正确配置server.xml或者server初始化时异常
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
//此处省略部分代码
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 (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
//创建ServerSocker并监听端口
await();
//停止standardServer实例
stop();
}
}
观察上述代码块,发现其主要是做了以下事情:
检查server是否完成初始化,若是则调用server.start()方法启动,否则返回。
触发CONFIGURE_START_EVENT状态的事件监听器及更新server状态。
启动全局命名资源组件,该组件的启动逻辑主要是更新自身的生命周期状态。
遍历启动所有嵌套的server组件。
注册CatalinaShutdownHook关闭钩子,使得在关闭JVM时能销毁Catalina实例、日志管理器等组件。
创建关闭tomcat的ServerSocket实例并监听对应端口,等待接收关闭命令。
关闭并销毁standardServer实例及其所有组件,其中start()方法与initInternal()方法一样,也是使用模板方法设计模式,具体交由子类StandardServer实现。
@Override
protected void startInternal() throws LifecycleException {
//触发生命周期状态事件监听器
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
//更新状态
setState(LifecycleState.STARTING);
//启动全局命名资源
globalNamingResources.start();
//遍历启动所有service组件
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
其中最重要的是启动嵌套的Service、Engine容器、Executor执行器、MapperListener监听器、Connector连接器等组件。
设置service状态、启动engine容器。
查找并启动绑定的cluster集群组件。
查找并启动绑定的security realm。
使用线程池异步启动其所有子容器。
启动绑定的Value阀门集合。
启动线程定期检查session是否过期。
启动绑定的Executor请求执行器。
构建任务队列TaskQueue、自定义线程工厂TaskThreadFactory。
根据线程工厂、最小空闲线程、最大线程数、最大空闲时间等参数构建线程池ThreadPoolExecutor。
更新执行器自身的生命周期状态为LifecycleState.STARTING。
启动service组件绑定的mapper监听器。
查找并匹配Engine容器及其子容器绑定的虚拟主机Host。
绑定该监听器到Engine及子容器中。
注册并绑定虚拟主机Host到Mapper映射规则实例中。
注册Engine及其子容器绑定的虚拟主机下的所有上下文Context。
启动绑定的connector连接器、协议处理器protocolHandler等等。
下面是执行start()方法触发START_EVENT事件时,StandardHost虚拟主机的执行序列图:
其代码对应下面的engine容器的启动:protected void startInternal() throws LifecycleException {
//省略部分代码
setState(LifecycleState.STARTING);
// 1、启动engine容器
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
//2、启动Executor执行器
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
//3、启动mapper监听器
mapperListener.start();
synchronized (connectorsLock) {
for (Connector connector: connectors) {
try {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
//启动connector连接器
connector.start();
}
} catch (Exception e) {
log.error(sm.getString("standardService.connector.startFailed",connector), e);
}
}
}
}
上面主要是启动tomcat中各个组件、容器,这时候还需要创建等待关闭tomcat的ServerSocket以及它监听的端口,而await()方法的作用正是创建ServerSocket并等待接收关闭命令的。
@Override
public void await() {
//该处省略部分代码...
//创建ServerSocker并监听端口
try {
awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address+ ":" + port+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// 循环等待有效连接和命令
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// 等待下一个连接
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) {
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) {
break;
}
log.error(sm.getString("standardServer.accept.error"), e);
break;
}
// 从套接字读取一组字符
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;
}
// 若字符是控制字符或者EOF(-1)则终止循环
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// 完成操作后关闭socket
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// 判断命令内容是否SHUTDOWN
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;
// 关闭ServerScoket
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
}
最后总结
tomcat的启动入口是Bootstrap的main方法,在启动时流程步骤较多,但逻辑都相对清晰好理解,另外在代码上使用了模板方法、责任链设计模式,因此读者需要先自行熟悉此两种设计模式会更加好理解上面的讲解及代码。
整体上tomcat的启动首先需要初始化Server、Service、Engine容器、虚拟主机Host、上下文Context、执行器Executor、连接器Connector等组件,接着再按照同样顺序启动组件,当启动成功后便会监听端口,可以对外提供服务了。
到这里已经讲解完tomcat是如何启动的,若上述有错误,欢迎纠正、一起交流。
你的点赞与分享是对我最大的支持
更多推荐内容
↓↓↓
《为自己搭建一个分布式 IM(即时通讯) 系统》