《看透SpringMVC源码分析与实践》
Tomcat源码分析—-初始化与启动
tomcat环境搭建
源码下载
由于项目使用的tomcat版本时7.47
,从apache svn check代码,svn地址是:
http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_47/。
idea环境搭建
idea搭建tomcat7开发环境,参照基于IntelliJ IDEA 15.0.2的Tomcat7.0.69源码运行环境搭建
- 将tomcat_7_0_47转换为Maven工程,添加 pom.xml 文件。
- 在tomcat_7_0_47的源码根目录下,新建 catalina-base目录,作为Tomcat的工作目录
- 将 conf、logs
(新建目录)
、webapps、work(新建目录)
文件夹,移入 catalina-base目录 - idea引入import Project –>pom.xml
- 删除无用的文件、文件夹(
非强迫症患者,此步可跳过
)。删除bin/,modules/,res/,test/,build.properties等文件
,最终的项目结构如下图:
- 运行
org.apache.catalina.startup.Bootstrap.main()
,jvm参数为:-Dcatalina.base="你的工程目录\catalina-base"
,log配置为output to file:你的工程目录\catalina-base\logs
可能会报错:
java.lang.ClassNotFoundException: websocket.drawboard.DrawboardContextListener
只需将/webapps/example 文件夹删除删除即可。
tomcat启动
tomcat的启动入口是org.apache.catalina.startup.Bootstrap.main()
Bootstrap.main()
//org.apache.catalina.startup.Bootstrap
public static void main(String args[]) {
//创建一个Bootstrap,当bootstrap 初始化完成之后,再赋值给daemon
if (daemon == null) {
//try-catch 省略....
Bootstrap bootstrap = new Bootstrap();
//调用init()方法,初始化ClassLoader,并用ClassLoader穿件Catalina实例,赋给catalinaDaemon变量。
bootstrap.init();
daemon = bootstrap;
} else {
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
try {
//默认command为start
String command = "start";
if (args.length > 0) {
//取args最后一个参数,为command
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("start")) {
//start比 startd多了一行代码: daemon.setAwait(true);
daemon.setAwait(true);
daemon.load(args);
//调用start方法...
daemon.start();
}
//省略,stopd,stop
else if (command.equals("configtest")) {
daemon.load(args);
if (null==daemon.getServer()) {
System.exit(1);
}
System.exit(0);
}
} catch (Throwable t) {
//省略log....
System.exit(1);
}
}
main方法可传入的参数有
startd、stopd、start、stop,startd和start的区别是设置了一个await的boolean变量为true
main方法内容分为三部分:
- 首先创建Bootstrap,并执行
init()
方法初始化, - 然后调用
load()
- 最后调用
start()
方法。
Bootstrap.init()
调用Boostrap的init方法主要完成:
- 初始化路径:CATALINA_HOME,CATALINA_BASE
- 初始化类加载器:commonLoader、catalinaLoader、sharedLoader.
- 初始化Boostrap的Catalina对象:通过反射生成Catalina对象,并通过反射调用setParentClassLoader方法设置其父 ClassLoader为
sharedLoader
//org.apache.catalina.startup.Bootstrap
public void init() throws Exception {
//设置安装目录:CATALINA_HOME
setCatalinaHome();
//设置工作目录CATALINA_BASE
setCatalinaBase();
//初始化classLoaders
initClassLoaders();
//主线程的classLoader设置为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
//安全管理的classLoad设置为catalineLoader
SecurityClassLoad.securityClassLoad(catalinaLoader);
//省略log....
//load启动类Catalina
Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
/*
* 反射实现:catalina的ParentClassLoader设置为sharedLoader
* catalina.setParentClassLoader(sharedLoader);
*/
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;
}
CATALINA_HOME和CATALINA_BASE区别?
CATALINA_HOME
是Tomcat的安装目录,指向公用信息
的位置,就是bin和lib
的父目录。。
CATALINA_BASE
是Tomcat的工作目录指向每个Tomcat目录私有信息
的位置,就是conf、logs、temp、webapps和work
的父目录。
如果我们想要运行Tomcat 的 多个实例,但是不想安装多个Tomcat软件副本。那么我们可以配置多个工作 目录
,每个运行实例独占一个工作目录,但是共享同一个安装目录
。
tomcat多实例配置
Bootstrap.initClassLoaders()
tomcat的classloader通过conf/catalina.properties
配置,配置内容为:
package.definition=sun.,java.,org.apache.catalina.,org.apache.coyote.,org.apache.tomcat.,org.apache.jasper.
##classloader 相关
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
server.loader=
shared.loader=
//省略....
initClassLoaders()方法逻辑
//org.apache.catalina.startup.Bootstrap
private void initClassLoaders() {
try {
//commonLoader 设置父类加载器为null,则为jdk中的
commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {
//若没有配置文件配置,则取SystemClassLoader。
commonLoader=this.getClass().getClassLoader();
}
//catalinaLoader , sharedLoader 设置父类加载器为 commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
Bootstrap.load()
Bootstrap.load最终调用的是Catalina的load方法.
,主要完成:
- 设置系统变量
- 初始化命名系统
- Digester类,默认将conf/server.xml解析成相应的对象,这个解析很重要,因为是他完成了Connector和Container的初始化工作。
- Server调用init初始化声明周期,其父类LifecycleMBeanBase实现
//org.apache.catalina.startup.Catalina
private void load() {
//当前面代码没有初始化CATALINA_BASE和CATALINA_HOME时,重新设置二者值
initDirs();
//初始化命名系统,即向System.Properties设置"java.naming.factory.initial"为"org.apache.naming.java.javaURLContextFactory"
initNaming();
//创建Digester,主要用于处理xml配置文件,创建Server,初始化Connector和Container
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
//获取配置文件:默认值conf/server.xml,可由command参数-config指定
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource(file.toURI().toURL().toString());
} //省略log,catch...
//省略log,try-catch,部分代码.....
try{
inputSource.setByteStream(inputStream);
digester.push(this);
//解析xml
digester.parse(inputSource);
} //省略log,catch...
getServer().setCatalina(this);
//System.out , System.error
initStreams();
//调用LifecycleBase.init();最终调用自身的initInternal()方法
getServer().init();
}
Digester解析server.xml
创建Server,并初始化Container、Connector之后文章解析。
Boostrap.start()
最终调用的是Catalina.start()
,它主要完成:
- Server加载:Server才是正真的tomcat服务执行者。调用load方法,初始化Connector和Container
- 调用Server的start方法,最终调用的是StandardServer的startInternal方法,调用自己所有的Service的start方法,启动connector和container、excutor的start
- 注册钩子
//org.apache.catalina.startup.Catalina
public void start() {
if (getServer() == null) {
//Server加载
load();
}
try {
//服务启动,//调用LifecycleBase.start();最终调用自身的startInternal()方法
getServer().start();
} //省略 catch,log.....
// 注册shutdown钩子
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
//进入等待状态
if (await) {
//调用Server.await()方法
await();
stop();
}
}
StandardServer类图
- Catalina在调用
init(),start(),stop()
方法时,最终都调用的是LifecycleBase的同名方法,而在LifecycleBase内部又是调用的StandardServer的xxxInternal()
方法。 - Catalina在调用
await()
方法时,则调用的是,StandardServer的同名方法
//org.apache.catalina.core.StandardServer
protected void initInternal() throws LifecycleException {
//省略其他部分.....
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
protected void startInternal() throws LifecycleException {
//省略其他部分.....
// Start our defined Services
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
public void await() {
//server.xml中<Server port="8005" shutdown="SHUTDOWN">
//port默认8005,shutdown默认值为SHUTDOWN
//如果端口是-2,则不进入方法,直接返回。
if( port == -2 ) {
return;
}
//如果端口是-1则进入循环,但是无法通过网络命令退出
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
Thread.sleep( 10000 );
}
} finally {
awaitThread = null;
}
return;
}
//如果不是-1,-2(应该是一个大于0的端口),则会新建一个监听关闭命令的serverSocket;
awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));
awaitThread = Thread.currentThread();
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
//监听关闭连接
Socket socket = serverSocket.accept();
StringBuilder command = new StringBuilder();
InputStream stream =socket.getInputStream();
//匹配关闭命令是否等于"SHUTDOWN"
boolean match = command.toString().equals(shutdown);
if (match) {
break;
}
}
}
StandardService类图
在StandardServerinitInternal(),startInternal()
方法,会循环调用Service的start(),init()
方法,Service的默认实现是org.apache.catalina.core.StandardService
,如下类图
,
最终也会经过LifecycleBase最终调用StandardService的initInternal(),startInternal()
方法。
//org.apache.catalina.core.StandardService
protected void initInternal() throws LifecycleException {
super.initInternal();
//初始化container
if (container != null) {
container.init();
}
//Executors:Connectors线程池
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
//初始化Connectors
synchronized (connectors) {
for (Connector connector : connectors) {
connector.init();
}
}
}
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
//首先启动Container
if (container != null) {
synchronized (container) {
container.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
//其次启动Connectors
synchronized (connectors) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
其中connectors配置在Server.xml中,默认是备注释掉,未开启。
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
</Service>
</Server>
上例表示:Connector配置一个叫做tomcatThreadPool
的线程池,最多可同时启用150个线程,至少要有4个可用线程。
至此,tomat就启动完成了。