一、启动脚本
当我们在服务启动Tomcat时,都是通过执行startup.sh脚本启动。
在Tomcat的启动脚本startup.sh中,最终会去执行catalina.sh脚本,传递的参数是start。
在catalina.sh脚本中,前面是环境判断和初始化参数,最终根据传递的start来执行上图的代码,最终会调用Tomcat的Bootstrap启动类的main方法,传递的参数是start。
二、源码解析
为了更容易的理解源码,整个系列中Tomcat的运行模式采用BIO的方式。
public static void main(String args[]) {
if (daemon == null) {
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];
}
//脚本传递的是start
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")) {
//加载和启动整个Tomcat
daemon.setAwait(true);
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) {
if (t instanceof InvocationTargetException &&
t.getCause() != null) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(1);
}
}
在启动类的main方法中,创建了一个启动类的实例,然后进行初始化,最后再根据启动命令传递进来的参数,也就是上文中的 start 来执行对应的逻辑,即加载和启动整个Tomcat。
public void init() throws Exception {
//设置容器的路径和脚本中的基本信息
setCatalinaHome();
setCatalinaBase();
//初始化Tomcat的三大类加载器
initClassLoaders();
//该类加载器用于加载这个源码的类
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
//创建一个容器的启动实例,待会用于加载server.xml
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.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;
}
在初始化启动器是,主要是创建三大类加载器(commonLoader、catalinaLoader、sharedLoader)并设置好父子关系,最后再创建一个启动的实例,主要是用来解析server.xml文件。
在Tomcat中一共有四大类加载器,如下图:
类加载器 | 作用 | 父加载器 |
commonLoader(共同类加载器) | 加载$CATALINA_HOME/lib下的类加载器 | 应用类加载器 |
catalinaLoader(容器类加载器) | 加载Tomcat应用服务器的类加载器,可以理解为加载Tomcat源码中的类 | 共同类加载器 |
sharedLoader(共享类加载器) | 加载应用类加载器的共享的类加载器,例如相同版本的mysql驱动等 | 共同类加载器 |
webappLoader(应用类加载) | 加载web应用下的类类加载,每个web应用之间是相互隔离的 | 共享类加载器 |
commonLoader、catalinaLoader、sharedLoader可以在tomcat下的conf/catalina.properties文件中修改。
初始化完成之后,会根据启动参数 start,然后执行daemon.load(args),即加载。
private void load(String[] arguments)
throws Exception {
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;
}
Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
method.invoke(catalinaDaemon, param);
}
加载就是通过反射去调用Catalina的load方法。
public void load() {
//找到脚本中设置的路径
initDirs();
initNaming();
//创建一个解析器
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
//读取server.xml文件
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);
}
}
}
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 {
//解析server.xml文件,把server.xml中的每一个标签都转换成对应的实例对象
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 {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
getServer().setCatalina(this);
initStreams();
try {
//初始化最顶层的组件,会导致Service组件和Connector组件也跟着初始化
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);
}
}
}
在Catalina的load方法中,会找到server.xml文件,然后解析标签并创建出对应的实例对象,最终在调用最顶层的Server组件的init方法,会调用Service组件的初始化,而Service组件的会调用Connector组件的初始化(容器的初始化是懒加载的,即有请求达到时才开始初始化)。
在上面3个组件的初始化中最值得关注的是Connector组件的初始化,因为它会绑定一个端口并且添加对应的协议处理器,从而等待请求。
Connector:
protected void initInternal() throws LifecycleException {
super.initInternal();
//创建一个适配器
adapter = new CoyoteAdapter(this);
//协议处理器在连接器创建时也跟着创建了
protocolHandler.setAdapter(adapter);
if( null == parseBodyMethodsSet ) {
setParseBodyMethods(getParseBodyMethods());
}
//...省略
try {
//协议处理器初始化
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException
(sm.getString
("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
mapperListener.init();
}
Http11Protocol:
public void init() throws Exception {
//...省略
try {
endpoint.init();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.initError",
getName()), ex);
throw ex;
}
}
JIoEndpoint:
public final void init() throws Exception {
testServerCipherSuitesOrderSupport();
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
public void bind() throws Exception {
if (acceptorThreadCount == 0) {
acceptorThreadCount = 1;
}
if (getMaxConnections() == 0) {
setMaxConnections(getMaxThreadsExecutor(true));
}
if (serverSocketFactory == null) {
if (isSSLEnabled()) {
serverSocketFactory =
handler.getSslImplementation().getServerSocketFactory(this);
} else {
serverSocketFactory = new DefaultServerSocketFactory(this);
}
}
//创建ServerSocket绑定监听端口
if (serverSocket == null) {
try {
if (getAddress() == null) {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog());
} else {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog(), getAddress());
}
} catch (BindException orig) {
String msg;
if (getAddress() == null)
msg = orig.getMessage() + " <null>:" + getPort();
else
msg = orig.getMessage() + " " +
getAddress().toString() + ":" + getPort();
BindException be = new BindException(msg);
be.initCause(orig);
throw be;
}
}
}
在连接器的初始化方法中,会调用协议处理器的初始化方法,协议处理器会调用Endpoint的初始化方法,最终在Endpoint中完成了ServerSocket的创建,并且绑定了端口(此时还不能接受处理HTTP请求)
当Catalina的load方法调用完成后,除了懒加载的容器组件还未创建,其它组件都已经创建出来了,下一步就是启动这些组件(上面只是初始化,组件还没开始工作)。
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
跟调用load方法一样,也是通过反射去调用Catalina的start方法。
public void start() {
//...省略
try {
//调用Server的启动方法
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;
}
//...省略
}
通过调用最顶层Server组件的start方法,会导致Service组件的start被调用,而Service组件的会调用Connector组件的start方法。
此处值得关注的还是Connector的start方法,在上文中,ServerSocket已经被创建并且绑定了端口,但是还没有去执行接受连接的方法。
Connector:
protected void startInternal() throws LifecycleException {
//...省略
try {
//调用协议处理器的start
protocolHandler.start();
} catch (Exception e) {
//...省略
}
mapperListener.start();
}
Http11Protocol
public void start() throws Exception {
try {
endpoint.start();
} catch (Exception ex) {
}
}
JIoEndpoint
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
//创建一个线程池
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
//创建连接接收器并放入线程池中接受连接
startAcceptorThreads();
//创建一个超时处理线程
Thread timeoutThread = new Thread(new AsyncTimeout(),
getName() + "-AsyncTimeout");
timeoutThread.setPriority(threadPriority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
}
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
//创建连接接收器
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
//一个连接接收器对应一个线程
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
Acceptor:
public void run() {
//...省略
try {
countUpOrAwaitConnection();
Socket socket = null;
try {
//此处调用ServerSocket的阻塞接受连接方法
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
countDownConnection();
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
//...省略
}
连接器start中调用协议处理器的start,协议处理器最终调用Endpoint的start,最终在Endpoint中创建了Acceptor(接收器),并且将Acceptor放入一个线程中异步处理(因为BIO的socket会阻塞),此刻整个tomcat的启动流程大致完成,Tomcat启动完成之后,就是在Acceptor中接受请求并处理了。
Tomcat启动时序图: