Tomcat之启动流程
Tomcat启动的main方法为Bootstrap,源码如下:
java
//Bootstrap#main
Bootstrap daemon;
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
daemon = bootstrap;
...
daemon.setAwait(true);
daemon.load(args);
daemon.start();
1. Bootstrap#init方法
init方法主要做了两件事:
- 初始化类加载器。
- 反射生成了Catalina实例,并调用了setParentClassLoader方法。
java
//Bootstrap#init方法
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
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;
1.1 初始化类加载器
java
//Bootstrap#initClassLoaders
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
2. Bootstrap#load
通过反射,调用Catalina的load方法(为什么要通过反射?)
java
String methodName = "load";
Method method =catalinaDaemon.getClass().getMethod(methodName, paramTypes);
method.invoke(catalinaDaemon, param);
Catalina的load方法主要分为三步:
- 初始化一些文件路径、文件名命名空间
- 根据文件解析出Server(通过Digester),并且进行实例化
- 调用Server的init方法,Server的是实现为StandardServer。
java
//Catalina#load
initDirs();
initNaming();
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
//...
getServer().init();
StandardServer的init方法(因为继承了LifecycleBase,所以实际会调用initInternal,后面的方法也是同理),主要是调用Service的init方法,Service的实现为StandardService。(当然也会扫描类加载器的资源,这里忽略)
java
//StandardServer#initInternal
for (Service service : services) {
service.init();
}
StandardService的init方法主要做一下事情:
- Engine调用init方法,Engine的是实现为StandardEngine
- MapperListener调用init方法。(并没有做特殊的事情)
- Connector调用init的方法,如果有多个则循环调用。
java
//StandardService#initInternal
engine.init();
mapperListener.init();
for (Connector connector : connectors) {
connector.init();
}
StandardEngine中的init则会调用父类ContainerBase的init,主要是初始化一个启动线程池。
java
//ContainerBase#initInternal
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
Connector的init方法主要是:
- 创建CoyoteAdapter
- 调用ProtocolHandler的init方法(并没有特殊逻辑),内部会调用Endpoint的init方法
java
//Connector#init
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
protocolHandler.init();
Endpoint是一个接口,抽象类为AbstractEndpoint,init方法内会调用bindWithCleanup方法,然然后内部会调用bind抽象方法,不同是实现类处理不一样。以NioEndpoint为例:主要是创建ServerSocketChannel,然后绑定端口。
java
//以NioEndpoint#bind
public void bind() throws Exception {
initServerSocket();
setStopLatch(new CountDownLatch(1));
// Initialize SSL if needed
initialiseSsl();
}
java
//NioEndpoint#initServerSocket
protected void initServerSocket() throws Exception {
serverSock = ServerSocketChannel.open();
socketProperties.setProperties(serverSock.socket());
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.socket().bind(addr,getAcceptCount());
serverSock.configureBlocking(true);
}
3. Bootstrap#start
通过反射调用Catalina的start方法
java
//Bootstrap#start
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
Catalina的start方法,主要有三个步骤:
- 调用Server的start方法.
- 设置shutdown hook。
- 主线程await();
java
//Catalina#start
getServer().start();
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
java
if (await) {
await();
stop();
}
3.1 Server#start
Server的start方法主要是调用Service的start方法,因为继承了LifecycBase,所以内部实现为startInternal方法。下面有些类的分析也是同理。
java
//StandardServer#startInternal
for (Service service : services) {
service.start();
}
Service的start方法,主要是做了以下三件事:
- 调用Engine的start方法
- 调用MapperListener的start方法
- 遍历调用Connector的start方法。
java
//StandardService#startInternal
engine.start();
mapperListener.start();
for (Connector connector: connectors) {
connector.start();
}
3.1.1 容器start方法
Engine的start方法,主要是调用父类的ContainerBase的startInternal方法,主要做了以下事情:
- 找到子容器,然后添加到线程中执行,StartChild内部的run方法就是调用child.start()方法。我们知道容器级别为:Engine->Host->Context->Wrapper,所以会产生递归调用,遍历着把子容器都给start一下。然后调用Future.get方法,等待子容器处理完start方法。
- 调用Pipeline(实现类为StandardPipeline)的start方法。
- 启动后台线程。后台线程会定时扫描,实现热加载和热部署,是一个单线程。虽然方法是在ContainerBase里,但是启动后台线程是要有条件的,必须backgroundProcessorDelay大于0,该参数默认为-1,只有StandardEngine中设置成了10
java
//ContainerBase#startInternal
//1. 找到子容器,然后调用start方法
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
for (Future<Void> result : results) {
result.get();
}
//2.
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
//3. 启动容器后台线程,做热加载和热更新
threadStart();
Host(实现类为StandardHost)中的start没有特殊的,会调用父类ContainerBase的startInternal方法,会继续找子容器调用start方法
在Context容器中,start方法的执行逻辑会比较复杂:
创建一个Loader,内部会包含一个类加载器,并且调用start方法。
java
//StandardContext#startInternal
WebappLoader webappLoader = new WebappLoader();
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
找子容器(Wrapper),然后调用start方法。
java
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
调用ServletContainerInitializer接口的onStartup方法
java
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :initializers.entrySet()) {
entry.getKey().onStartup(entry.getValue(),getServletContext());
}
找子容器需要在启动的时候load的,子容器Wrapper内部加载Servlet,并调用Servlet#init方法。这种就是在启动的时候加载,默认情况下,是延迟加载。
java
loadOnStartup(findChildren());
Wrapper容器中,并没有特殊的地方。
3.1.2 Connector#start
Connector内部会调用ProtocolHandler的start方法
java
//Connector#start
protocolHandler.start();
ProtocolHandler是一个接口,抽象类为AbstractProtocol,内部是实现主要逻辑:
- Endpoint调用start方法
- 启动AsyncTimeout 超时线程。
java
//AbstractProtocol#start
endpoint.start();
asyncTimeout = new AsyncTimeout();
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
timeoutThread.start();
Endpoint是一个接口,抽象类为AbstractEndpoint,内部会调用startInternal,由不同的实现类实现。以NioEndpoint为例。主要逻辑如下:
- 创建线程池
- 初始化连接数限制。
- 启动Poller线程,不同实现可能逻辑有点差异
- 启动Acceptor线程。
java
//NioEndpoint#startInternal
//1. 创建线程池
createExecutor();
//2. 初始化连接限制
initializeConnectionLatch();
//3. 启动Poller线程
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-Poller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
//4. 启动Acceptor线程
startAcceptorThread();
java
//NioEndpoint#createExecutor
public void createExecutor() {
internalExecutor = true;
TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
3.2 shutdown hook
runnable接口的run方法,主要是调用Catalina的stop
java
//Catalina$CatalinaShutdownHook#run
Catalina.this.stop();
Catalina#stop方法,主要是调用Server的stop和destroy
java
//Catalina#stop
Server s = getServer();
LifecycleState state = s.getState();
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();
s.destroy();
}
3.3 主线程await
内部实际会上调用Server的await方法。默认的情况情况下,port是有值的,会创建一个ServerSocket,然后主线程阻塞在accept()方法中。
java
//StandardServer#await
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
Socket socket = serverSocket.accept();
}
在Catalina中的stopServer方法中(可以通过命令stop,会调用该方法),除了会对Server调用Stop之外,如果port大于0,还会创建一个Socket,然后发送SHUTDOWN字符串。
java
//Catalina#stopServer
Socket socket = new Socket(s.getAddress(), s.getPort();
OutputStream stream = socket.getOutputStream();
String shutdown = s.getShutdown();
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));
}
stream.flush();
4. 参考资料
- Tomcat源码8.5.x分支