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();
这节只讲初始化,所以下面依次讲下init、load方法,start方法下一节在分析
init方法
public void init() throws Exception {
/*
* 初始化ClassLoader
*/
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");
/*
* 通过classLoader反射实例化org.apache.catalina.startup.Catalina类
*/
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
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);
/*
* 反射调用Catalina的setParentClassLoader方法
*/
method.invoke(startupInstance, paramValues);
// 进行变量赋值
catalinaDaemon = startupInstance;
}
简单总结下上面init方法,然后依次进行分析
- initClassLoaders(),初始化ClassLoader,加载lib/*.jar,这些都是Tomcat公共的资源
- catalinaLoader.loadClass(“org.apache.catalina.startup.Catalina”),反射实例化Catalina类
- 反射调用Catalina的setParentClassLoader方法
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);
initClassLoaders方法
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
实际上调用了createClassLoader方法
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
/*
* 获取conf/catalina.properties文件里的 name.loader的值,这个文件大家可以自己去看
*/
String value = CatalinaProperties.getProperty(name + ".loader");
/*
* 如果value为空,那么直接返回parentClassLoader
*/
if ((value == null) || (value.equals("")))
return parent;
/*
* 把value中的${catalina.base}、${catalina.home}替换成真实路径
*/
value = replace(value);
List<Repository> repositories = new ArrayList<>();
/*
* 取出""双引号或者()里的值,切分成数组
*/
String[] repositoryPaths = getPaths(value);
/*
* 根据path,构造对应的Repository对象
*/
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(
new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
//e.printStackTrace();
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(
new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(
new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(
new Repository(repository, RepositoryType.DIR));
}
}
/*
* 创建URLClassLoader,里面的源码就不分析了,感兴趣可以继续看
*/
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
简单总结下initClassLoaders
- 创建了三个CommonClassLoader、ServerClassLoader、SharedClassLoader
- 根据conf/catalina.properties目录里的name+“.loader”的路径,来创建ClassLoader
- 默认情况下server.loader、shared.loader为空,所以ServerClassLoader和SharedClassLoader都是CommonClassLoader
- 如果server.loader、shared.loader不为空,那么CommonClassLoader就是ServerClassLoader和SharedClassLoader的parentClassLoader
所以init方法,就分析完毕,主要是创建了三个ClassLoader,并且把ClassLoader设置到Catalina中
紧接着来看下load方法
load方法
private void load(String[] arguments)
throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}
上面实际上,反射调用了Catalina类的load方法
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// 创建一个Digester XML解析器 后续分一章节介绍
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
try {
/**
* 创建server.xml的File对象
*/
file = configFile();
inputStream = new FileInputStream(file);
/**
* 这里使用JDK自带的SAX解析去解析server.xml
* 所以构造InputSource 对象
*/
inputSource = new InputSource(file.toURI().toURL().toString());
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("catalina.configFail", file), e);
}
}
/*
* 如果server.xml文件不存在
* 那么从ClassLoader中获取conf/server.xml
*/
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);
}
}
}
/*
* 如果上面两个文件都获取不到server.xml
* 那么从ClassLoader中获取server-embed.xml
*/
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);
}
}
}
/*
* 如果都获取不到server.xml或者server-embed.xml,那么就直接打印异常信息,return
*/
if (inputStream == null || inputSource == null) {
if (file == null) {
log.warn(sm.getString("catalina.configFail",
getConfigFile() + "] or [server-embed.xml]"));
} else {
log.warn(sm.getString("catalina.configFail",
file.getAbsolutePath()));
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
}
return;
}
try {
inputSource.setByteStream(inputStream);
// 把当前对象也就是Catalina,压入Digester的栈中,这个很有用,后面再说
digester.push(this);
/**
* 解析server.xml 并且创建相应的对象包括,列举几个关键的对象
* 1、Server:org.apache.catalina.core.StandardServer
* 2、Server的几个监听器:
* 1、org.apache.catalina.startup.VersionLoggerListener
* 2、org.apache.catalina.core.AprLifecycleListener
* 3、org.apache.catalina.core.JreMemoryLeakPreventionListener
* 4、org.apache.catalina.mbeans.GlobalResourcesLifecycleListener
* 5、org.apache.catalina.core.ThreadLocalLeakPreventionListener
* 3、Service:org.apache.catalina.core.StandardService
* 4、2个Connector,分别为
* 1、org.apache.coyote.http11.Http11NioProtocol:处理HTTP请求
* 2、org.apache.coyote.http11.Http11AprProtocol:处理AJP请求
* 5、Engine:org.apache.catalina.core.StandardEngine
* 6、Host:org.apache.catalina.core.StandardHost
*/
digester.parse(inputSource);
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
/**
* 给Server设置Catalina信息
*/
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// Start the new server
try {
/**
* 调用Server的init方法,下面都是靠着父组件去驱动子组件初始化
*/
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
}
简单总结下上面的内容:
- 创建并且初始化Digester对象,用于解析server.xml或者server-embed.xml文件,这个Digester是JDK SAX解析的框架
- 先获取server.xml文件流,如果获取不到,那么在获取server-embed.xml。如果都获取不到,那么直接return,打印异常信息
- 使用Digester进行解析server.xml,创建相应的对象
- 给Server【org.apache.catalina.core.StandardServer】设置Catalina信息,并且调用Server的init方法
下面我们看下Server的init方法,StandardServer类继续了LifecycleMBeanBase,所以实际上调用的是模板方法initInternal,不懂的,可以回去看下我讲的Lifecyle执行机制
protected void initInternal() throws LifecycleException {
/*
* 调用父类的initInternal,把当前对象注册到JMX
*/
super.initInternal();
/*
* 下面分别把StringCache和MBeanFactory注册到JMX
*/
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();
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
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();
}
}
// 调用了Service的init方法,默认只有一个
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
这里同样是调用Service的init方法,这个Service是StandardService,它也继承了LifecycleMBeanBase,所以也是调用了它的模板方法initInternal
protected void initInternal() throws LifecycleException {
/*
* 同样调用父类的initInternal,把当前对象注册到JMX
*/
super.initInternal();
/*
* 调用Engine的init方法
*/
if (engine != null) {
engine.init();
}
/*
* 如果用户有在server.xml的Service标签下配置Executor
* 那么会以此调用每个Executor的init方法
* 这个Executor是Tomcat是继承了JUC里的Executor,
* 并且继承了Lifecycle,让其拥有生命周期,
* 实现类是StandardThreadExecutor
*/
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
}
mapperListener.init();
// Initialize our defined Connectors
synchronized (connectorsLock) {
/*
* 循环调用Connector的init方法
* 默认存在2个Connector,1个是处理AJP,1个是处理Http
*/
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
简单总结下StandardService的initInternal方法:
- 调用 super.initInternal(),把自身对象注册到JMX
- 调用Engine的init方法,进行Engine初始化
- 如果有配置自定义Executor,那么调用findExecutors(),并且遍历Executor集合,一次调用Executor的init方法
- 遍历Connector集合,依次调用Connector的init方法
紧接着我们看下Engine的init方法,也就是StandardEngine的initInternal
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();
}
很简单,只是获取Realm和把自身注册到JMX,并且调用了父类的initInternal()方法
ContainerBase
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
/*
* 创建了一个线程池,默认coreThread=1而且maxThread=1,这个线程池的作用
* 主要是调用子组件的start方法
*/
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
/*
* 设置ThreadPoolExecutor.allowCoreThreadTimeOut属性为true
* 当线程池中没有Task达到10s,它会回收coreThread
*/
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
紧接着我们看下Connector的initInternal方法,首先我们看下Connector的构造方法
public Connector(String protocol) {
/*
* 如果protocol是HTTP/1.1,那么会设置protocolHandlerClassName = org.apache.coyote.http11.Http11NioProtocol
* 如果是AJP/1.3,那么会设置protocolHandlerClassName = org.apache.coyote.ajp.AjpAprProtocol
*/
setProtocol(protocol);
// Instantiate protocol handler
ProtocolHandler p = null;
try {
/*
* 根据上面设置的protocolHandlerClassName,反射实例化对象
*/
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;
}
}
public void setProtocol(String protocol) {
boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
AprLifecycleListener.getUseAprConnector();
if ("HTTP/1.1".equals(protocol) || protocol == null) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
}
} else if ("AJP/1.3".equals(protocol)) {
if (aprConnector) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
}
} else {
setProtocolHandlerClassName(protocol);
}
}
/*
* 创建了一个Nio2Endpoint类,这个类主要负责端口监听,请求的处理
*
*/
public Http11Nio2Protocol() {
super(new Nio2Endpoint());
}
protected void initInternal() throws LifecycleException {
/*
* 把自身注册到JMX
*/
super.initInternal();
/*
* 实例化一个CoyoteAdapter适配器,
* 把Request、Response转为HttpServletRequest、HttpServletResponse
*/
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if (null == parseBodyMethodsSet) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
protocolHandler instanceof AbstractHttp11JsseProtocol) {
AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
(AbstractHttp11JsseProtocol<?>) protocolHandler;
if (jsseProtocolHandler.isSSLEnabled() &&
jsseProtocolHandler.getSslImplementationName() == null) {
// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
}
}
try {
/*
* 这里调用protocolHandler的init方法
* 以HTTP/1.1为例,也就是org.apache.coyote.http11.Http11NioProtocol
*/
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
}
紧接着调用org.apache.coyote.http11.Http11NioProtocol的init方法,实际上调用的是父类org.apache.coyote.AbstractProtocol的init
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
/* 最关键就是调用了endpoint的init,这里以Nio2Endpoint为例,
* 这个对象是在创建Http11Nio2Protocol的构造函数里创建的,上面有说
*/
endpoint.init();
}
调用Http11Nio2Protocol的init方法,实际上也是调用父类org.apache.tomcat.util.net.AbstractEndpoint的init
public void init() throws Exception {
if (bindOnInit) {
/*
* bind()方法很关键,主要就是绑定端口
*/
bind();
bindState = BindState.BOUND_ON_INIT;
}
if (this.domain != null) {
// Register endpoint (as ThreadPool - historical name)
oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
Registry.getRegistry(null, null).registerComponent(this, oname, null);
for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
registerJmx(sslHostConfig);
}
}
}
Nio2Endpoint
public void bind() throws Exception {
// Create worker collection
/*
* 如果没有在server.xml没有在service标签下配置自定义的Executor,
* 那么会调用createExecutor(),创建默认的Executor线程池
*/
if ( getExecutor() == null ) {
createExecutor();
}
if (getExecutor() instanceof ExecutorService) {
threadGroup = AsynchronousChannelGroup.withThreadPool((ExecutorService) getExecutor());
}
// AsynchronousChannelGroup currently needs exclusive access to its executor service
if (!internalExecutor) {
log.warn(sm.getString("endpoint.nio2.exclusiveExecutor"));
}
/*
* 这里很熟悉的NIO编程了,鉴定指定端口
*/
serverSock = AsynchronousServerSocketChannel.open(threadGroup);
socketProperties.setProperties(serverSock);
InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
serverSock.bind(addr,getAcceptCount());
// Initialize thread count defaults for acceptor, poller
if (acceptorThreadCount != 1) {
// NIO2 does not allow any form of IO concurrency
acceptorThreadCount = 1;
}
// Initialize SSL if needed
initialiseSsl();
}
到此为止,Tomcat初始化完毕。
总结
简单总结下Tomcat初始化,主要分为几点
- 创建3个ClassLoader,分别为CommonClassLoader、ServerClassLoader、SharedClassLoader。这几个ClassLoader默认都是CommonClassLoader,他们加载了Tomcat公共的Jar,比如lib下的所有的jar
- 根据server.xml的配置创建对应的组件,比如Server、Service、Engine等等组件
- Tomcat初始化特点:每个组件都是在父组件里去调用初始化,以此循环去初始化各个组件,以HTTP协议为例,最后到Nio2Endpoint绑定端口成功后就结束。
所以你会发现,Tomcat初始化时,并没有去加载War项目,它是在调用start()启动的时候才去加载项目。