Bootstrap
org.apache.catalina.startup.Bootstrap类是Tomcat的入口,当我们想在IDE中运行Tomcat进行调试,可以找到这个类直接运行main方法。
我们看下Bootstrap的main方法
public static void main(String args[]) {
if (daemon == null) {
// 实例化Bootstrap对象
Bootstrap bootstrap = new Bootstrap();
try {
// 调用init进行Tomcat初始化,这节主要讲这个
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
String command = "start";
// args数组为空,所以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);
// Tomcat启动的方法,后面在讲start
daemon.load(args);
daemon.start();
} 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 bootstrap = new Bootstrap();
/*
* 初始化过程
*/
bootstrap.init();
daemon = bootstrap;
daemon.setAwait(true);
daemon.load(args);
/*
* 启动过程
*/
daemon.start();
上节已经把Tomcat初始化讲完,也就是init()、load()方法,这节讲start()方法,这个过程涉及到的流程比较多,相对上一节比较复杂一点。
setAwait()方法
/*
* 实际上是反射调用了Catalina.setAwait方法
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
org.apache.catalina.startup.Catalina
public void setAwait(boolean b) {
await = b;
}
所以Bootstrap.setAwait方法,实际上调用的是Catalina.setAwait方法,标识await为true。这个标识作用是阻塞主线程,阻塞的方式是开启一个ServerSocket监听关闭端口,直到ServerSocket接受到SHUTDOWN命令,才结束主线程。后面分析。
下面我们开始看下Tomcat启动过程,也就是Boostrap.start
Boostrap
/*
* 实际上调用的是Catalina的start方法
*/
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina
Catalina.start()
public void start() {
/*
* 如果Server实例为空,那么调用load()
*/
if (getServer() == null) {
load();
}
/*
* 如果到达这里Server实例还为空,那么直接打印异常信息,return
*/
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
// 调用Server的start方法,启动Server
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
// 如果Server.start()启动失败,则调用Server.destroy()销毁组件
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
// 注册一个SHUTDOWN钩子函数,用于Tomcat关闭后清理资源
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);
}
}
// 如果await=true,那么调用 await(),阻塞主线程
if (await) {
await();
stop();
}
}
简单总结下Catalina的start方法,主要为三点:
- 判断Server实例是否为空,如果为空那么调用load()初始化组件
- 如果Server实例不为空,那么调用Server.start()方法启动组件,我们上一节说过组件的初始化时由父组件驱动子组件进行初始化的,那么启动也是一样的,由父组件驱动子组件进行启动
- 如果await为true。那么它会调用await()方法进行阻塞当前主线程
下面我们先看await()方法是如何阻塞当前主线程的,在看Server.start()去启动各个组件
Catalina:
/*
* 实际上调用的是Server.await()
*/
public void await() {
getServer().await();
}
Server:
public void await() {
// port = -2,那么直接return,不阻塞当前线程了。这个port怎么来,后面会说
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
/*
* 如果port = -1,则进去循环,则Tomcat只能调用Thread.interrupt()来关闭
*/
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
/*
* 如果port != -1 && port != -2,则会到达这里
*/
try {
// 开启一个ServerSocket,监听了默认8085端口
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;
}
// Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
/*
* 接收Socket连接,如果没有连接,则一直阻塞
* 所以它是靠这种方式进行线程阻塞的。
*/
socket = serverSocket.accept();
// 设置Socket超时时间为10s
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// Read a set of characters from the socket
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("StandardServer.await: read: ", e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// 如果读取到的字符串是关闭字符串,默认是SHUTDOWN,也可以设置的,后面说
boolean match = command.toString().equals(shutdown);
if (match) {
// 匹配成功,则直接break跳出循环,结束该线程
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
}
} finally {
// 后面进行资源回收
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
上面说的port和关闭字符串都可以在server.xml进行设置
上面的port:关闭端口,shutdown:关闭字符串
大家如果感兴趣,可以手动测试一下,本机启动Tomcat,然后启动成功后,使用Telnet工具连接8005端口,然后发送SHUTDOWN,然后观察Tomcat的状态即可。
下面接着来说Server.start方法,开始进入Tomcat组件启动的源码分析
因为StandardServer继承的是LifecycleMBeanBase,所以实际上调用的是startInternal,不懂的可以去看我之前写的Tomcat生命周期Lifecycle
StandardServer
protected void startInternal() throws LifecycleException {
/*
* 给Server的监听器发送CONFIGURE_START_EVENT事件
* 这些监听器主要可以看server.xml文件中server标签的下一级listener都是
* Server的监听器
*/
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
/*
* 设置Server状态为LifecycleState.STARTING
* 并且给监听器发送各种事件,就不深究了
*
*/
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
// 遍历Service,调用start方法,默认只有一个
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
紧接着我们看下Service的start方法,实现类为StandardService,因为它也继承了LifecycleMBeanBase,所以实际上调用的是StandardService的startInternal方法
StandardService
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
// 设置Service的state为LifecycleState.STARTING
setState(LifecycleState.STARTING);
// Start our defined Container first
// 调用Engine的start方法
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
// 如果有配置自定义的Executor,则调用它的start方法
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
/*
* 循环调用Connector.start()
* 默认有两个,Tomcat初始化的时候说过,
* 1个是处理Http请求的Connector
* 1个是处理AJP请求的Connector。当然这里以Http请求为主
*/
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);
}
}
}
}
简单总结下Service的start
- 调用了Engine的start方法
- 如果有自定义Executor,那么循环遍历Executor集合,依次调用它的start方法
- 循环调用Connector的start方法。默认有2个Connector,1个是处理Http请求的Connector,1个是处理AJP请求的Connector。当然这里以Http请求为主
首先看下Engine的start方法,实现类是StandardEngine,它继承了ContainerBase,所以调用了startInternal方法
StandardEngine
StandardEngine:
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// 调用父类ContainerBase的startInternal()
super.startInternal();
}
ContainerBase
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();
}
/*
* 首先获取子容器,我们回想一下第一节讲的Tomcat架构
* Engine是顶层容器
* Host是Engine的子容器
* Context是Host的子容器
* 因为这里是Engine,所以这里获取的Host容器
*/
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
// 往startStopExecutor提交一个Task,这个startStopExecutor在上一节有说过。默认线程数只有1
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
// 遍历futrue,调用get()阻塞,直到子容器启动完毕
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
// 设置当前Engine的start为LifecycleState.STARTING
setState(LifecycleState.STARTING);
// 开启一个线程,这个先不说,这个跟session机制有关
threadStart();
}
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
// 实际上调用的是子容器的start方法
child.start();
return null;
}
}
紧接着我们看下Host的start方法,实现类是StandardHost,它继承了ContainerBase,所以调用了startInternal方法
StandardHost
StandardHost:
protected synchronized void startInternal() throws LifecycleException {
// errorValve默认使用org.apache.catalina.valves.ErrorReportValve
String errorValve = getErrorReportValveClass();
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
// 如果所有的阀门中已经存在这个实例,则不进行处理,否则添加到 Pipeline 中
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
// 如果未找到则添加到 Pipeline 中,注意是添加到 basic valve 的前面
// 默认情况下,first valve 是 AccessLogValve,basic 是 StandardHostValve
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方法,上面分析过了,它调用了子容器的start方法
* 因为在server.xml中 host标签下没有Context标签,所以Host目前是没有子容器。
* 那是不是说启动就到此结束了呢?后面继续说
*/
super.startInternal();
}
StandardHost Pipeline 包含的 Valve 组件:
- basic:org.apache.catalina.core.StandardHostValve
- first:org.apache.catalina.valves.AccessLogValve
这个Value的执行时机,是在接受到请求,并且准备调用Servlet前执行
需要注意的是,在往 Pipeline 中添加 Valve 阀门时,是添加到 first 后面,basic 前面
由上面的代码可知,在 start 的时候,StandardHost 并没有做太多的处理,而且在server.xml中,默认情况host标签下时没有context的,所以目前来说,Host它是没有子容器Context的。那么 StandardHost 又是怎么知道它有哪些 child 容器需要启动呢?
tomcat 在这块的逻辑处理有点特殊,使用 HostConfig 加载子容器,而这个 HostConfig 是一个 LifecycleListener,它会处理 start、stop 事件通知,并且会在线程池中启动、停止 Context 容器,接下来看下 HostConfig 是如何工作的
HostConfig
我们看下HostConfig的lifecycleEvent方法
public void lifecycleEvent(LifecycleEvent event) {
// (省略若干判断代码)
// 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 (host.getDeployOnStartup())
deployApps();
}
protected void deployApps() {
// 这里获取部署文件夹路径。默认是webapp文件夹路径
File appBase = host.getAppBaseFile();
File configBase = host.getConfigBaseFile();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);
}
deployWARs:根据war包来部署项目
deployDirectories:根据文件夹来部署项目
这里我们只看deployDirectories
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
// 获取startStopExecutor
ExecutorService es = host.getStartStopExecutor();
List<Future<?>> results = new ArrayList<>();
// 遍历webapp下的文件
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
// 如果是目录,那么才继续部署
if (dir.isDirectory()) {
// 根据目录名来创建一个ContextName
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
// 提交一个DeployDirectory Task到startStopExecutor
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
// 调用future的get方法,阻塞当前线程,直到部署成功
for (Future<?> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
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() {
// 很简单,直接调用了HostConfig.deployDirectory
config.deployDirectory(cn, dir);
}
}
接下来我们看下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);
File xmlCopy =
new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");
DeployedApplication deployedApp;
boolean copyThisXml = isCopyXML();
boolean deployThisXML = isDeployThisXML(dir, cn);
try {
// 如果项目下存在META-INF/context.xml,并且deployThisXML =true
if (deployThisXML && xml.exists()) {
synchronized (digesterLock) {
try {
/*
* 那么这里使用Digester去解析xml,并且生成Context【实现类StandardContext】对象
*/
context = (Context) digester.parse(xml);
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDescriptor.error",
xml), e);
// 解析失败 则创建一个FailedContext
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 {
/*
* 如果项目下不存在META-INF/context.xml,并且deployThisXML = false,
* 则通过反射实例化一个Context【StandardContex】对象
*/
context = (Context) Class.forName(contextClass).getConstructor().newInstance();
}
/*
* host.getConfigClass() = "org.apache.catalina.startup.ContextConfig",
* 这里也通过反射实例化了一个ContextConfig对象,而且它是一个LifecycleListener
* 根据HostConfig和Host的关系,就很明显知道这个对象的作用了
*/
Class<?> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
// 把ContextConfig设置为Context的监听器
context.addLifecycleListener(listener);
// 设置一些Context的属性
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
/*
* 把Context设置为Host的子容器,
* 一旦调用addChild,那么它就会启动子容器。也就是调用Context.start()
* 感兴趣的,可以去看下源码
*/
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
// 省略一些信息的设置
}
}
我们对HostConfig.deployDirectory做下简单的总结,如下
- 判断当前项目下是否存在META/context.xml,存在的话,使用Digester解析context.xml,来生成Context对象
- 如果存在的该xml,那么通过反射实例化来生成Context对象
- 实例化ContextConfig,把ContextConfig设置为Context的监听器,ContextConfig的作用跟HostConfig一样,借助XXConfig帮助容器启动
- 将当前Context设置为Host的子容器,并且启动子容器,也就是调用Context.start()
StandardContext
上面说过调用host.addChild(context);的时候,会启动Context。Context主要是解析项目的web.xml、加载Servlet、Filter等等资源,而且每一个Context对应一个WebappClassLoader,用于资源的隔离【试想一种情况,当一个Tomcat,跑了2个项目,而且2个项目都使用了fastjson这个jar,但是这2个jar的版本不一样,Tomcat如何识别那个jar是对应那个项目的?这里就是用了WebappClassLoader,每一个项目它会对应一个WebappClassLoader,用于加载本项目的jar和class。】,所以Context加载过程比较复杂。
所以首先我们来看下StandardContext这个对象的比较重要的属性,然后在慢慢分析
public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
protected ApplicationContext context:即ServletContext上下文
private InstanceManager instanceManager:根据 class 实例化对象,比如 Listener、Filter、Servlet 实例对象
private List<Object> applicationEventListenersList:SessionListener、ContextListner 等集合
private HashMap<String, ApplicationFilterConfig> filterConfigs:filer 名字与 FilterConfig 的映射关系
private Loader loader:用于加载class等资源
private final ReadWriteLock loaderLock:用于对loader的读写操作
protected Manager manager:Session管理器
private final ReadWriteLock managerLock:用于对manager的读写操作
private HashMap<String, String> servletMappings:url与Servlet名字的映射关系
private HashMap<Integer, ErrorPage> statusPages:错误码与错误页的映射
private JarScanner jarScanner:用于扫描jar包资源
}
这个我们继续看下它的start方法,如下,为了更容器逻辑,会省略一些比较不重要的代码,只留下比较关键的代码,如果想看更多细节的,可以去看下源码。
protected synchronized void startInternal() throws LifecycleException {
// 省略一些判断
/*
* 一个标识符,标识某些阶段调用是否成功
*/
boolean ok = true;
/*
* 1、根据项目名,在Catalina目录下创建一个工作目录
* 比如$CATALINA_HOME\work\Catalina\localhost\examples
*/
postWorkDirectory();
/*
* 2、创建一个StandardRoot,这类对象主要用于获取项目下的class文件、项目引入的第三方jar等资源
*/
setResources(new StandardRoot(this));
/*
* 3、启动StandardRoot,也就是调用StandardRoot的start
*/
resourcesStart();
/*
* 4、创建WebappLoader,上面说过,这个ClassLoader主要用于项目资源隔离
* 用于加载本项目的jar和class
*/
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
/*
* 5、创建一个Cookie处理器,这个先不说
*/
cookieProcessor = new LegacyCookieProcessor();
/*
* 6、把WebappLoader绑定到当前线程
* 也就是调用Thread.currentThread().setContextClassLoader()
*/
ClassLoader oldCCL = bindThread();
try {
if (ok) {
/*
* 7、向监听器发送CONFIGURE_START_EVENT事件,
* 这时候ContextConfig就上场了,等等再分析ContextConfig,
* ContextConfig主要作用就是解析web.xml,加载Servlet、Filter、Listener的定义等等,
* 注意不是加载Servlet、Filter、Listener的对象。
*/
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
/*
* 8、获取Context的子容器也就是Wrapper【Servlet实际上是封装在Wrapper里面】
* 并且启动Wrapper
*/
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
/*
* 9、创建StandardManager,作用主要是对session的管理
*/
Manager contextManager = null;
contextManager = new StandardManager();
setManager(contextManager);
}
if (ok)
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (ok ) {
if (getInstanceManager() == null) {
javax.naming.Context context = null;
if (isUseNaming() && getNamingContextListener() != null) {
context = getNamingContextListener().getEnvContext();
}
Map<String, Map<String, String>> injectionMap = buildInjectionMap(
getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
setInstanceManager(new DefaultInstanceManager(context,
injectionMap, this, this.getClass().getClassLoader()));
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
}
}
if (ok) {
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
}
/*
* 10、为ServletContext设置在web.xml设置的<init-param>的key-value
*/
mergeParameters();
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
/*
* 11、调用ServletContainerInitializer的onStartup方法
*/
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
if (ok) {
/*
* 12、创建各种Listener,比如ServletContextListener、HttpSessionIdListener等等,
* 而且调用ServletContextListener的contextInitialized方法
*/
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if ((manager != null) && (manager instanceof Lifecycle)) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
if (ok) {
/*
* 13、根据Filter的定义来创建Filter对象
* 并且初始化Filter,也就是调用Filter的init方法,
* 并且把创建的Filter放入filterConfigs
*/
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
if (ok) {
/*
* 14、根据在web.xml设置的Servlet的loadOnStartup属性,
* 来依次来创建Servlet,并且调用Servlet的init方法。
* 没有设置loadOnStartup属性的,则是懒加载。
*/
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
/*
* 15、启动有关session管理的线程,这个先不管了
*/
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
} else {
setState(LifecycleState.STARTING);
}
}
上面关于Context的startInternal的分析,关键代码上都有注释,如果想更深入分析,可以点进去看源码,这里就不讲了,下面讲一下ContextConfig。
ContextConfig
上面的Context的startInternal,会触发一个CONFIGURE_START_EVENT事件,自然ContextConfig就能监听的到
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
CONFIGURE_START_EVENT事件的处理方法是configureStart
protected synchronized void configureStart() {
// 省略一些日志打印
/*
* 解析web.xml
*/
webConfig();
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
validateSecurityRoles();
}
// Configure an authenticator if we need one
if (ok) {
authenticatorConfig();
}
// Dump the contents of this pipeline if requested
if (log.isDebugEnabled()) {
log.debug("Pipeline Configuration:");
Pipeline pipeline = context.getPipeline();
Valve valves[] = null;
if (pipeline != null) {
valves = pipeline.getValves();
}
if (valves != null) {
for (int i = 0; i < valves.length; i++) {
log.debug(" " + valves[i].getClass().getName());
}
}
log.debug("======================");
}
// Make our application available if no problems were encountered
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
protected void webConfig() {
Set<WebXml> defaults = new HashSet<>();
defaults.add(getDefaultWebXmlFragment());
// 创建一个WebXml对象,主要是用于存储web.xml文件的数据
WebXml webXml = createWebXml();
InputSource contextWebXml = getContextWebXmlSource();
/*
* 使用webXmlParser对象开始解析web.xml,把web.xml的数据解析到WebXml对象中
*/
if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
ok = false;
}
ServletContext sContext = context.getServletContext();
/*
* 下面都是解析web-fragment.xml,这个是Servlet3.0新特性,暂时先不说了
*/
Map<String,WebXml> fragments = processJarsForWebFragments(webXml);
Set<WebXml> orderedFragments = null;
orderedFragments =
WebXml.orderWebFragments(webXml, fragments, sContext);
// Step 3. Look for ServletContainerInitializer implementations
if (ok) {
/*
* 解析ServletContainerInitializer的实现,
* ServletContainerInitializer使用的SPI机制,
* 需要在META-INF/services/javax.servlet.ServletContainerInitializer
* 然后在文件中写入实现类
*/
processServletContainerInitializers();
}
/*
* 下面解析项目中的注解,
* 1、首先从/WEB-INF/classes中加载注解的类
* 2、然后在从jar中解析,比如有些jar有web-fragment.xml的,那么也会加载
*/
if (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
/*
* 从/WEB-INF/classes中加载注解的类
*/
if (ok) {
WebResource[] webResources =
context.getResources().listResources("/WEB-INF/classes");
for (WebResource webResource : webResources) {
// Skip the META-INF directory from any JARs that have been
// expanded in to WEB-INF/classes (sometimes IDEs do this).
if ("META-INF".equals(webResource.getName())) {
continue;
}
processAnnotationsWebResource(webResource, webXml,
webXml.isMetadataComplete());
}
}
if (ok) {
/*
* 然后在从jar中解析含有注解的类,比如有些jar有web-fragment.xml的,那么也会加载
*/
processAnnotations(
orderedFragments, webXml.isMetadataComplete());
}
// Cache, if used, is no longer required so clear it
javaClassCache.clear();
}
if (!webXml.isMetadataComplete()) {
// Step 6. Merge web-fragment.xml files into the main web.xml
// file.
if (ok) {
ok = webXml.merge(orderedFragments);
}
// Step 7. Apply global defaults
// Have to merge defaults before JSP conversion since defaults
// provide JSP servlet definition.
webXml.merge(defaults);
// Step 8. Convert explicitly mentioned jsps to servlets
if (ok) {
convertJsps(webXml);
}
// Step 9. Apply merged web.xml to Context
if (ok) {
configureContext(webXml);
}
} else {
webXml.merge(defaults);
convertJsps(webXml);
/*
* 把WebXml的数据,封装到Context对象中,比如Wrapper、Filter、Listener、Servlet的映射、Filter的映射等等,由Context来加载对象,并且初始化,后面会讲一下
*/
configureContext(webXml);
}
// Step 9a. Make the merged web.xml available to other
// components.
String mergedWebXml = webXml.toXml();
@SuppressWarnings("deprecation")
String attributeName = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;
sContext.setAttribute(attributeName, mergedWebXml);
if (context.getLogEffectiveWebXml()) {
log.info("web.xml:\n" + mergedWebXml);
}
// Always need to look for static resources
// Step 10. Look for static resources packaged in JARs
if (ok) {
// Spec does not define an order.
// Use ordered JARs followed by remaining JARs
Set<WebXml> resourceJars = new LinkedHashSet<>();
for (WebXml fragment : orderedFragments) {
resourceJars.add(fragment);
}
for (WebXml fragment : fragments.values()) {
if (!resourceJars.contains(fragment)) {
resourceJars.add(fragment);
}
}
processResourceJARs(resourceJars);
}
// Step 11. Apply the ServletContainerInitializer config to the
// context
if (ok) {
/*
* 把ServletContainerInitializer,设置到Context,由Context来加载,并且调用
*/
for (Map.Entry<ServletContainerInitializer,
Set<Class<?>>> entry :
initializerClassMap.entrySet()) {
if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(
entry.getKey(), null);
} else {
context.addServletContainerInitializer(
entry.getKey(), entry.getValue());
}
}
}
}
private void configureContext(WebXml webxml) {
// 设置 Filter 定义
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
// 设置 FilterMapping,即 Filter 的 URL 映射
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
// 往 Context 中添加子容器 Wrapper,即 Servlet
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// 省略若干代码。。。
wrapper.setOverridable(servlet.isOverridable());
context.addChild(wrapper);
}
// ......
}
这里我们可以知道ContextConfig只是解析web.xml、web-fragment.xml,然后得到Wrapper、Filter、Listenter、ServletContainerInitializer、还有Servlet的映射等等的定义,然后把这些定义设置到Context,由Context进行加载对象,并且进行一些对象初始化
整个Tomcat启动就差不多是这样了。