在看Tomcat启动流程之前,首先了解下Tomcat的架构,类加载父子关系以及容器的生命周期
在Tomcat中有Server的概念,一个Tomcat运行实例只会包含一个Server实例
Server实例的作用是用来管理多个Service,那么Service又是干嘛用的呢?
从下面的图可以看出,一个Service实例包含多个Connector实例和一个Container实例,Service是用来管理Connector和Container之间的关联关系
Connector负责处理网络连接,Container负责处理请求并且生成响应
每个Connector一般用来处理不同种类的连接
一共有4种容器,Engine,Host,Context,Wrapper,,它们都实现了Container接口
父子关系如下图所示,每种容器的关注点不同
在Tomcat中,除了使用常见的启动类加载器,扩展类加载和应用类加载器之外,另外使用了CommonClassLoader,CatalinaClassLoader和SharedClassLoader以及WebappClassLoader
public static void main(String args[]) {
// 初始化bootstrap
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 {
// 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);
}
}
// 根据入参执行bootstrap的不同方法
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);
}
}
这里做的主要有两件事:
重点看Bootstrap.init做了什么操作
public void init() throws Exception {
// 初始化三个classloader commonLoader catalinaLoader sharedLoader
initClassLoaders();
// 使用catalinaLoader作为当前的类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 使用catalinaloader来加载catalina
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");
// 下面的代码就是调用catalina的setParentClassLoader方法,将sharedLoader设置cataLinaLoader的父加载器
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;
}
private void initClassLoaders() {
try {
// 创建三个类加载器
// 从这里可以看出, commonLoader的父加载器是系统类加载器
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的父加载器是commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// sharedLoader的父加载器是sharedLoader
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
// 根据系统配置来获取当前类加载器负责加载的目录,并且创建类加载器
// 这里的值可能为common.loader,server.loader,shared.loader
// 通过debug,可以知道server.loader和shared.loader的值都为空,因此会使用父classloader
// 即如果没有配置common.loader和server.loader,commonLoader,catalinaLoadewr,sharedLoader都是同一个classLoader
// 这里配置信息的来源有三种可能可能:系统变量catalina.config指定的文件,catalinaBase路径(一般就是tomcat的源码根目录)下的/resource/conf/catalina.properties文件,/org/apache/catalina/startup/catalina.properties
// 这三个配置文件的生效顺序就是上面的顺序,当前一个配置文件存在,就不会读取后面一个配置文件
// common.loder的值为 ${tomcat根目录}/resource/lib ${tomcat根目录}/resource/lib/*.jar
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
// 进行变量替换
value = replace(value);
List<Repository> repositories = new ArrayList<>();
// 使用,来分割上面的value
String[] repositoryPaths = getPaths(value);
if (!Objects.isNull(repositoryPaths) && repositoryPaths.length > 0) {
log.info("classLoaderName:" + name + ", repositoryPaths:{}" + StringUtils.join(Arrays.asList(repositoryPaths), ','));
} else {
log.info("classLoaderName:" + name + ", repositoryPaths is null");
}
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
// 根据路径的后缀名的不同创建不同种类的仓库对象
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
// 将当前需要加载的仓库的地址传给ClassLoaderFactory,由其创建ClassLoader
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
这里主要看指令为start时的执行逻辑
else if (command.equals("start")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
}
Bootstrap.load 会调用Catalina的带参load方法
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;
}
// 这里的catalinaDaemon就是Catalina的的一个实例
// 调用catalina的load方法
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(String args[]) {
try {
// 检验参数
if (arguments(args)) {
load();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
这里的load方法主要完成的工作如下:
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// 创建Digester对象,开始解析conf/server.xml文件
// Create and execute our Digester
// 创建一个digester对象,并且向其中添加解析规则
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);
// 将catalina对象压入到digester的解析栈中,成为栈顶
digester.push(this);
// 开始解析conf/server.xml文件
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();
// Start the new server
// 初始化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("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
这里简单地看下createStartDigester是怎么添加解析规则的
createStartDigester会反复地调用digester对象的addObjectCreate,addSetProperties,addSetNext等方法,来添加解析规则
这里的Digester类是在apache Digester类上做了一层封装
// Configure the actions we will be using
// 当遇到server元素时,创将一个StandardServer的实例,如果元素定义了className属性,那么就会使用className对应的类来实例化一个Serve
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
// 使用Server标签中的属性来设置栈顶元素,也就是Server对象
digester.addSetProperties("Server");
// 将栈顶元素作为参数,传给栈顶元素的下一个元素的指定方法作为入参
// 此时的栈顶元素是Catalina对象
// 即将刚刚解析好的server对象设置到catelian对象中
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
这里使用的Server的实现类是StandardServer,StandardServer的init的主要执行逻辑在initInternal里面,下面看下initInternal
StandardServer的初始化主要是触发包含的Service的初始化
protected void initInternal() throws LifecycleException {
super.initInternal();
// Register global String cache
// Note although the cache is global, if there are multiple Servers
// present in the JVM (may happen when embedding) then the same cache
// will be registered under multiple names
onameStringCache = register(new StringCache(), "type=StringCache");
// Register the MBeanFactory
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory");
// Register the naming resources
globalNamingResources.init();
// Populate the extension validator with JARs from common and shared
// class loaders
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
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 e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
// Initialize our defined Services
// 启动当前server管理的service
for (Service service : services) {
service.init();
}
}
下面再看下StandardService的initInternal方法
主要是初始化管理的Engine和connector
protected void initInternal() throws LifecycleException {
super.initInternal();
// 初始化Engine
if (engine != null) {
engine.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize mapper listener
mapperListener.init();
// Initialize our defined Connectors
// 初始化连接器
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);
}
}
}
}
下面再看StandardEngine的initInternal
StandardEngine主要是设置一个Realm,Realm是用来做身份校验的
@Override
protected void initInternal() throws LifecycleException {
// Ensure that a Realm is present before any attempt is made to start
// one. This will create the default NullRealm if necessary.
getRealm();
super.initInternal();
}
当执行完Bootstrap的start方法后,接着会执行start方法,主要是调用了Catalina的start方法
public void start() throws Exception {
// 判断catalina是否初始化过,如果没有初始化,那么开始初始化
if (catalinaDaemon == null) {
init();
}
// 调用Catalina的start方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
下面看下Catalina的start方法
主要是调用了Server的start方法
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
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;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}
// Register shutdown hook
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) {
await();
stop();
}
}
下面看下StandardServer的startInternal
主要是启动管来的service
protected void startInternal() throws LifecycleException {
// 发送configure_start事件
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
// 设置service的生命周期状态为STARTING
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
// 启动所有的service
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
}
下面看下StandardService的启动
主要是启动了StandardEngine
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
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
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}
可以看到只是简单地打了个日志,然后就会执行父类ContainerBase的startInternal方法,主要做了如下几件事儿:
protected synchronized void startInternal() throws LifecycleException {
// 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();
}
// Start our child containers, if any
// 这里是关键,获取当前容器的所有子容器,并且将子容器的启动任务提交到线程池中执行
// 通过debug,这里只有一个StandardHost
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());
}
// Start the Valves in our pipeline (including the basic), if any
// 启动pipeline
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// 将当前的状态修改为正在启动
setState(LifecycleState.STARTING);
// Start our thread
// 启动一个后台线程,这个线程会执行一些需要后台任务,比如周期性地检查代码是否需要热替换等
threadStart();
}
protected void threadStart() {
// 避免重复创建后台线程
if (thread != null)
return;
// 当属性为正值时,会启动后台线程
// 一些容器的backgroundProcessorDelay为-1,因此不会启动后台线程
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
log.info("threadName:" + threadName);
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
下面看下StandardHost的startInternal
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
// 获取错误报告的阀
String errorValve = getErrorReportValveClass();
// 判断是否需要在pipeline中添加这个阀
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
// 仍然会调用ContainerBase的startInternal,但是此时的子容器个数为0,并且当前的backgroundProcessorDelay为-1,因此不会启动后台线程
super.startInternal();
}
至此,Server,Service,Connector,Engine,Host已经启动了,那么代表我们应用程序的Context和代表Servlet的Wrapper在哪里启动呢?
我们回到之前使用Digester解析server.xml的地方,看看那里是怎么解析Host标签的
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));```
接着看下HostRuleSet的addRuleInstances方法中的这一部分
```java
// 当digester对象开始解析时,会调用LifecycleListenerRule的begin方法
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
下面看下LifecycleListenerRule的begin方法
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
// 获取栈顶元素,此时获取到的就是StandardHost对象
Container c = (Container) digester.peek();
Container p = null;
// 获取此栈顶元素,此时获取到的是StandardEngine对象
Object obj = digester.peek(1);
if (obj instanceof Container) {
p = (Container) obj;
}
String className = null;
// Check the container for the specified attribute
// 判断Host标签是否指定了hostConfigClass属性
if (attributeName != null) {
String value = attributes.getValue(attributeName);
if (value != null)
className = value;
}
// 判断上一级容器,StandardEngine是否指定了hostConfigClass属性
// Check the container's parent for the specified attribute
if (p != null && className == null) {
String configClass =
(String) IntrospectionUtils.getProperty(p, attributeName);
if (configClass != null && configClass.length() > 0) {
className = configClass;
}
}
// 如果都没有指定,那么使用默认的值HostConfig
// Use the default
if (className == null) {
className = listenerClass;
}
// Instantiate a new LifecycleListener implementation object
// 创建一个HostConfig的实例,然后添加到StandardHost的生命周期监听器中
Class<?> clazz = Class.forName(className);
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
// Add this LifecycleListener to our associated component
c.addLifecycleListener(listener);
}
我们上面看到过,在tomcat启动的时候会调用StandardHost的start方法,实际调用的是LifecycleBase的start方法,在该方法中会调用startInternal,而这个方法会被ContainerBase来实现,在ContainerBase的setState方法中,会将当前容器的生命周期设置为STARTING,并且会触发START_EVENT事件
HostConfig本质上也是一个Host的生命周期监听器,因此会监听到这个事件,并且调用自身的start方法
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
setContextClass(((StandardHost) host).getContextClass());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}
public void start() {
if (log.isDebugEnabled())
log.debug(sm.getString("hostConfig.start"));
try {
ObjectName hostON = host.getObjectName();
oname = new ObjectName
(hostON.getDomain() + ":type=Deployer,host=" + host.getName());
Registry.getRegistry(null, null).registerComponent
(this, oname, this.getClass().getName());
} catch (Exception e) {
log.warn(sm.getString("hostConfig.jmx.register", oname), e);
}
if (!host.getAppBaseFile().isDirectory()) {
log.error(sm.getString("hostConfig.appBase", host.getName(),
host.getAppBaseFile().getPath()));
host.setDeployOnStartup(false);
host.setAutoDeploy(false);
}
// 这里会开始部署应用程序
if (host.getDeployOnStartup())
deployApps();
}
protected void deployApps() {
// 部署指定路径下的webapp
// 默认情况下是${tomcat目录}/resource/webapps
File appBase = host.getAppBaseFile();
// 默认情况下是${tomcat目录}/resoure/conf/Catalina/localhost
File configBase = host.getConfigBaseFile();
// 会识别出appBase下需要部署的应用程序
// 默认情况下会识别出docs,manager,examples,ROOT和host-manager这几个应用
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 部署应用程序一共有三种方式:xml描述符,WAR和文件夹
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
// 部署后缀名为.war的文件
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
// 部署文件夹形式的应用程序
deployDirectories(appBase, filteredAppPaths);
}
这里简单地看下deployDirectories
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
// 遍历目录下的文件
for (String file : files) {
if (file.equalsIgnoreCase("META-INF"))
continue;
if (file.equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, file);
if (dir.isDirectory()) {
ContextName cn = new ContextName(file, false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
// 将每个应用的部署封装成一个DeployDirectory任务,然后使用线程池来部署
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
下面看下DeployDirectory具体如何部署应用的
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;
public DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}
@Override
public void run() {
config.deployDirectory(cn, dir);
}
}
可以看到实际上是通过HostConfig的deployDirecotry,那么接着看下HostConfig的deployDirectory
protected void deployDirectory(ContextName cn, File dir) {
long startTime = 0;
// Deploy the application in this directory
if( log.isInfoEnabled() ) {
startTime = System.currentTimeMillis();
log.info(sm.getString("hostConfig.deployDir",
dir.getAbsolutePath()));
}
Context context = null;
// 读取应用目录下的META-INF/context.xml
File xml = new File(dir, Constants.ApplicationContextXml);
// ${tomcat目录}/resource/conf/Catalina/localhost/docs.xml
// configBaseFile代表当前host的配置文件
File xmlCopy =
new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = isCopyXML();
boolean deployThisXML = isDeployThisXML(dir, cn);
try {
if (deployThisXML && xml.exists()) {
synchronized (digesterLock) {
try {
// 解析context.xml文件
// 这里多个解析任务公用同一个digester,并且解析的过程中会修改digester,比如在栈中压入解析后的结果,因此需要上锁
// 并且在解析完毕之后,重置digester
context = (Context) digester.parse(xml);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
xml), e);
context = new FailedContext();
} finally {
digester.reset();
if (context == null) {
context = new FailedContext();
}
}
}
if (copyThisXml == false && context instanceof StandardContext) {
// Host is using default value. Context may override it.
copyThisXml = ((StandardContext) context).getCopyXML();
}
if (copyThisXml) {
Files.copy(xml.toPath(), xmlCopy.toPath());
context.setConfigFile(xmlCopy.toURI().toURL());
} else {
context.setConfigFile(xml.toURI().toURL());
}
} else if (!deployThisXML && xml.exists()) {
// Block deployment as META-INF/context.xml may contain security
// configuration necessary for a secure deployment.
log.error(sm.getString("hostConfig.deployDescriptor.blocked",
cn.getPath(), xml, xmlCopy));
context = new FailedContext();
} else {
context = (Context) Class.forName(contextClass).getConstructor().newInstance();
}
// 这里创建一个ContextConfig,是Context的一个生命周期监听器
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
// 将解析后的context作为host的子容器
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
deployedApp = new DeployedApplication(cn.getName(),
xml.exists() && deployThisXML && copyThisXml);
// Fake re-deploy resource to detect if a WAR is added at a later
// point
deployedApp.redeployResources.put(dir.getAbsolutePath() + ".war",
Long.valueOf(0));
deployedApp.redeployResources.put(dir.getAbsolutePath(),
Long.valueOf(dir.lastModified()));
if (deployThisXML && xml.exists()) {
if (copyThisXml) {
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(xmlCopy.lastModified()));
} else {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(xml.lastModified()));
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
}
} else {
// Fake re-deploy resource to detect if a context.xml file is
// added at a later point
deployedApp.redeployResources.put(
xmlCopy.getAbsolutePath(),
Long.valueOf(0));
if (!xml.exists()) {
deployedApp.redeployResources.put(
xml.getAbsolutePath(),
Long.valueOf(0));
}
}
addWatchedResources(deployedApp, dir.getAbsolutePath(), context);
// Add the global redeploy resources (which are never deleted) at
// the end so they don't interfere with the deletion process
addGlobalRedeployResources(deployedApp);
}
deployed.put(cn.getName(), deployedApp);
if( log.isInfoEnabled() ) {
log.info(sm.getString("hostConfig.deployDir.finished",
dir.getAbsolutePath(), Long.valueOf(System.currentTimeMillis() - startTime)));
}
}
StandardHost在调用addChild的时候,最终会调用到ContainerBase的addChildInternal方法
在该方法中会启动子容器
private void addChildInternal(Container child) {
if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
// Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
child.start();
}
} catch (LifecycleException e) {
log.error("ContainerBase.addChild: start: ", e);
throw new IllegalStateException("ContainerBase.addChild: start: " + e);
} finally {
fireContainerEvent(ADD_CHILD_EVENT, child);
}
}
最终会调用LifecycleBase的start
在执行start的时候,首先会判断当前的状态是否可以执行start操作
此时Context的状态为new,不能直接执行start操作,所以会首先执行init,然后执行start
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
// 首先执行初始化
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
// 初始化完毕后开始启动
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
接着会调用StandardContextd的startInternal方法,下篇分析Context具体如何部署应用的时候再分析