在上一章我们讲到了,tomcat是怎么用生命周期来统一管理它底下这么多容器的各种操作,但是关于tomcat具体的启动流程我们只是在文章的末尾,简单的提了一句。在这这一章我会详细的讲解一下,tomcat的启动流程和配置文件是怎么样 在什么时候生效的。
我们看一下tomcat的启动代码 `
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
// init()完成之前不要设置daemon
Bootstrap bootstrap = new Bootstrap();
try {
// 初始化操作 1 初始化tomcat核心类加载器 2 通过反射实例化核心类Catalina.class
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
// 将当前
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
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方法中 主要做了以下两步
- 初始化tomcat中的顶层类加载器(关于tomcat类加载器的问题会在后续篇幅中进行讲解),然后通过反射的方式实例化Catalina类
- 将当前bootstrap对象赋值给deamon属性 方便后续操作
- 根据命令行从顶层容器开始执行对应的start方法
bootstrap.load()
在load方法中会调用我们刚生成的Catalina对象的load方法
standardServer.init()
if (loaded) {
return; // 只能被加载一次
}
loaded = true;
long t1 = System.nanoTime();
initDirs(); // 废弃的方法
// Before digester - it may be needed
initNaming(); // 和JNDI 相关的内容 忽略
// Create and execute our Digester
// 创建并且执行我们的 Digester 对象 Server.xml
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail",
"server-embed.xml"), e);
}
}
}
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
// 解析server.xml 获取对应server service connector属性信息
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 {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
// 设置server对应的Catalina属性
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 启动Server 服务
try {
getServer().init(); // 完成 Server Service Engine Connector等组件的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);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
在Catalina.load中分为一下几个步骤
- 解析server.xml文件 获取对应的配置信息 并设置standardServer到Catalina的server属性中(这一步不详细讲解,因为本人也没详细看),这是所有生命周期方法的起点
- 通过配置的service,遍历执行对应service的init方法
standardServer.init()
从这一步开始就正式进入我们的生命周期了,通过上一章的知识我们知道,init方法本质是一个模板方法,具体的子类实现的是initInternal方法。这一点后面不会再次提到,如果有不清楚生命周期的同学可以看上一章的知识点
super.initInternal();
onameStringCache = register(new StringCache(), "type=StringCache");
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
globalNamingResources.init();
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException | IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// 开始初始化server底下的service
for (Service service : services) {
service.init();
}
- 设置生命周期模板方法工厂
- 设置全局资源命名空间
- 遍历执行server下所有service的init方法
standardService.init()
super.initInternal();
// 初始化engine
if (engine != null) {
engine.init();
}
// 初始化连接池
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// 初始化mapper监听器
mapperListener.init();
// 初始话连接器
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);
}
}
}
}
- 执行engine的init方法
- 初始化连接线程池
- 初始化监听器
- 初始化连接器(连接器这块会在后续专门的章节进行讲解)
到了这里我们已经过了tomcat load方法的流程.但是小伙伴会说为什么这里到了engine就结束了 后面的host context wrapper呢。这也就是我们要讲的下一个方法,Catalina.start(),因为执行流程的思路和load的方法是一致的。所以后面的内容会直接上代码
standardServer.startInternal()
// 发布 configure_start 事件,触发对应的监听器
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING); // 子类中设置状态为 启动中
globalNamingResources.start(); // JNDI 的操作 忽略
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
service.start(); // 分别启动各个service 服务
}
}
standardService.startInternal()
if(log.isInfoEnabled()) {
log.info(sm.getString("standardService.start.name", this.name));
}
setState(LifecycleState.STARTING); // service 组件的状态也更新为 Starting 状态
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start(); // 启动Engine
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start(); // 启动 Executor
}
}
mapperListener.start();
// Start our defined Connectors second
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.startInternal()
// Log our server identification information
if (log.isInfoEnabled()) {
log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo()));
}
// Standard container startup
super.startInternal();
super.startInternal()
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// 异步启动子容器 如果有的话
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// 启动容器中的管道
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// 执行容器中需要后台执行的方法
threadStart();
- 启动对应属性的start方法
- 异步执行子容器的start方法
- 启动管道
- 启动容器异步线程去执行本容器的后台方法
到了这里关于tomcat本身的启动流程就基本完了,剩下关于engine往下的操作,包括tomcat的自定义类加载器,连接器的初始化和启动,管道的初始化和启动,tomcat处理请求的流程。会在后续的文章里面,有专门的章节来进行讲解。
总结
在这一章里,我们详细的看了tomcat的初始化方法和启动方法,并且和生命周期串联在了一起。但是有很多内容,我是简单带过或者直接跳过。这边作者给大家道个歉,因为实力有限,有一下东西我也不敢妄下定论以免误人子弟。如果有兴趣的小伙伴自己阅读了文章之外的代码,或者在我的文章中有理解有误或者用词不当的地方。欢迎与我进行讨论和勘误。