Container是一个Tomcat容器的接口,Tomcat有四种容器html
· Enginejava
· Hostgit
· Contextweb
· Wrapperapache
Engine表明整个Catalina的Servlet引擎,Host则表明若干个上下文的虚拟主机。Context则表明一个Web应用,而一个Context则会用有多个Wrapper。Wrapper是一个单独的Servlet。tomcat
下图是几种容器实现的类继承图,咱们能够看到最下层以Standard开头的几个类安全
· StandardEnginesession
· StandardHostapp
· StandardContextdom
· StandardWrapper
以上几个类是Tomcat对几种容器的默认实现。
以上几个类是Tomcat对几种容器的默认实现。
Engine的属性name,是Engine的名字,若是有多个Engine,Engine须要惟一。defaultHost也很是重要,若是一个Engine有多个Host时,若是匹配不到合适的Host时,则须要默认选取一个,也就是defaultHost定义的,它的值为Host的name。
<Engine name="Catalina" defaultHost="localhost"> <RealmclassName="org.apache.catalina.realm.LockOutRealm"> <!--This Realm uses the UserDatabase configured in the global JNDI resources under the key"UserDatabase". Any edits that are performed against thisUserDatabase are immediately available for use by theRealm. --> <RealmclassName="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <!--SingleSignOn valve, share authentication between web applications Documentation at: /docs/config/valve.html--> <!-- <ValveclassName="org.apache.catalina.authenticator.SingleSignOn" /> --> <!-- Access log processes allexample. Documentation at:/docs/config/valve.html Note: The pattern used isequivalent to using pattern="common" --> <ValveclassName="org.apache.catalina.valves.AccessLogValve"directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> </Engine>
Engine还有另一个很是重要的属性叫jvmRoute,它通常用在Cluster里。
假设Cluster是这么配置的,Tomcat1的 conf/server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
Tomcat2的conf/server.xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
在生成SessionID时,jvmRoute会用到的,代码以下:
public class StandardSessionIdGeneratorextends SessionIdGeneratorBase{ @Override publicString generateSessionId(String route) { byterandom[] = newbyte[16]; int sessionIdLength = getSessionIdLength(); //Render the result as a String of hexadecimal digits // Start with enough space forsessionIdLength and medium route size StringBuilderbuffer = new StringBuilder(2 * sessionIdLength + 20); int resultLenBytes = 0; while (resultLenBytes < sessionIdLength) { getRandomBytes(random); for (int j = 0; j < random.length && resultLenBytes < sessionIdLength; j++) { byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2< 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if(route != null&& route.length() > 0) { buffer.append('.').append(route); }else { String jvmRoute =getJvmRoute(); if (jvmRoute != null && jvmRoute.length() > 0) { buffer.append('.').append(jvmRoute); } } returnbuffer.toString(); } }
最后几行代码显示若是在Cluster状况下会将jvmRoute加在sessionID后面。
Host是表明虚拟主机,主要设置appbase目录,例如webapps等。Host中的name表明域名,因此下面的例子中表明的localhost,能够经过localhost来访问。appBase是指该站点所在的目录,默认通常是webapps。unpackWARs这个属性也很重要,通常来讲,一个webapp的发布包有格式各样,例如zip,war等,对于war包放到appBase
下是否自动解压缩,显而易见,当为true时,自动解包。autoDeploy是指是指Tomcat在运行时应用程序是否自动部署。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
Context能够在如下几个地方声明:
1. Tomcat的server.xml配置文件中的<Context>节点用于配置Context,它直接在Tomcat解析server.xml的时候,就完成Context对象的建立。
2. Web应用的/META-INF/context.xml文件可用于配置Context,此配置文件用于配置Web应用对应的Context属性。
3. 可用%CATALINA_HOME%/conf[EngineName]/[HostName]/[Web项目名].xml文件声明建立一个Context。
4. Tomcat全局配置为conf/context.xml,此文件配置的属性会设置到全部的Context中
5. Tomcat的Host级别配置文件为/conf[EngineName]/[HostName]/context.xml.default文件,它配置的属性会设置到某Host下面全部的Context中。
以上5种方法有些是共享的,有些是独享的。其中后面2种是被Tomcat共享的。在实际的应用中,我的很是推荐第三种方法。若是在采用第一种方法,这种方法是有侵入性的,不建议,并且该文件是在Tomcat启动时才加载。对于共享的方法我我的也是不推荐使用的,毕竟在实际的应用中仍是但愿本身的app配置单独出来更合理一些。
Wrapper 表明一个Servlet,它负责管理一个Servlet,包括Servlet 的装载、初始化、执行以及资源回收。Wrapper的父容器通常是Context,Wrapper是最底层的容器,它没有子容器了,因此调用它的addChild 将会抛illegalargumentexception。Wrapper的实现类是StandardWrapper,StandardWrapper还实现了拥有一个Servlet 初始化信息的ServletConfig,由此看出StandardWrapper 将直接和Servlet 的各类信息打交道。
前面的类图讲过,前面提到的容容器都实现或继承了LifeCycle,因此LifeCycle里的几个生命周期一样适用于这里。不过除了继承自LifeCycle以外,几个容器也继承ContainerBase这个类。几个Container的初始化和启动都是经过initInternal和startInternal来实现的。须要的话,各个容器能够实现本身的逻辑。
由于4大容器都继承ContainerBase,咱们看看该类的initInternal和startInternal的实现。
@Override protected void initInternal() throws LifecycleException { reconfigureStartStopExecutor(getStartStopThreads()); super.initInternal(); } /* * Implementation note: If there is ademand for more control than this then * it is likely that the best solutionwill be to reference an external * executor. */ private void reconfigureStartStopExecutor(int threads) { if (threads == 1) { //Use a fake executor if(!(startStopExecutorinstanceof InlineExecutorService)) { startStopExecutor = new InlineExecutorService(); } } else{ //Delegate utility execution to the Service Serverserver = Container.getService(this).getServer(); server.setUtilityThreads(threads); startStopExecutor= server.getUtilityExecutor(); } }
咱们能够看到这里并无设置一些状态。在初始化的过程当中,初始化statStopExecutor,它的类型是java.util.concurrent.ExecutorService。
下面是startInternal的代码,咱们能够看出这里作的事情:
1. 若是cluster和realm都配置后,须要调用它们本身的启动方法。
2. 调用子容器的启动方法。
3. 启动管道。
4. 设置生命周期的状态。
5. 同时启动一些background的监控线程。
@Override protected synchronized void startInternal() throws LifecycleException { // Start our subordinate components, if any logger = null; getLogger(); Cluster cluster = getClusterInternal(); if (cluster instanceof Lifecycle) { ((Lifecycle) cluster).start(); } Realm realm = getRealmInternal(); if (realm instanceof Lifecycle) { ((Lifecycle) realm).start(); } // Start our child containers, if any Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } MultiThrowable multiThrowable = null; for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } } if (multiThrowable != null) { throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"), multiThrowable.getThrowable()); } // Start the Valves in our pipeline (including the basic), if any if (pipeline instanceof Lifecycle) { ((Lifecycle) pipeline).start(); } setState(LifecycleState.STARTING); // Start our thread if (backgroundProcessorDelay > 0) { monitorFuture = Container.getService(ContainerBase.this).getServer() .getUtilityExecutor().scheduleWithFixedDelay( new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
这里首先根据配置启动了Cluster和Realm,启动的方法也很直观,直接调用它们的start方法。Cluster通常用于集群,Realm是Tomcat的安全域,管理资源的访问权限,例如身份认证,权限等。一个Tomcat能够拥有多个Realm的。
根据代码,子容器是使用startStopExecutor来实现的,startStopExecutor会使用新的线程来启动,这样可使用多个线程来同时启动多个子容器,这样在性能上更胜一筹。由于可能有多个子容器,把他们存入到Future的List里,而后遍历每一个Future并调用其get方法。
遍历Future的做用是什么?1,get方法是阻塞的,只有线程处理完后才能继续往下走,这样保证了Pipeline启动以前容器确保调用完成。2,能够处理启动过程当中的异常,若是有容器启动失败,也不至于继续执行下去。
启动子容器调用了StartChild这么一个相似,它的实现以下:
private static class StartChild implements Callable<Void> { private Container child; public StartChild(Container child) { this.child = child; } @Override public Void call() throws LifecycleException { child.start(); return null; } }
这个类也是定义在ContainerBase里的,因此全部容器的启动过程都对调用容器的start方法。
咱们能够看到StartChild实现了Callable接口。咱们知道启动线程,有Runnable和Callable等方式,那么Runnable和Callable的区别在哪里呢?我认为的区别是:
1. 对于实现Runnable,run方法并不会返回任何东西,可是对于Callable,真是能够实现当执行完成后返回结果的。但须要注意,一个线程并不能和Callable建立,尽能够和Runnable一块儿建立。
2. 另一个区别就是Callable的Call方式能够抛出Exception,可是Runnable的run方法这不能够。
根据以上,咱们能够看出为何要用Callable,前面说捕获到异常也正是这个原理。
在这里咱们也看到了Future这个东西。有必要在这里详细解释一下Future的概念。Future用来表示异步计算的结果,它提供了一些方法用来检查计算是否已经完成,或等待计算的完成以及获取计算的结果。计算结束后的结果只能经过get方法来获取。固然,也可使用Cancel方法来取消计算。在回到咱们这里的代码,以下,咱们能够看到结果已经存在result里,经过get方法来获取,前面咱们分析Callable能够抛出异常,这里咱们能够看到有捕获到这些异常的代码。
for (Future<Void> result : results) { try { result.get(); } catch (Throwable e) { log.error(sm.getString("containerBase.threadedStartFailed"), e); if (multiThrowable == null) { multiThrowable = new MultiThrowable(); } multiThrowable.add(e); } }
Engine的默认实现类是StandardEngine,它的初始化和启动会调用initInternal和startInternal。下面是StandardEngine的结构图
初始化和启动的代码分别以下:
@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(); } /** * Start this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void startInternal() throws LifecycleException { // Log our server identification information if (log.isInfoEnabled()) { log.info(sm.getString("standardEngine.start", ServerInfo.getServerInfo())); } // Standard container startup super.startInternal(); }
初始化和启动仍是分别调用了ContainerBase的initInternal·和startInternal。特别要注意的是initInternal额外调用了getRealm获取Realm的信息。那么getRealm的实现以下:
@Override
public Realm getRealm() {
Realm configured = super.getRealm();
// If no set realm has been called - default to NullRealm
// This can be overridden at engine, context and host level
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
咱们能够看出,若是没有realm配置,直接返回默认的NullRealm。
Host的默认实现类是StandardHost,继承图以下。
下面代码只有startInternal,并无initInternal,那是由于StandardHost并无重写initInternal。
代码比较简单,除了调用ContainerBase的startInternal,前面还须要查询Pipeline里的Valve有没有和ErrorReport相关的。若是没有建立Valve一下,而后加到Pipeline里。
protected synchronized void startInternal() throws LifecycleException { // Set error report valve String errorValve = getErrorReportValveClass(); if ((errorValve != null) && (!errorValve.equals(""))) { try { boolean found = false; Valve[] valves = getPipeline().getValves(); for (Valve valve : valves) { if (errorValve.equals(valve.getClass().getName())) { found = true; break; } } if(!found) { Valve valve = (Valve) Class.forName(errorValve).getConstructor().newInstance(); getPipeline().addValve(valve); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString( "standardHost.invalidErrorReportValveClass", errorValve), t); } } super.startInternal(); }
其中默认的ErrorReport Valve是
/** * The Java class name of the default error reporter implementation class * for deployed web applications. */ private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve"
下面是Context的初始化代码,后面调用了NamingResource相关信息。
@Override protected void initInternal() throws LifecycleException { super.initInternal(); // Register the naming resources if (namingResources != null) { namingResources.init(); } // Send j2ee.object.created notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.object.created", this.getObjectName(), sequenceNumber.getAndIncrement()); broadcaster.sendNotification(notification); } }
接下来看看startInternal,这个方法很是长,节选重要代码.
若是resouce没有启动,须要调用resource的启动,接下来是调用web.xml中定义的Listener,另外还须要初始化该配置文件定义的Filter以及load-on-startup的Servlet。
protected synchronized void startInternal() throws LifecycleException { //… … if (ok) { resourcesStart(); } //… … // Configure and call application event listeners if (ok) { 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) { if (!loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail")); ok = false; } }