Tomcat不管在学习还是在工作中,都是用得很多的servlet容器。本文先来分析一下Tomcat的启动流程,熟悉整个流程后有助于后续对一些细节问题的分析。
虽然本文的标题叫作启动流程,但实际上包含了Tomcat中各个组件的创建和初始化过程。只有先创建和初始化了组件,才能启动组件。
启动方式
独立启动器
脚本
一般我们会在shell执行Tomcat的启动脚本startup.sh,而该脚本会调用到另一个脚本catalina.sh,在这个脚本中会调用Bootstrap类的main方法,并传入start参数。
# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# -----------------------------------------------------------------------------
# Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 may be a softlink
PRG="$0"
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done
PRGDIR=`dirname "$PRG"`
# 将catalina.sh作为可执行程序,后续会调用
EXECUTABLE=catalina.sh
# Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# 1. owned by the user
# 2. owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit 1
fi
fi
# 调用catalina.sh
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
>> "$CATALINA_OUT" 2>&1 "&"
Bootstrap
private static volatile Bootstrap daemon = null;
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
// 执行初始化
bootstrap.init();
}
daemon = bootstrap;
} else {
// 设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
try {
// 默认命令是start
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);
/*
* 调用Catalina的load方法,
* 内部会解析server.xml,这也是load的含义。
* 在这个过程中会创建各个组件,并设置好父子关系。
* 注意在add组件的方法中,存在start逻辑,但是要注意getState().isAvailable()返回的是false。
* 所以在第一次启动的时候,添加组件并不会start组件。
*
* 解析完server.xml后,各个组件的创建过程已完成。
* 接下来就执行从顶层组件Server组件到各个子组件的init操作。
*/
daemon.load(args);
/*
* 调用Catalina的start方法,同样地,也是��顶层组件Server到各个子组件都执行一遍。
* 从这里可以看出,是先调用init方法再调用start方法。
*/
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.");
}
}
}
本文关注的是start操作,所以可以分为下面3个步骤:
- init
- load
- start
public void init(String[] arguments) throws Exception {
init();
load(arguments);
}
public void init() throws Exception {
// 初始化类加载器
initClassLoaders();
// 设置线程上下文类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 加载Catalina类
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
// 创建Catalina对象
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
// 将sharedLoader作为Catalina对象的parentClassLoader对象
paramValues[0] = sharedLoader;
// 获取Catalina中的setParentClassLoader方法
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
// 为Catalina对象设置parentClassLoader属性
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
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;
}
// 获取Catalina中的load方法
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
// 反射调用Catalina中的load方法
method.invoke(catalinaDaemon, param);
}
public void start() throws Exception {
if (catalinaDaemon == null) {
// 如果还没有初始化则执行初始化
init();
}
// 获取Catalina类的start方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
// 反射调用
method.invoke(catalinaDaemon, (Object [])null);
}
这三个方法中都会调用到Catalina类中的方法,所以这也是为什么把Bootstrap类叫作门面类的原因。 在init方法中,主要是处理类加载器相关的工作,本文主要是分析启动过程,类加载机制后面单独写文章分析。
Catalina
下面来看看Catalina类的load方法。
public void load() {
// 避免重复加载
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
// 空操作
initDirs();
// Before digester - it may be needed
// 设置解析配置文件需要的一些属性
initNaming();
// Create and execute our Digester
// 创建配置文件解析器
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);
}
}
try {
/*
* 通过Digester库解析server.xml
*/
inputSource.setByteStream(inputStream);
digester.push(this);
// 这里会设置this的server属性为StandardServer
digester.parse(inputSource);
}
} finally {
}
/*
* 给server设置catalina属性
*/
getServer().setCatalina(this);
// 设置catalina.home属性
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
// 设置catalina.base属性
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
// 对System的流进行重定向
initStreams();
// Start the new server
try {
/*
* 初始化Server对象,为后面的start做准备。
*/
getServer().init();
}
}
protected String configFile = "conf/server.xml";
protected File configFile() {
File file = new File(configFile);
if (!file.isAbsolute()) {
// 转为绝对路径
file = new File(Bootstrap.getCatalinaBase(), configFile);
}
return file;
}
这里会先读取配置文件,一般是$TOMCAT_HOME/conf/server.xml文件,然后对配置文件进行XML解析,解析过程还是比较复杂的,这里不介绍。 然后为Server组件设置catalina.base和catalina.home属性,最后调用Server组件的init方法来初始化Server组件。
接下来看看start方法。
public void start() {
// 一般在bootstrap中就已经调用过load()方法了,所以这里不会是null。
if (getServer() == null) {
// 初始化server
load();
}
// 确保server组件存在
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
// 启动server
getServer().start();
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
/*
* 注册JVM shutdown钩子
* 将hook对象添加到JVM退出时执行列表中,
* JVM退出时会执行hook对象的run方法。
*/
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
if (await) {
// 让当前线程等待Tomcat结束
await();
stop();
}
}
这里调用的也是Server组件的start方法。关于Server组件的init和start方法,在下面进行分析。
内嵌启动器
随着Spring Boot的流行,Tomcat越来越被作为内嵌servlet容器来启动。在Spring Boot中的Web服务器一文中,介绍了Tomcat容器的创建过程,但是并没有深入分析Tomcat类的实现细节。
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 禁用JMX注册
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 创建Tomcat实例
Tomcat tomcat = new Tomcat();
// 获取或者创建Tomcat的工作目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
// 设置工作目录
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
// 创建连接器组件
Connector connector = new Connector(this.protocol);
// 设置在失败时抛出异常
connector.setThrowOnFailure(true);
// 获取service组件,不存在则会创建,然后添加连接器组件
tomcat.getService().addConnector(connector);
// 定制化连接器,会设置监听端口
customizeConnector(connector);
// 为tomcat实例设置连接器
tomcat.setConnector(connector);
// 获取Host组件,内部会先获取Engine组件,如果不存在则新建;然后设置主机自动部署任务为false
tomcat.getHost().setAutoDeploy(false);
// 配置Engine组件
configureEngine(tomcat.getEngine());
// 添加额外的连接器组件
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 创建Tomcat中的Context组件,并设置其属性,然后将其添加到Host组件下
prepareContext(tomcat.getHost(), initializers);
// 根据Tomcat实例,创建TomcatWebServer实例对象
return getTomcatWebServer(tomcat);
}
在该方法中,先是创建了Tomcat对象,然后一次调用了getService方法来获取Service组件,先来看看该方法是怎么实现的。
创建Server和Service组件
public Service getService() {
return getServer().findServices()[0];
}
public Server getServer() {
// 如果创建过server,
if (server != null) {
// 则直接返回
return server;
}
System.setProperty("catalina.useNaming", "false");
// 创建Server组件
server = new StandardServer();
// 初始化工作目录,包括Catalina的base和home目录
initBaseDir();
server.setPort( -1 );
// 创建Service组件
Service service = new StandardService();
service.setName("Tomcat");
// 把service添加到server中
server.addService(service);
return server;
}
public Server getServer() {
// 如果创建过server,
if (server != null) {
// 则直接返回
return server;
}
System.setProperty("catalina.useNaming", "false");
// 创建Server组件
server = new StandardServer();
// 初始化工作目录,包括Catalina的base和home目录
initBaseDir();
server.setPort( -1 );
// 创建Service组件
Service service = new StandardService();
service.setName("Tomcat");
// 把service添加到server中
server.addService(service);
return server;
}
可以看到,虽然方法名称是getXXX,但是会判断对应的组件是否存在,如果不存在会创建的,其他的getXXX方法也是相似的。 这里创建了StandardServer和StandardService两个组件。
创建Connector组件
在Spring Boot的TomcatServletWebServerFactory类的getWebServer方法中,直接创建了Connector组件,然后对其进行设置。由于其创建过程还是比较复杂的,涉及到协议相关子组件的创建,所以在下面统一进行分析。
创建Engine和Host组件
因为在Spring Boot的TomcatServletWebServerFactory类中的getWebServer方法中,因为先调用了Tomcat的getHost方法,然后再调用的getEngine方法。但因为host组件是engine组件的子组件,所以会先创建Engine组件再创建Host组件。
public Host getHost() {
// 获取engine组件
Engine engine = getEngine();
// 如果存在host子组件,则直接返回
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
// 创建host组件
Host host = new StandardHost();
host.setName(hostname);
// 将host组件添加到engine组件中
getEngine().addChild(host);
return host;
}
public Engine getEngine() {
// 获取server中的第一个service组件
Service service = getServer().findServices()[0];
if (service.getContainer() != null) {
return service.getContainer();
}
// 创建Engine对象
Engine engine = new StandardEngine();
engine.setName( "Tomcat" );
engine.setDefaultHost(hostname);
engine.setRealm(createDefaultRealm());
// 将engine设置到service中
service.setContainer(engine);
return engine;
}
这里还会把Host组件添加到Engine组件的children属性中。
创建Context组件
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
// 创建tomcat中的context组件,该类是Spring Boot中实现的,继承tomcat中的StandardContext
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
// 为context组件设置一些属性
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldPatterns(context);
WebappLoader loader = new WebappLoader();
// 设置类加载器名称
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
// 设置web应用加载器
context.setLoader(loader);
// 是否注册默认servlet,该方法默认返回false
if (isRegisterDefaultServlet()) {
// 注册默认的servlet
addDefaultServlet(context);
}
// JSP相关
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
// 添加声明周期监听器,用于配置静态资源
context.addLifecycleListener(new StaticResourceConfigurer(context));
// 添加一些默认的初始化器,并和上层传递过来的合并在一起
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
// 为host添加context子组件
host.addChild(context);
// 将initializers设置到context中
configureContext(context, initializersToUse);
// 默认是空操作
postProcessContext(context);
}
这里创建了Context组件,是Spring Boot中提供的一个类,该类继承了Tomcat中的StandardContext类。并将Context组件设置为Host组件的子组件。
启动Server组件
在Spring Boot的TomcatWebServer类的initialize方法中,会启动Tomcat中的Server组件。
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
// 添加生命周期监听器
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
/*
* ==========
* 启动tomcat
* ==========
*/
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
// 绑定类加载器
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
会调用到Tomcat类的start方法。
/*
* SpringBoot中的TomcatWebServer在创建过程中,调用了该类的start()方法。
*/
public void start() throws LifecycleException {
// 确保Server组件和Service组件存在,如果不存在的话会新建
getServer();
// 确保Connector组件存在,如果不存在的话会新建
getConnector();
// 从server开始执行各个组件的start操作
server.start();
}
可以看出来,Tomcat也像是一个门面类,负责创建、设置、启动、关闭Tomcat中的其他组件。只是Tomcat负责内嵌启动,Bootstrap和Catalina负责独立启动。
总结
不管是独立启动还是内嵌启动,最终都会调用到Server组件的start方法。只不过独立启动的方式,会先调用其init方法来初始化,虽然Spring Boot没有调用Tomcat 的init方法,但是在各个组件的start方法中,会判断有没有初始化过,如果没有则会先初始化。
由于Tomcat中的组件众多,一个组件中包含其他组件,调用链条比较长。但是好在逻辑还是比较清晰的,本文涉及到的就主要是两条主线:
- 初始化
- 启动 针对这种调用链条较长的情况,不适合垂直线性分析,本文会使用横向分析方法,依次分析各个组件的上面两个方面的内容,如果涉及到其他方面的内容,到时候再介绍。 另外,大多数组件都定义了接口,但主要使用的都是StardardXXX类,所以本文仅会分析这些类。
组件生命周期
在具体分析各个组件之前,先来了解一下组件的生命周期。Lifecycle接口定义了各种生命周期阶段名称,以及生命周期方法,比如init、start。但各个组件一般没有直接实现该接口,而是继承另一个实现了Lifecycle接口的抽象类LifecycleBase。这个抽象类中实现了init和start方法。
@Override
public final synchronized void init() throws LifecycleException {
// 如果组件状态不是NEW,则不能初始化
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
try {
// 设置状态为正在初始化
setStateInternal(LifecycleState.INITIALIZING, null, false);
// 调用各个组件的实现
initInternal();
// 设置状态为已初始化
setStateInternal(LifecycleState.INITIALIZED, null, false);
} catch (Throwable t) {
handleSubClassException(t, "lifecycleBase.initFail", toString());
}
}
protected abstract void initInternal() throws LifecycleException;
@Override
public final synchronized void start() throws LifecycleException {
// 不能启动的状态
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
return;
}
// 如果没有初始化过
if (state.equals(LifecycleState.NEW)) {
/*
* 如果没有初始化过则先初始化,
* 在SpringBoot中,在创建TomcatWebServer的时候,会调用Tomcat的start方法,
* 但该方法直接启动StandardServer的server方法,并没有先执行init。
*
* 所以在这里可以发现,start的时候发现如果没有初始化过,则先初始化。
*/
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());
}
}
protected abstract void startInternal() throws LifecycleException;
可以发现,这里实现了init和start方法,主要实现各组件公共的状态机。具体的初始化和启动操作通过各组件重写initInternal和startInternal这两个模板方法来实现。所以在很多组件的实现类中找不到init和start方法,这是因为它们实现的是LifecycleBase中的生命周期模板方法。 在start方法中,如果发现组成还没有初始化过,那么会在调用startInternal方法之前,会先调用组件的init方法来初始化。这就能解释为什么在Spring Boot中的TomcatWebServer类的initialize方法中直接调用Tomcat类的start方法而没有调用init方法也能正常工作了。
注意大多数组件并不是直接继承LifecycleBase,而是LifecycleMBeanBase。LifecycleMBeanBase也实现了LifecycleBase中的生命周期模板方法,主要提供接入JMX的功能,本文不会分析到JMX,所以忽略。
强烈建议下面的内容不要按着顺序看,而是先看创建过程,再看初始化过程,最后看启动过程。而且遇到什么组件就去看什么组件,下面有些组件的顺序是打乱的。不过也没办法,本来Tomcat的组件结构就是树形的,没办法进行线性排版。
StandardServer
初始化
@Override
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
// 注意内嵌启动的方式,并不会被设置Catalina对象
if (getCatalina() != null) {
// 在简化模式下,这里获取到的是commonClassLoader。
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
for (Service service : services) {
/*
* 初始化Service
*/
service.init();
}
}
这里主要在遍历各个Service组件并调用其init方法。
从这里也可以看出来一个Server组件包含了多个Service组件,但在广泛被作为内嵌容器的情况下,往往一个Server组件中只有一个Service组件。
启动
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
// 启动各个Service组件
for (Service service : services) {
service.start();
}
}
}
这里主要在遍历各个Service组件并调用其service方法。
StandardService
初始化
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
if (engine != null) {
// 初始化engine组件
engine.init();
}
// Initialize any Executors
/*
* 获取Executor组件,不管是独立启动还是Spring Boot的内嵌启动,这里获取到的executors数量都是0.
*
*/
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
// 初始化Executor,StandardThreadExecutor的init方法没有做额外操作
executor.init();
}
// Initialize mapper listener
// 初始化mapper监听器,没有做额外的操作
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// 初始化连接器
connector.init();
}
}
}
}
该方法中初始化了Engine、Executor、Connector组件和MapperListener,但Executor和MapperListener的初始化操作中没有做额外的操作。
从这里也可以看出来,一个Service组件包含了多个Connector组件和一个Engine组件。
启动
@Override
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
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
// 启动executor,会创建实际的线程池
executor.start();
}
}
// 启动MapperListener
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
connector.start();
}
}
}
}
}
这里启动了Engine、Executor、Connector组件和MapperListener。
Connector
创建
public Connector() {
this(null);
}
public Connector(String protocol) {
setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {
// 获取协议处理器类,默认是Http11NioProtocol
Class<?> clazz = Class.forName(protocolHandlerClassName);
// 创建协议处理器类
p = (ProtocolHandler) clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
} finally {
this.protocolHandler = p;
}
// 设置字符集
if (Globals.STRICT_SERVLET_COMPLIANCE) {
uriCharset = StandardCharsets.ISO_8859_1;
} else {
uriCharset = StandardCharsets.UTF_8;
}
// Default for Connector depends on this (deprecated) system property
if (Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"))) {
encodedSolidusHandling = EncodedSolidusHandling.DECODE;
}
}
protected String protocolHandlerClassName =
"org.apache.coyote.http11.Http11NioProtocol";
初始化
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
/*
* 创建适配器,并传入了this。
* 在adapter的service方法中,会调用connector通过pipeline完成请求处理。
*/
adapter = new CoyoteAdapter(this);
// 给协议处理器设置请求处理适配器
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
try {
// 初始化协议处理器,具体参考AbstractHttp11Protocol及其父类AbstractProtocol。
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
该方法中主要是在创建适配器,并初始化构造方法中创建的协议处理器。
启动
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
// 检查端口
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
/*
* 启动协议处理器,start方法定义在AbstractProtocol中。
* 内部会启动Endpoint,对于默认的Http11NioProtocol,endpoint是NioEndpoint类型的。
*/
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
该方法中主要是启动了协议处理器。
StandardEngine
该类继承自ContainerBase,说明这是一个容器组件。
创建
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
在这里会创建StandardEngineValve组件,并设置到pipeline中。
初始化
@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();
}
这里主要操作是在父组件中实现的。
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
// 该线程池的主要作用是用于启动子组件
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
这里主要是在创建一个用于启动子组件的线程池。
启动
@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中完成的。
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our child containers, if any
// 获取子组件
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();
}
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
// 启动pipeline
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
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;
}
}
在该方法中,会启动子组件,但是好像没有初始化组件啊?其实调用各个组件的start方法,最终会走到LifecycleBase中的start方法,会检测如果组件没有被初始化,则会先初始化的,上面已经介绍过了。 StandardHost、StandardContext也继承了ContainerBase类,所以其生命周期过程和这里介绍的一样,下面不会再贴出ContainerBase的相关方法。
StandardThreadExecutor
创建
如果在Tomcat的配置文件server.xml配置了Executor(默认情况下这个配置在配置文件中是被注释掉的),例如:
<!--
minSpareThreads表示核心线程数量,maxThreads表示最大线程数量。
具体参考StandardThreadExecutor类的startInternal方法。
-->
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
executor="tomcatThreadPool"
/>
那么Tomcat会解析Executor配置,并创建StandardThreadExecutor对象,然后添加到StandardService对象的executors列表中。 如果在Connector配置中配置了executor(需要引用配置文件中的其他Executor配置项的名称,如上例),那么会通过反射调用ProtocolHanlder的setExecutor方法来设置,这样在下面NioEndpoint的startInternal方法中就不会再创建线程池了。
上面的操作是在ConnectorCreateRule类中的begin方法中进行的。
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Service svc = (Service)digester.peek();
Executor ex = null;
// 如果配置文件中Connector标签中设置了executor属性
if ( attributes.getValue("executor")!=null ) {
ex = svc.getExecutor(attributes.getValue("executor"));
}
Connector con = new Connector(attributes.getValue("protocol"));
if (ex != null) {
// 设置线程池
setExecutor(con, ex);
}
String sslImplementationName = attributes.getValue("sslImplementationName");
if (sslImplementationName != null) {
setSSLImplementationName(con, sslImplementationName);
}
digester.push(con);
}
private static void setExecutor(Connector con, Executor ex) throws Exception {
// 获取到Connector的协议处理器类中的setExecutor方法
Method m = IntrospectionUtils.findMethod(con.getProtocolHandler().getClass(),"setExecutor",new Class[] {java.util.concurrent.Executor.class});
if (m!=null) {
// 反射调用方法来设置线程池
m.invoke(con.getProtocolHandler(), new Object[] {ex});
}else {
log.warn(sm.getString("connector.noSetExecutor", con));
}
}
如果使用内嵌启动方式,也可以按照这种思路设置自定义的线程池。
启动
// ---------------------------------------------- Properties
/**
* Default thread priority
*/
protected int threadPriority = Thread.NORM_PRIORITY;
/**
* Run threads in daemon or non-daemon state
*/
protected boolean daemon = true;
/**
* Default name prefix for the thread name
*/
protected String namePrefix = "tomcat-exec-";
/**
* max number of threads
*/
protected int maxThreads = 200;
/**
* min number of threads
*/
protected int minSpareThreads = 25;
/**
* idle time in milliseconds
*/
protected int maxIdleTime = 60000;
/**
* The executor we use for this component
*/
protected ThreadPoolExecutor executor = null;
/**
* the name of this thread pool
*/
protected String name;
/**
* prestart threads?
*/
protected boolean prestartminSpareThreads = false;
/**
* The maximum number of elements that can queue up before we reject them
*/
protected int maxQueueSize = Integer.MAX_VALUE;
/**
* After a context is stopped, threads in the pool are renewed. To avoid
* renewing all threads at the same time, this delay is observed between 2
* threads being renewed.
*/
protected long threadRenewalDelay =
org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;
private TaskQueue taskqueue = null;
@Override
protected void startInternal() throws LifecycleException {
// 创建任务队列
taskqueue = new TaskQueue(maxQueueSize);
// 创建线程工厂
TaskThreadFactory tf = new TaskThreadFactory(namePrefix,daemon,getThreadPriority());
/*
* 创建线程池,这里的ThreadPoolExecutor和JDK的类同名,是对后者的封装。
* 默认是25个核心线程,200个最大线程,非核心线程最长空闲时长为60秒。
*/
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf);
executor.setThreadRenewalDelay(threadRenewalDelay);
// 这个属性默认是false,表示不预先启动核心线程
if (prestartminSpareThreads) {
// 预启动核心线程
executor.prestartAllCoreThreads();
}
// 为任务队列绑定执行器
taskqueue.setParent(executor);
setState(LifecycleState.STARTING);
}
这里创建的线程池类型是Tomcat中自己封装JDK的,两者同名,要注意区分。默认情况下Tomcat会创建25个核心线程,最多200个线程。
ProtocolHandler
Tomcat提供了多种协议处理器,默认使用的是Http11NioProtocol,另外还有Http11Nio2Protocol,两者区别在于使用的NIO的版本不一样。
本文以默认使用的Http11NioProtocol为例。
创建
public Http11NioProtocol() {
/*
* 创建NioEndPoint对象,并传入父类构造器。
*/
super(new NioEndpoint());
}
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint<S> endpoint) {
super(endpoint);
}
public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
super(endpoint);
// 设置连接超时时间,默认是60秒
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
// 创建并设置连接处理器
ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
// 给endpoint对象设置handler,在线程池中会调用到该对象。
getEndpoint().setHandler(cHandler);
}
public AbstractProtocol(AbstractEndpoint<S> endpoint) {
this.endpoint = endpoint;
// 这里设置的值是 -1
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
// 设置TCP无延迟,默认为true
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
初始化
对于ProtocolHandler组件而言,其没有实现Lifecycle接口,但是在ProtocolHanlder接口中定义了init、start等方法,所以这里和上面其他组件的生命周期方法不一样,注意进行区分。
@Override
public void init() throws Exception {
// Upgrade protocols have to be configured first since the endpoint
// init (triggered via super.init() below) uses this list to configure
// the list of ALPN protocols to advertise
// 协议升级相关
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
configureUpgradeProtocol(upgradeProtocol);
}
// 会初始化端点组件
super.init();
}
@Override
public void init() throws Exception {
String endpointName = getName();
/*
* 给Endpoint设置名称,对于NioEndpoint而言,该名称会被作为线程池中线程名称的前缀。
* 上面返回的endpointName是被引号括住的,这里去掉引号。
*/
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
// 初始化端点组件,参考NioEndpoint
endpoint.init();
}
在AbstractProtocol的init方法中会初始化端点(AbstractEndpoint)组件,并设置名称。这个名称比较重要,接下来看看设置的是什么内容。
public String getName() {
return ObjectName.quote(getNameInternal());
}
private String getNameInternal() {
// 这里考虑Http11NioProtocol中的getNamePrefix方法,返回的是http-nio
StringBuilder name = new StringBuilder(getNamePrefix());
name.append('-');
// 如果设置了主机地址
if (getAddress() != null) {
// 获取主机地址
name.append(getAddress().getHostAddress());
name.append('-');
}
// 获取端口
int port = getPort();
// 如果端口是0
if (port == 0) {
// Auto binding is in use. Check if port is known
name.append("auto-");
name.append(getNameIndex());
port = getLocalPort();
if (port != -1) {
name.append('-');
name.append(port);
}
} else {
// 追加上端口,如最常见的http-nio-8080
name.append(port);
}
return name.toString();
}
protected abstract String getNamePrefix();
@Override
protected String getNamePrefix() {
if (isSSLEnabled()) {
return ("https-" + getSslImplementationShortName()+ "-nio");
} else {
// 这里返回的名称会被作为业务线程池中线程的名称
return ("http-nio");
}
}
这里我们就知道了在日志中经常出现的线程名称前缀“http-nio”是怎么来的了。
启动
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
}
// 启动端点组件,参考NioEndpoint
endpoint.start();
// Start timeout thread
asyncTimeout = new AsyncTimeout();
// 创建线程,周期性处理异步超时相关任务。
Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
int priority = endpoint.getThreadPriority();
if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
priority = Thread.NORM_PRIORITY;
}
timeoutThread.setPriority(priority);
timeoutThread.setDaemon(true);
// 启动线程
timeoutThread.start();
}
这里会启动端点组件。
设置适配器
在Connector的init方法中,会创建适配器并设置到协议处理器中。
protected Adapter adapter;
@Override
public void setAdapter(Adapter adapter) { this.adapter = adapter; }
@Override
public Adapter getAdapter() { return adapter; }
在后续的请求处理中会调用到该适配器。
AbstractEndpoint
初始化
和ProtocolHandler一样,AbstractEndpoint自己内部定义了init、start等方法,而且定义了xxxInternal的模板方法,这和LifecycleBase那一套很类似,但不是使用的LifecycleBase,注意区分。
@Override
public void init() throws Exception {
// 测试加密套件
testServerCipherSuitesOrderSupport();
super.init();
}
public void init() throws Exception {
if (bindOnInit) {
// 执行bind操作
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
// 定义了一些模板方法让子类实现
public abstract void bind() throws Exception;
public abstract void unbind() throws Exception;
public abstract void startInternal() throws Exception;
public abstract void stopInternal() throws Exception;
这里主要是在调用子类实现的bind方法。
NioEndpoint
对于NioEndpoint,其bind方法如下。
private NioSelectorPool selectorPool = new NioSelectorPool();
@Override
public void bind() throws Exception {
if (!getUseInheritedChannel()) {
// 创建服务端网络通道
serverSock = ServerSocketChannel.open();
// 这里socket()方法返回的是JDK的ServerSocketAdaptor,是ServerSocket的子类。
socketProperties.setProperties(serverSock.socket());
// 创建所要监听的地址信息
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
/*
* 绑定地址,第二个参数的默认值为100,表示等待处理的连接队列的长度。
* 同时进行监听。
*/
serverSock.socket().bind(addr,getAcceptCount());
} else {
// Retrieve the channel provided by the OS
Channel ic = System.inheritedChannel();
if (ic instanceof ServerSocketChannel) {
serverSock = (ServerSocketChannel) ic;
}
if (serverSock == null) {
throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
}
}
/*
* 配置为阻塞模式,为什么?
* 如果请求没有到来,则会阻塞poller线程。
*/
serverSock.configureBlocking(true); //mimic APR behavior
/*
* 如果没有设置acceptor和poller的数量,那么都设置为1。
*/
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
// 默认最多是2
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
setStopLatch(new CountDownLatch(pollerThreadCount));
// Initialize SSL if needed
initialiseSsl();
// 打开选择器池
selectorPool.open();
}
在这个方法中创建了ServerSocket,并进行了地址绑定和监听。
运行
// 定义了一些模板方法让子类实现
public abstract void bind() throws Exception;
public abstract void unbind() throws Exception;
public abstract void startInternal() throws Exception;
public abstract void stopInternal() throws Exception;
public final void start() throws Exception {
// 如果子类没有重写init方法,那么在init方法中就执行了bind了
if (bindState == BindState.UNBOUND) {
// 如果还未绑定就先绑定
bind();
bindState = BindState.BOUND_ON_START;
}
// 调用子类实现
startInternal();
}
对于NioEndpoint而言,没有重写父类的init方法,所以在AbstractEndpoint的init方法中就已经完成了绑定了,所以这里不会再次绑定,而是直接调用子类的实现。
NioEndpoint
@Override
public void startInternal() throws Exception {
// 记录启动过没有
if (!running) {
running = true;
paused = false;
processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getProcessorCache());
eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getEventCache());
nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
socketProperties.getBufferPool());
// Create worker collection
// 如果没有设置线程池,
if ( getExecutor() == null ) {
// 则创建
createExecutor();
}
// 初始化用于控制最大连接数量的门闩
initializeConnectionLatch();
// Start poller threads
/*
* 创建并启动n个poller线程
*/
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
// 创建poller线程
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
// 启动线程
pollerThread.start();
}
/*
* 启动acceptor线程
*/
startAcceptorThreads();
}
}
如果没有设置线程池,则调用父类中的createExecutor方法来创建线程池。
/*
* 注意区分这里的executor和StandardService中的组件Executor。
* 前者直接就是JUC中的类,后者是tomcat中实现的继承了JUC中同名类的类。
* 如果是server.xml文件中的Connector标签中设置了executor选项,并引用了Executor配置,
* 那么Catalina在解析配置文件的时候时会自动通过反射来设置到该属性上的。
*/
private Executor executor = null;
public void setExecutor(Executor executor) {
this.executor = executor;
this.internalExecutor = (executor == null);
}
public Executor getExecutor() { return executor; }
private int minSpareThreads = 10;
private int maxThreads = 200;
public void createExecutor() {
internalExecutor = true;
// 创建任务队列
TaskQueue taskqueue = new TaskQueue();
// 创建线程工厂
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
/*
* 创建线程池对象,和Executor组件一样,这里的ThreadPoolExecutor也是tomcat中与JUC同名的类,并且继承了JUC中对应的同名类。
*/
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
}
可以看到,这里默认情况下是10个核心线程数,最大线程数为200。 另外这里创建的线程工厂时传递的线程名称前缀,还记得上面设置的名称是“http-nio-8080”(假设监听的端口是8080),那么这里拼接一下就成了“http-nio-8080”。
注意,如果在配置文件中的Connector标签中设置了executor属性引用其他的Executor配置,那么Tomcat会自动将该线程池设置到AbstractEndpoint的executor属性上。总之,如果其他地方设置了该属性,导致该属性不是null,都不会创建这里的默认线程池。
接下来创建用于控制最大连接数的门闩变量。
private int maxConnections = 10000;
public void setMaxConnections(int maxCon) {
this.maxConnections = maxCon;
LimitLatch latch = this.connectionLimitLatch;
if (latch != null) {
// Update the latch that enforces this
if (maxCon == -1) {
// 如果不限制连接数,则将门闩变量设置为null
releaseConnectionLatch();
} else {
// 设置新的最大连接数
latch.setLimit(maxCon);
}
} else if (maxCon > 0) {
// 重新创建过门闩变量
initializeConnectionLatch();
}
}
public int getMaxConnections() { return this.maxConnections; }
protected LimitLatch initializeConnectionLatch() {
// 如果是-1,则不用限制连接数
if (maxConnections==-1) return null;
if (connectionLimitLatch==null) {
connectionLimitLatch = new LimitLatch(getMaxConnections());
}
return connectionLimitLatch;
}
protected void releaseConnectionLatch() {
LimitLatch latch = connectionLimitLatch;
if (latch!=null) latch.releaseAll();
connectionLimitLatch = null;
}
再接下来就是创建poller线程。
/*
* poller个数最大不能超过2。
*/
private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
public int getPollerThreadCount() { return pollerThreadCount; }
这里默认最多创建2个poller线程。
最后会启动acceptor线程。
protected final void startAcceptorThreads() {
// 获取acceptor线程的数量,默认是1
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
// 创建Acceptor对象
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
// 创建acceptor线程
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
// 启动acceptor线程
t.start();
}
}
这里的acceptor线程会获取客户端的连接请求,然后选择一个poller线程,并将请求放入其任务队列中,poller线程会从该队列中取出任务并提交到上面AbstractEndpoint的createExecutor方法创建的线程池中处理。具体细节在后面连接请求过程中再分析。
StandardHost
在上面介绍内嵌启动方式时,会看到在Spring Boot中会创建StandardHost组件。独立启动方式也是会启动的,只不过会通过解析配置文件来创建,没有分析具体的创建过程。 该类和StandardEngine一样,继承了ContainerBase,是一个容器组件。
创建
public StandardHost() {
super();
/*
* 设置流水线的basic属性。
*/
pipeline.setBasic(new StandardHostValve());
}
这里往流水线pipeline中添加了一个StandardHostValve组件。
启动
@Override
protected synchronized void startInternal() throws LifecycleException {
// Set error report valve
// 获取错误报告这个Valve的类名
String errorValve = getErrorReportValveClass();
// 如果存在这个类
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
// 获取pipeline中的valves
Valve[] valves = getPipeline().getValves();
// 遍历valves数组
for (Valve valve : valves) {
// 找到错误报告ErrorReportValve
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
// 如果没有找到,则创建一个并添加到pipeline中
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve);
}
}
}
super.startInternal();
}
这里会判断pipeline中是否有负责错误报告的Valve,如果没有则会创建一个并添加到pipeline中。然后再次调用父类中的启动方法来启动子组件Context。
StandardContext
创建
public StandardContext() {
super();
// 添加一个基本的Valve
pipeline.setBasic(new StandardContextValve());
broadcaster = new NotificationBroadcasterSupport();
// Set defaults
if (!Globals.STRICT_SERVLET_COMPLIANCE) {
// Strict servlet compliance requires all extension mapped servlets
// to be checked against welcome files
resourceOnlyServlets.add("jsp");
}
}
该构造方法中,主要是创建一个基本的Valve并添加到pipeline中。
初始化方法中主要是在做JNDI相关处理,这里忽略。
运行
该方法中原内容非常多,我挑选一些重要的步骤。
// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
// 启动pipeline
((Lifecycle) pipeline).start();
}
// Call ServletContainerInitializers
// 遍历servlet容器初始化器,并调用其onStartup方法。
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
// 调用servlet上下文初始化器的onStartup方法
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) {
/*
* 获取各种监听器,并执行ServletContextListener。
* SpringMVC中的web.xml中配置listener为Spring的ContextLoadListener,就是在这里被调用的。
*/
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Configure and call application filters
if (ok) {
// 启动过滤器,会将过滤器定义对象转换为过滤器配置对象。
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
/*
* 会调用wrapper的load方法,内部会调用Servlet的init方法。
* SpringBoot中的Context是StandardContext的子类,TomcatEmbeddedContext,重写了loadOnStartup方法,默认直接返回true。
*/
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
注释中已经说明各个阶段所执行的操作,不再赘述。注意该类中就没有再调用父类ContainerBase中的startInternal方法了。
StandardPipeline
在创建StandardEngine、StandardHost、StandardContext组件时,会创建StandardPipeline对象。
创建
创建的位置在ContainerBase这个抽象基类中,而不是在各个实现类中。
/*
* 流水线
*/
protected final Pipeline pipeline = new StandardPipeline(this);
重写的initInternal是空方法,所以没有必要介绍。
启动
除了StandardContext组件是在自己的startInternal方法中启动的pipeline以外,其他的那几个组件都是在ContainerBase的startInternal方法中启动的pipeline。
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start the Valves in our pipeline (including the basic), if any
Valve current = first;
if (current == null) {
// 如果没有设置first,则从basic开始处理
current = basic;
}
// 遍历Valve链
while (current != null) {
// 要求Valve实现了Lifecycle接口,才进行启动
if (current instanceof Lifecycle)
// 启动Valve
((Lifecycle) current).start();
current = current.getNext();
}
setState(LifecycleState.STARTING);
}
这里在遍历Valve组件链,并进行启动。
总结
本文梳理了Tomcat中的各个组件,分析了各个组件的创建、初始化、启动过程。整体内容比较多,不适合从头到尾通读,而是需要在不同组件之间跳着看。