Tomcat分析三—— Container分析
3.1 ContainerBase的结构
Container是Tomcat中容器的接口,通常使用的Servlet就封装在其子接口Wrapper中 。
Container一共有四个子接口Engine、Host、Context、Wrapper和一个默认实现类ContainerBase。每个子接口都是一个容器,四个容器都有对应的StandardXX实现类,且这些实现类都继承COntainerBase类。
Container还继承Lifecycle接口,且ContainerBase间接继承LifecycleBase,所以Engine、Host、Context、Wrapper这四个子容器都符合Tomcat的生命周期管理模式。
3.2 Container的四个子容器
Container的子容器Engine、Host、Context、Wrapper是逐层包含的关系,Engine是最顶层,每个service最多只能有一个Engine,Engine里可以有多个Host,每个Host下可以有多个Context,每个Context下可以有多个Weapper。
- Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine
- Host:代表一个站点,也叫虚拟主机,通过配置Host就可以添加站点 /localhost
- Context:代表一个应用程序localhost/test,对应着平时开发的一个程序,或者一个WEB-INF目录及下面的web.xml文件
- Wrapper:每个Wrapper封装着一个Servlet
3.3 四种容器的配置方法
Engine和Host的配置
都在conf/server.xml文件中,server.xml文件是Tomcat中最重要的配置文件,大部分功能都在这配置
- 首先定义Server,8005端口监听关闭命令"SHUTDOWN"
- Server里定义了名为Cataline的Service
- Service定义了两个Connector:HTTP协议和AJP协议(通过AJP协议和另一个web容器进行交互,主要用于集成)
- Service还定义了名为Catalina的Engine
- Engine里定义了名为localhost的Host
- defaultHost 表示请求的域名如果在所有的Host的name和Alias中都找不到时使用的默认Host,
- Host标签
- name 表示定义的站点可以通过localhost访问
- appBase 指定站点的位置,默认在webapps下,
- unpackWARs 表示是否自动解压war文件
- autoDeploy 表示是否自动部署(运行中新加入webapp目录的应用会自动部署并启动)
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
Context配置
Context有三种配置方法
-
通过文件配置,通过文件配置有五个位置可以配置
-
conf/server.xml文件中的Context标签 只有在Tomcat重启才会重新加载
-
conf/[enginename]/[hostname]/目录下以应用命令的xml文件
-
应用自己的/META-INF/context.xml文件
-
conf/context.xml文件 配置的内容在整个Tomcat*享
-
conf/[enginename]/[hostname]/context.xml.default文件 在对应的host*享
前三个用于配置单独的应用,后两个配置的Context是共享的
-
-
将WAR应用直接放到Host的appBase配置的目录下,Tomcat会自动查找并添加到Host中
-
将应用的文件夹放到HostHost的appBase配置的目录下,Tomcat也会自动查找并添加到Host中
Wrapper配置
就是在web.xml中配置的Servlet,一个Servlet对应一个Wrapper。
也可以在conf/web.xml配置全局的Wrapper,处理Jsp的JspServlet就配置在这里,不需要自己配置Jsp就可以处理Jsp请求了
3.4 Container的启动
Container启动是通过init和start方法来完成的,这两个方法会在Tomcat启动时被Service调用。
Container也是按照Tomcat的生命周期来管理的,init和start方法也会调用initInternal和startInternal方法具体处理,不过Container和Tomcat整体结构启动的过程稍微有些不一样
1. Container的四个子容器有一个共同的父类ContainerBase,定义了Container容器的initInternal和startInternal方法通用处理内容,具体容器还可以添加自己的内容
2. 除了最顶层容器的init被Service调用的,子容器的init方法并不是在容器中逐层循环调用的,而是在执行start方法时通过状态判断还没有初始化才会调用
3. start方法除了在父容器的startInternal方法中调用,还会在父容器的添加子容器的addChild方法中调用,主要是因为Context和Wrapper是动态添加的,
ContainerBase的initInternal
该方法主要初始化ThreadPoolExecutor类型的startStopExecutor属性,用于管理启动和关闭的线程。这里并没有设置生命周期的相应状态,所以如果具体容器也没有设置相应生命周期状态,那么即使已经调用init方法进行初始化,在start进行启动前也会再次调用init方法。
ThreadPoolExecutor继承Executor用于管理线程,特别是Runable类型的线程。
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue =
new LinkedBlockingQueue<Runnable>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
ContainerBase的startInternal
该方法主要做了五件事
-
如果有cluster和Realm则调用其start方法
cluster用于配置集群,作用同步Session;Realm是Tomcat的安全域,同来管理资源的访问权限
-
调用所有子容器的start方法启动子容器
子容器使用stratStopExecutor调用新线程启动的,这样可以用多个线程同时启动,效率更高,具体是通过for循环对每个子容器启动一个线程,并将返回的Future保存在List中,然后遍历每个Future并调用其get方法。
遍历Future有两个作用:
1. 其get方法是阻塞的,只有线程处理完后才会向下走,保证了管道pipeline启动前容器已经启动完成了 2. 可以处理启动过程中遇到的异常
-
调用管道中Value的start方法来启动管道
-
启动完成后将生命周期状态设置为LifecycleState.STARTING状态
-
启用后台线程定时处理一些事
protected synchronized void startInternal() throws LifecycleException { Loader loader = getLoaderInternal(); if ((loader != null) && (loader instanceof Lifecycle)) ((Lifecycle) loader).start(); getLogger(); Manager manager = getManagerInternal(); if ((manager != null) && (manager instanceof Lifecycle)) ((Lifecycle) manager).start(); Cluster cluster = getClusterInternal(); if ((cluster != null) && (cluster instanceof Lifecycle)) ((Lifecycle) cluster).start(); Realm realm = getRealmInternal(); if ((realm != null) && (realm instanceof Lifecycle)) ((Lifecycle) realm).start(); DirContext resources = getResourcesInternal(); if ((resources != null) && (resources instanceof Lifecycle)) ((Lifecycle) resources).start(); //获取所有子容器 Container children[] = findChildren(); List<Future<Void>> results = new ArrayList<Future<Void>>(); for (int i = 0; i < children.length; i++) { results.add(startStopExecutor.submit(new StartChild(children[i]))); } boolean fail = false; //处理子容器 启动线程的Future for (Future<Void> result : results) { try { result.get(); } catch (Exception e) { fail = true; } } //启动管道 if (pipeline instanceof Lifecycle) ((Lifecycle) pipeline).start(); //将生命周期状态设置为LifecycleState.STARTING setState(LifecycleState.STARTING); //启动后台线程 threadStart(); }
启动子容器的线程类型StartChild是实现了Callable的内部类,作用就是调用子容器的start方法
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; } }
threadStart方法启动的后台线程是一个while循环,内部定期调用backgroundProcess方法做些事,间隔时间通过ContainerBase的backgroundProcessorDelay属性来设置,单位事秒,小于0不启动后台线程,其backgroundProcess方法会在父容器的后台线程中调用。
backgroundProcess方法是Container接口中的一个方法,共有三个实现,分别在ContainerBase、StandardContext和StandardWrapper中,ContainerBase提供了所有容器共同的处理过程。
StandardContext和StandardWrapper的backgroundProcess方法除了处理自己相关的业务,也调用ContainerBase中的处理。
ContainerBase的backgroundProcess方法调用了Cluster、Realm和管道的backgroundProcess方法
StandardContext的backgroundProcess方法中对Session过期和资源变化进行了处理
StandardWrapper的backgroundProcess方法对Jsp生成的Servlet定期进行检查
Engine
Service会调用最顶层容器的init和start方法,如果使用了Engine会调用Engine的。Engine的默认实现类StandardEngine中的initInternal和startInternal方法如下
protected void initInternal() throws LifecycleException {
getRealm();
super.initInternal();
}
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
}
它们分别调用ContainerBase中的相应方法,initInternal方法还调用了getRealm方法,如果没有配置Realm,则使用一个默认的NullRealm
public Realm getRealm() {
Realm configured = super.getRealm();
if (configured == null) {
configured = new NullRealm();
this.setRealm(configured);
}
return configured;
}
Host
Host的默认实现类StandardHost没有重写initInternal方法,初始化默认调用ContainerBase的initInternal方法。startInternal方法如下,检查Host的管道中有没有指定的value,没有则添加进去。
检查的value的类型通过getErrorReportValveClass方法获取,它返回errorReportValveClass属性,可以配置,默认是ErrorReportValve
private String errorReportValveClass = “org.apache.catalina.valves.ErrorReportValve”
protected synchronized void startInternal() throws LifecycleException {
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).getDeclaredConstructor().newInstance();
//管道中没有ErrorReportValv则将其加入管道
getPipeline().addValve(valve);
}
}
}
super.startInternal();
}
Host的启动除了startInternal方法还有HostConfig中相应的方法,HostConfig继承子LifecycleListener的监听器(Engine对应的EngineConfig监听器只是做了日志记录),在接受到Lifecycle.START_EVENT事件时会调用start方法启动,HostConfig的start方法会检查配置的Host站点的位置是否存在及是不是目录,最后调用deployApps方法部署应用。
protected void deployApps() {
File appBase = appBase();
File configBase = configBase();
String[] filteredAppPaths = filterAppPaths(appBase.list());
// 部署XML描述文件
deployDescriptors(configBase, configBase.list());
// 部署WAR文件
deployWARs(appBase, filteredAppPaths);
// 部署 文件夹
deployDirectories(appBase, filteredAppPaths);
}
-
XML文件指conf/[enginename]/[hostname]/*.xml文件
-
WAR文件和文件夹是Host站点目录下的,会自动找出来并部署
部署后会将部署的Context通过StandardHost的addChild方法添加到Host里面。
StandardHost的addChild方法会调用父类ContainerBase的addChild方法,其中会调用子类(这里指Context)的start方法来启动子容器
Context
Context的默认实现类StandardContext在startInteral方法中调用了在web.xml定义的Listener,还初始化了其中的Filter和load-on-startup的Servlet
protected synchronized void startInternal() throws LifecycleException {
// 触发listener
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// 初始化Filter
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// 初始化Servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
listenerStart、filterStart和loadOnStartup方法分别调用配置在Listener的contextInitialized方法及Filter和配置了load-on-startup的Servlet的init方法
Context和Host一样也有个LifecycleListener类型的监听器ContextConfig,其中configureStart方法用来处理CONFIGURE_START_CEVENT事件,这个方法里面调用webConfig方法,webConfig方法解析了web.xml文件,相应地创建了Wrapper并使用addChild添加到了Context里面
Wrapper
Wrapper的默认实现类StandardWrapper没有重写initInternal方法,初始化时会默认调用ContainerBase的initInternal方法
protected synchronized void startInternal() throws LifecycleException {
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
super.startInternal();
setAvailable(0L);
if (this.getObjectName() != null) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber++);
broadcaster.sendNotification(notification);
}
}
主要做了三件事
-
用broadcaster发送通知,用于JMX
-
调用了父类ContainerBase中的startInternal方法
-
调用setAvaliable方法让Servlet有效
该方法是Wrapper接口的方法,作用是设置Wrapper所包含的Servlet有效的起始时间,如果设置的是将来的事件,那么调用所对应的Servlet就会产生错误,知道过了所设置的事件后才可以正常调用。
Wrapper没有XXConfig样式的LifecycleListener监听器
上一篇: IDEA全局查找关键字的方法
下一篇: 接口
推荐阅读
-
Oracle数据库安全策略分析 (三)第1/2页
-
Android 图片的三级缓存机制实例分析
-
网站遭遇百度降权最常见的三种原因分析
-
网站排名下降很快该怎么办? 三个方便分析网站降权的解决办法
-
mouseleave和mouseout,mouseenter和mouseover,mouseover和mousemove三类比较分析
-
Tomcat源码分析 (二)----- Tomcat整体架构及组件
-
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
-
Tomcat源码分析 (六)----- Tomcat 启动过程(一)
-
seo日志分析重点在哪里?seo日志分析需要关注的三个重点数据
-
Java虚拟机三:OutOfMemoryError异常分析