上一篇文章,我们分析了Catalina的load()方法,这一篇文章我们就来分析下start()方法
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal("Cannot start server. Server instance is not configured.");
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
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;
}
//...省略了一大坨代码
}
我们可以看到套路和之前的load()方法是一样的,这里主要还是调用了Server实例的start()方法,那我们把目光锁定在StandardServer的start()方法
StardardServer.startInternal()
也是一样的套路,init()的实现在祖先类中,主要的调用方法startInternal的实现也是使用了模板方法模式设计的,我们来看他在StardardServer的实现。
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
如出一辙,还是调用了Service实例的start()方法
StandardService.startInternal()
start()方法依旧是使用了模板方法模式,我们直接看statInternal()方法
@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 (container != null) {
synchronized (container) {
container.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.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.start();
}
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connector), e);
}
}
}
}
这个方法调用了三个子元素的start()方法
我们先来看 container.start();
这几个子元素的start()到需要分别使用一篇文章来讲
container.start();
注意了,这一部分就涉及到解析web.xml的操作了,不过别着急,我们来一层一层一步一步的分析。
当然我们还是要结合server.xml和createStartDigester()来确定Contianer对应的实现类
先给出server.xml与Contianer有关的部分。
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Engine defaultHost="localhost" name="Catalina">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
Realm>
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log." suffix=".txt"/>
<Context docBase="C:\Coding\WorkSpace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\ROOT" path="" reloadable="false"/><Context docBase="C:\Coding\WorkSpace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\SpringMVC" path="/SpringMVC" reloadable="true" source="org.eclipse.jst.j2ee.server:SpringMVC"/>Host>
Engine>
Service>
Server>
只给出Contianer相关的父节点和子节点,叔叔节点以及兄弟节点都略去了。
我们再看下相关的解析规则是怎样定义的。
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
EngineRuleSet的addRuleInstances
@Override
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Engine",
"org.apache.catalina.core.StandardEngine",
"className");
digester.addSetProperties(prefix + "Engine");
//注册监听器
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
digester.addSetNext(prefix + "Engine",
"setContainer",
"org.apache.catalina.Container");
HostRuleSet的addRuleInstances
@Override
public void addRuleInstances(Digester digester) {
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
...... //注册监听器
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
......
}
ContextRuleSet的addRuleInstances
@Override
public void addRuleInstances(Digester digester) {
if (create) {
digester.addObjectCreate(prefix + "Context",
"org.apache.catalina.core.StandardContext", "className");
digester.addSetProperties(prefix + "Context");
} else {
digester.addRule(prefix + "Context", new SetContextPropertiesRule());
}
通过解析规则和xml文件我们可以得出以下结论:
Service里的Container对应的是StandardEngine
StandardEngine->StandardHost->StandardContext (前者是后者的父容器,因为是通过addChild方法添加的,意思很明确,就是添加子容器,方法具体内容后面会讲)
并且StandardEngine,StandardHost都注册了监听器
StandardEngine.startInternal()
container.start()的start()方法依旧是采用了模板方法模式设计,所以我们直接看startInternal方法
@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();
}
你是不是发现这与之前的都不一样,这里只调用了父类的startInternal方法,本身却没有任何实现。
StandardEngine的直接父类和StandardServer及StandService的直接父类是不一样的,前者是ContainerBase(容器类的直接父类,直接继承LifecycleMBeanBase),而后者直接是LifecycleMBeanBase。
我们来看ContainerBase的startInternal()方法
ContainerBase.startInternal()
@Override
protected synchronized void startInternal() throws LifecycleException {
//...省略一坨代码
// Start our child containers, if any
Container children[] = findChildren();
List> results = new ArrayList>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}
先说下这个方法都干了什么:
1.通过findChildren找到Engine的子容器Host
2.使用线程池来start子容器们,子容器start的线程包装在StartChild这个类中。
3.调用 threadStart();来
先看findChildren
/**
* The child Containers belonging to this Container, keyed by name.
*/
protected HashMap children =
new HashMap();
@Override
public Container[] findChildren() {
synchronized (children) {
Container results[] = new Container[children.size()];
return children.values().toArray(results);
}
}
我们发现就是取出children这个属性转化为数组,那么children是什么时候注入的呢。
之前我们说过了StandardHost是通过addChild注入到StandardEngine中的。所以children就是通过addchild注入的
//StandardEngine
/**
* Add a child Container, only if the proposed child is an implementation
* of Host.
*
* @param child Child container to be added
*/
@Override
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
//ContainerBase
@Override
public void addChild(Container child) {
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction dp =
new PrivilegedAddChild(child);
AccessController.doPrivileged(dp);
} else {
addChildInternal(child);
}
}
private void addChildInternal(Container child) {
if( log.isDebugEnabled() )
log.debug("Add child " + child + " " + this);
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException("addChild: Child name '" +
child.getName() +
"' is not unique");
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
所以我们通过findChildren先成功的找到StandardHost数组,然后我们通过StartChild来执行StandardHost的start()操作。
//一个带返回值的线程
private static class StartChild implements Callable<Void> {
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
}
在这个线程里便执行了StandardHost的start()方法了
我们再看 threadStart();
threadStart()
//*启动将定期检查会话超时的后台线程
/**
* Start the background thread that will periodically check for
* session timeouts.
*/
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}
protected class ContainerBackgroundProcessor implements Runnable {
@Override
public void run() {
//...省略一坨代码
try {
if (!threadDone) {
Container parent = (Container) getMappingObject();
ClassLoader cl =
Thread.currentThread().getContextClassLoader();
if (parent.getLoader() != null) {
cl = parent.getLoader().getClassLoader();
}
processChildren(parent, cl);
}
}
//...省略一坨代码
}
protected void processChildren(Container container, ClassLoader cl) {
try {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
container.backgroundProcess();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error("Exception invoking periodic operation: ", t);
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
Container[] children = container.findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i].getBackgroundProcessorDelay() <= 0) {
processChildren(children[i], cl);
}
}
}
}
/**
* Execute a periodic task, such as reloading, etc. This method will be
* invoked inside the classloading context of this container. Unexpected
* throwables will be caught and logged.
*/
@Override
public void backgroundProcess() {
//....省略一大坨代码
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);
}
这一段代码看起来似乎有点长,但是都很好理解,就是开一个后台线程来不断检测环境,时刻检测是不是有reload(热部署,你懂的,不懂的自己去查一个哈)的情况出现等等,就不过多解释了,我们直接看最后一个方法:
fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null)
protected void fireLifecycleEvent(String type, Object data) {
lifecycle.fireLifecycleEvent(type, data);
}
//LifecycleSupport类 ,用来构造事件对象
public void fireLifecycleEvent(String type, Object data) {
//创建一个事件对象实例
LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
LifecycleListener interested[] = listeners;
for (int i = 0; i < interested.length; i++)
interested[i].lifecycleEvent(event);
}
可能你看这段代码会很懵逼,不知道到底在讲些什么鬼东西,那是因为我还有个重要的东西没讲到,那就是监听器的注册,我们之前在使用xml配置文件结合解析规则分析的时候有提到,这些容器都在实例化的时候注册了监听器,我们来回头看看StandardEngine的监听器的注册
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
我们可以知道注册的监听器是org.apache.catalina.startup.EngineConfig
我们再看看LifecycleListenerRule的begin方法
@Override
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Container c = (Container) digester.peek();
Container p = null;
Object obj = digester.peek(1);
if (obj instanceof Container) {
p = (Container) obj;
}
String className = null;
//...日常省略一坨代码
//中间一坨就是为了找到正确的className
// Instantiate a new LifecycleListener implementation object
//实例化监听器类
Class> clazz = Class.forName(className);
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
//将监听器加入到StandardEngine实例中
// Add this LifecycleListener to our associated component
c.addLifecycleListener(listener);
}
addLifecycleListener
这个方法在LifecycleBase中实现
//充当事件
private LifecycleSupport lifecycle = new LifecycleSupport(this);
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
熟悉观察者模式(或者说监听器模式,但是设计模式里好像没有单独交监听器模式的)的同学看到这里就应该能明白上面fireLifecycleEvent的意思了。
这里事件监听器是EngineConfig,事件源是LifecycleEvent,事件对象是StandardEngine
好了,我们现在要看我们的回调方法 interested[i].lifecycleEvent(event);了
这里是EngineConfig的lifecycleEvent,我们发现这个监听器,并没有对PERIODIC_EVENT这种类型的事件做出相应的动作,所以继续往下走,执行他的子容器StandardHost的相应事件。
用同样的方法找到StandardHost的监听器,HostConfig,我们看看这个监听器的lifecycleEvent方法,
HostConfig.lifecycleEvent
@Override
public void lifecycleEvent(LifecycleEvent event) {
// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
}
把无用的代码都删掉了,不多bb,直接进入到check()方法
check()
/**
* Check status of all webapps.
*/
protected void check() {
if (host.getAutoDeploy()) {
// Check for resources modification to trigger redeployment
DeployedApplication[] apps =
deployed.values().toArray(new DeployedApplication[0]);
for (int i = 0; i < apps.length; i++) {
if (!isServiced(apps[i].name))
checkResources(apps[i], false);
}
// Check for old versions of applications that can now be undeployed
if (host.getUndeployOldVersions()) {
checkUndeploy();
}
// Hotdeploy applications
deployApps();
}
}
*/
protected void deployApps() {
File appBase = appBase();//// 在server.xml中的Host标签指定appbase的属性为 webapps
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list());////列出appBase下的所有文件、文件夹,进行过滤
// Deploy XML descriptors from configBase
deployDescriptors(configBase, configBase.list());
// Deploy WARs
deployWARs(appBase, filteredAppPaths);///部署war包
// Deploy expanded folders
deployDirectories(appBase, filteredAppPaths);//部署项目文件夹
}
平时我们发布web的时候可以直接拷贝web的目录或者war压缩包到webapps目录下的.因此这里都一一对应了,deployDirectories该方法对应的就是web目录的发布,deployWARs对应的就是war包的发布方式。
我们重点看deployDirectories的部署方式。
deployDirectories
protected void deployDirectories(File appBase, String[] files) {
if (files == null)
return;
ExecutorService es = host.getStartStopExecutor();
//为什么要使用Callable而不是Runnable呢,我想应该是想得到线程运行的结果是否有报错把。如果得不到返回,证明就是有错误,就可以抛出异常,让异常在我们想要抛出的地方抛出
List> results = new ArrayList>();
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
ContextName cn = new ContextName(files[i], false);
if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
continue;
results.add(es.submit(new DeployDirectory(this, cn, dir)));
}
}
for (Future> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString(
"hostConfig.deployDir.threaded.error"), e);
}
}
}
DeployDirectory
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;
public DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}
@Override
public void run() {
config.deployDirectory(cn, dir);
}
}
一个用来部署的线程
deployDirectory
public static final String ApplicationContextXml = "META-INF/context.xml";
protected void deployDirectory(ContextName cn, File dir) {
//...省略了一大段代码,上面是如果META-INF/context.xml存在的时候实例化StandardContext
//****重点在这
else {
context = (Context) Class.forName(contextClass).newInstance();
}
Class> clazz = Class.forName(host.getConfigClass());
LifecycleListener listener =
(LifecycleListener) clazz.newInstance();
context.addLifecycleListener(listener);
context.setName(cn.getName());
context.setPath(cn.getPath());
context.setWebappVersion(cn.getVersion());
context.setDocBase(cn.getBaseName());
host.addChild(context);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("hostConfig.deployDir.error",
dir.getAbsolutePath()), t);
} finally {
//....省略了一堆代码
}
}
先说下这个方法的作用吧:
如果META-INF/context.xml存在,Context实例对象,由XML文件中的配置来实例化,感兴趣的可以看源码,方式就是接着解析server.xml的套路。
如果没有这个配置文件的存在,就按我们代码给出的方式给出,代码很简单,就不解释了,要注意,在这里,已经将StandardContext设置成了StandardHost的子容器了,并且也给StandardContext注册了监听器了。这里是很重要的,StandardContext和StandardHost的关系终于建立了。
到这里我们就把StandardEngine的start()方法分析完了。
下一篇文章我们将分析StandardHost的start()方法,正式揭开web.xml的处理过程。
从server.xml文件可以知道Host的父容器是Engine,而从org.apache.catalina.startup.Catalina类的createStartDigester可以知道server.xml的Engine标签创建的是org.apache.catalina.core.StandardEngine类的实例.从上一章分析可以知道StandardEngine的启动是在StandardService类的startInternal方法里面启动的,部分代码如下: