一 概述
孩子两岁了最近一直不爱吃饭,做什么好吃的她都不感兴趣!每天看着她吃饭心急如焚!情不自禁的在这里发了下牢骚!言归正传吧,前面我们详解了tomcat的整体架构,这里说说下tomcat的启动。
二 tomca启动处理
在平时的编码中,我们启动一个功能都用main函数来启动。tomcat里面也一样,启动的入口也是mian函数。只是我们平时在使用时都是双击startup.bat文件来启动,这文件里面的代码我看的不是太懂,但可以肯定是双击以后它运行了其中的main函数。查阅Bootstrap类中main函数代码,就是启动类。如下:
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
t.printStackTrace();
return;
}
daemon = bootstrap;
}
try {
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);
daemon.load(args);
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
t.printStackTrace();
}
}
1.判断 Bootstrap类型的变量daemon是否为null,为空就创建对象
2.调用bootstrap.init()初始方法
3.根据调用main函数传入的参数数据,比如start,stopd来区分是停止服务还是启动服务
这里看下bootstrap.init()方法处理。代码如下:
public void init()
throws Exception
{
String dir=System.getProperty("user.dir");
// Set Catalina path
setCatalinaHome();//1
setCatalinaBase();//2
initClassLoaders();//3
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.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);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
1.跟进setCatalinaHome和setCatalinaBase方法中,设置catalina.home和catalina.base系统属性值
2.initClassLoaders方法初始化ClassLoader类型的commonLoader,catalinaLoader,sharedLoader三个类加载器变量。
3.通过catalinaLoader类加载器加载org.apache.catalina.startup.Catalina类。并通过反射机制调用Catalina中的setParentClassLoader方法,并传递 sharedLoader类加载器参数
4.同时将创建的Catalina对象赋值给静态的catalinaDaemon属性
跳过main函数中的bootstrap.init()代码继续往下看,根据启动断点知道command的默认值是start,所以流程会走入下面红色框的分支中。
我们先看daemon.load(args)这行代码做了什么事情。
/**
* Load daemon.
*/
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方法。查看Catalina类的load方法代码如下
/**
* Start a new server instance.
*/
public void load() {
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 {
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource("file://" + file.getAbsolutePath());
} catch (Exception e) {
// Ignore
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
// Ignore
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
// Ignore
}
}
if ((inputStream == null) && (file != null)) {
log.warn("Can't load server.xml from " + file.getAbsolutePath());
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
return;
}
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
} catch (Exception e) {
log.warn("Catalina.start using "
+ getConfigFile() + ": " , e);
return;
}
// Stream redirection
initStreams();
// Start the new server
try {
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");
}
上面的load主要做了两件事情
1.通过Digester方式解析tomcat工具下的apache-tomcat-7.0.63_DEV\conf\server.xml文件。打开该xml文件,发里面的节点已经将Server,Service,Connector,Engine,Host的容器关系展现出来了。查看createStartDigester方法它已经指明了各个容器对应的标准实现类路径,以及创建对应的容器对象通过什么方法来注入到父容器中或子容器中。不了解Digester解析XML的处理可以查看 http://blog.csdn.net/caihaijiang/article/details/5944955 ,里面详细讲解了它是如何来解析的。
2.getServer().init()获取到注入的StandardServer对象调用init方法初始化开始。因为StandardServer继承了LifecycleBase类(可以查看章节三中的整体类结构图),所以调用的inti方法是LifecycleBase类中的。如下面代码为LifecycleBase类中的init实现,它最终还是调用了抽象的initInternal方法来完成初始化,同时调用setState方法来设置当前容器的生命状态为“init”。按照抽象模版方法模式调用抽象的initInternal方法其实是具体实现类的initInternal方法,即StandardServer中的。
public synchronized final void init() throws LifecycleException {
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.INIT_EVENT);
}
initInternal();
setState(LifecycleState.INITIALIZED);
}
protected abstract void initInternal() throws LifecycleException;
我们继续查看StandardServer类中的initInternal方法。代码如下:
@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
onameMBeanFactory = register(new MBeanFactory(), "type=MBeanFactory");
// Register the naming resources
onameNamingResoucres = register(globalNamingResources,
"type=NamingResources");
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}
我们重点看最下面的一个for循环处理,它循环遍历了Server容器中的Service类型容器数组,同时也调用了service对象的init方法。service的标准实现是StandardService类,调用它的init方法也是跟StandardServer类的处理方式一样,最终调用的也是StandardService类中的initInternal方法。查看源码如下:
protected void initInternal() throws LifecycleException {
super.initInternal();
if (container != null) {
container.init();
}
// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof LifecycleMBeanBase) {
((LifecycleMBeanBase) executor).setDomain(getDomain());
}
executor.init();
}
// Initialize our defined Connectors
synchronized (connectors) {
for (Connector connector : connectors) {
try {
connector.init();
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.initFailed", connector),
e);
}
}
}
}
以上代码有3部初始化
1.container.init()初始化,这里的container属性指的就是StandardEngine容器对象的初始化。
2.executor.init()初始化,这里是StandardThreadExecutor类的初始化,它实现了Executor接口,重写实现了一套自己的线程管理工具。
3.connector.init()初始化,这里是Connector连接器做初始化操作。看代码里面是做for循环初始化连接器,一个tomcat可以设置多个连接器。
查看connector.init()实际处理的初始化代码如下:
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
IntrospectionUtils.setProperty(protocolHandler, "jkHome",
System.getProperty("catalina.base"));
onameProtocolHandler = register(protocolHandler,
createObjectNameKeyProperties("ProtocolHandler"));
mapperListener.setDomain(getDomain());
onameMapper = register(mapperListener,
createObjectNameKeyProperties("Mapper"));
}
1.新建了一个CoyoteAdapter对象,并将当前的connector对象以构造函数属性传入。
2.protocolHandler属性设置adapter属性。protocolHandler属性是什么时候注入进connector的,查看Connector类的带参构造函数,传入了一个字符串protocol,调用setProtocol(protocol)函数来设置对应的protocolHandlerClassName处理器类。然后通过Class.forName(protocolHandlerClassName);来实例化protocolHandler属性。而这个构造函数是在什么时候调用的呢?翻看通过Digester解析server.xml时,实例化 connector对象时的处理。如下代码:
digester.addRule("Server/Service/Connector",new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",new SetAllPropertiesRule(new String[]{"executor"}));
digester.addSetNext("Server/Service/Connector","addConnector","org.apache.catalina.connector.Connector");
指定的解析规则ConnectorCreateRule类中的begin方法,在创建Connector对象时传入的构造参数就是xml配置中的protocol属性值。所以protocolHandler属性的注入是在Digester解析server.xml创建容器对象时构造的。
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Service svc = (Service)digester.peek();
Executor ex = null;
if ( attributes.getValue("executor")!=null ) {
ex = svc.getExecutor(attributes.getValue("executor"));
}
Connector con = new Connector(attributes.getValue("protocol"));
if ( ex != null ) _setExecutor(con,ex);
digester.push(con);
}
到这里所有相关联的组件在启动时需要的资源已经初始化完毕。
三总结
从进入main函数启动开始,传入参数startd参数,到调用Bootstrap类的daemon.load(args);函数,里面一系列的逻辑处理都在为tomcat各个相互关联的父子容器做资源初始化,为后续启动tomcat做准备。在这一节中比较难理清的是tomcat的类加载器处理和Digester方式解析server.xml文件并注入相关类属性对象。 下一节来研究下tomcat的真正启动处理,即main函数中的daemon.start();中的实现逻辑。对于tomcat资源初始化的调用过程,整理了一个流程图如下