tomcat container
ContainerBase的结构
Container是tomcat中容器的接口,通常使用的servlet封装在其子接口Wrapper中。Container共有4个子接口Engine、host、context、Wrapper和一个默认实现类ContainerBase,每个子接口都是一个容器,这四个子容器都有一个对应的StandardXXX实现类,并且这些实现类都继承ContainerBase类。Container还继承Lifecycle接口,ContainerBase间接继承LifecycleBase,所以Engine、Host、Context、Wrapper4个子容器都符合前面文章提的Tomcat生命周期管理模式:
Container的四个子容器
Container的子容器Engine、Host\Context、Wrapper是逐层包含的关系,其中Engine是最顶层,每个Service最多只有一个Engine,Engine里面可以包含多个Host,每个Host下可以有多个Context,每个Context下可以有多个Wrapper:
- Engine:用来管理多个站点,一个service只有一个Engine
- Host:代表一个站点,也叫虚拟主机,通过配置Host可以添加站点
- Context:代表应用程序,对应平时开发的程序
- Wrapper:每个Wrapper封装着一个Servlet
Context和Host的区别是Context表示一个应用,webapps下的每个目录都代表一个应用,而这个webapps是一个站点。
4个容器的配置方法
Engine和Host的配置都在conf/server.xml中,server.xml文件是 tomcat最重要的配置文件,tomcat大部分功能都可以在这个文件配置:
<?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" />
<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>
这里首先定义了一个Server,在8005端口监听“SHUTDOWN”关闭命令;Server中定义了名叫Catalina的service,Service中定义了两个Connector,一个是HTTP协议,一个是AJP协议(用于集成,如与Apache集成);Service中还定义了一个名为Catalina的Engine,Engine中定义了名为localhost的host。
Engine和Host直接用Engine、Host标签定义到相应位置即可。Host中name代表域名,所以可以通过localhost访问,appBase属性指定站点的位置,如默认站点为webapps目录,unpackWARs表示是否自动解压war文件,autoDeploy表示是否自动部署,如果为true在tomcat运行过程中如果webapps加入新应用将自动部署并启动。Host有个Alias子标签,可以通过这个标签定义别名,如果多个域名访问同一个站点可以使用它定义:
<Host name="www.xxx.com" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Alias>xxx.com</Alias>
</Host>
Engine在定义的时候有个defaultHost属性,它表示接收到的域名如果所有的Host的name和Alias都找不到时,使用默认Host,例如我们配置是localhost,如果把这个属性删除,那么使用127.0.0.1就访问不到了。
Context配置有三种方式:
- 文件配置
- 将war应用直接放到Host目录下,tomcat会自动查找并添加到Host中
- 将应用文件夹直接放到Host目录中,tomcat也会自动查找
Context通过配置文件共有五个位置可以配置:
- conf/server.xml文件的Context标签
- conf/[enginename]/[hostname]/目录下以应用命名的xml文件
- 应用自己的/META-INF/context.xml文件
- conf/context.xml文件
- conf/[enginename]/[hostname]/context.xml.default文件
前三个用于配置单独的应用,后两个配置是context共享的,第四个是整个tomcat共享,第五个是对应站点下共享。server.xml在tomcat启动时才会重新加载,所以使用时要慎重。
Wrapper的配置就是我们在web.xml中配置的Servlet,一个Servlet对应一个Wrapper,conf/web.xml可以配置全局的Wrapper,处理Jsp的JspServlet就配置在这里,所以不用配置Jsp就可以处理Jsp请求。
同一service中的所有站点共享Connector,所以监听的端口一样,如果想要监听不同端口,可以通过配置不同的Service实现,service配置在conf/server.xml中。
Container启动
Container的启动时通过init和start完成的,前面文章分析过,这两个方法在启动时会被service调用。Container也是按照tomcat生命周期管理的,init和start方法调用initInternal和startInternal来具体处理。container与tomcat整体结构启动有些区别:
- Container四个子容器有一个共同的父类ContainerBase,Container的initInternal和startInternal方法处理通用内容,具体容器可以添加自己的内容
- 顶层的容器init是被service调用的,子容器的init不是容器中逐层调用,而是在执行start是判断状态没有初始化才会调用
- start方法除了父容器的startInternal方法调用,还在父容器添加子容器的addChild方法调用,因为Context和Wrapper是动态添加的
ContainerBase
ContainerBase的initInternal方法主要初始化ThreadPoolExecutor类型的startStopExecutor属性,用于启动和关闭线程:
protected void initInternal()
throws LifecycleException
{
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue();
this.startStopExecutor = new ThreadPoolExecutor(getStartStopThreadsInternal(), getStartStopThreadsInternal(), 10L, TimeUnit.SECONDS, startStopQueue, new StartStopThreadFactory(getName() + "-startStop-"));
this.startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}
ThreadPoolExecutor继承于Executor用于管理线程池,这里没有设置生命周期的相应状态,如果具体容器也没有设置相应的生命周期状态,那么即使调用了init方法进行初始化,在start启动前也会调用init方法:
ContainerBase的startInternal方法主要做了五件事:
- 如果有Cluster和Realm则调用其start方法
- 调用所有子容器的start方法启动子容器
- 通过管道中的Value的start方法启动管道
- 启动完成后将生命周期状态设置为Lifecycle.STARTING状态
- 启用后台线程定时处理一些事情
protected synchronized void startInternal()
throws LifecycleException
{
this.logger = null;
getLogger();
//如果有Cluster和Realm则启动
Cluster cluster = getClusterInternal();
if ((cluster != null) && ((cluster instanceof Lifecycle))) {
((Lifecycle)cluster).start();
}
Realm realm = getRealmInternal();
if ((realm != null) && ((realm instanceof Lifecycle))) {
((Lifecycle)realm).start();
}
//获取所有子容器
Container[] children = findChildren();
List<Future<Void>> results = new ArrayList();
for (int i = 0; i < children.length; i++) {
//通过线程调用子容器的start方法
results.add(this.startStopExecutor.submit(new StartChild(children[i])));
}
//处理子容器启动线程的Future
boolean fail = false;
for (Future<Void> 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"));
}
//启动管道
if ((this.pipeline instanceof Lifecycle)) {
((Lifecycle)this.pipeline).start();
}
//设置生命周期状态
setState(LifecycleState.STARTING);
//启动后台线程
threadStart();
}
先启动Cluster和Realm,启动是通过直接调用他们的start方法。Cluster用于配置集群,server.xml有注释的参考配置,作用是同步Session,Realm是tomcat的安全域,用于管理资源的访问权限。
子容器通过startStopExecutor调用新线程来启动,这样可以用多个线程同时启动,效率更高,具体启动过程是通过for循环对每个子容器启动一个线程,并将返回的Future保存到List,然后遍历每个Future调用其Future方法。遍历Future主要有两个作用:1、get方法是阻塞的,只有线程处理之后才会往下走,保证管道Pipeline启动之前容器已经完成启动了;2、可以处理启动过程中遇到的异常。
启动子容器的线程类型StartChild说是一个实现了Callable的内部类,主要作用是调用子容器的start方法:
private static class StartChild
implements Callable<Void>
{
private Container child;
public StartChild(Container child)
{
this.child = child;
}
public Void call()
throws LifecycleException
{
this.child.start();
return null;
}
}
startInternal方法是定义在所有容器的父类ContainerBase中的,所以所有容器启动过程中都会调用子容器的start方法来启动子容器。
threadStart方法启动的后台线程是一个while循环,内部定期调用backgroundProcess方法处理一些事情,包含ContainerBase、StandardContext和StandardWrapper三个实现。
Engine
Service会调用容器最顶层的init和start方法,如果使用了Engine就会调用Engine的。Engine默认实现是StandardEngine中的initInternal和startInternal:
protected void initInternal()
throws LifecycleException
{
getRealm();
super.initInternal();
}
protected synchronized void startInternal()
throws LifecycleException
{
if (log.isInfoEnabled()) {
log.info("Starting Servlet Engine: " + ServerInfo.getServerInfo());
}
super.startInternal();
}
Host
Host的默认实现类Standardhost没有重写initInternal,默认调用ContainerBase的initInternal方法,startInternal实现方法如下:
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).newInstance();
getPipeline().addValve(valve);
}
}
catch (Throwable t)
{
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardHost.invalidErrorReportValveClass", new Object[] { errorValve }), t);
}
}
super.startInternal();
}
主要功能是检查Host的管道中有没有指定的Value,如果没有则添加进去。
Host启动除了startInternal方法,还有HostConfig中对应的方法,HostConfig继承自LifecycleListener的监听器,接收到Lifecycle.START_EVENT事件时会调用start方法启动,HostConfig的start方法会检查Host站点配置的位置是否存在以及是不是目录,最后调用deployApps部署应用(xml、war、文件夹三种方式)。
Context
Context的默认实现类StandardContext在startInternal方法调用web.xml中定义的Listener,另外初始化Filter和load-on-startup的Servlet。listenerStart、filterStart和loadOnStartup方法分别调用配置在Listener的contextInitializerd以及Filter和配置了load-on-startup中的Servlet的init方法。
Context和Host一样有一个LifecycleListener类型的监听器ContextConfig,其中configureStart处理CONFIGUR_START_EVENT事件,这个方法调用webConfig,webConfig里面解析web.xml文件,相应创建Wrapper并使用addChild添加到Context里面。
Wrapper
Wrapper没有重写initInternal方法,默认调用ContainerBase的initInternal,startInternal方法如下:
protected synchronized void startInternal()
throws LifecycleException
{
if (getObjectName() != null)
{
Notification notification = new Notification("j2ee.state.starting", getObjectName(), this.sequenceNumber++);
this.broadcaster.sendNotification(notification);
}
super.startInternal();
setAvailable(0L);
if (getObjectName() != null)
{
Notification notification = new Notification("j2ee.state.running", getObjectName(), this.sequenceNumber++);
this.broadcaster.sendNotification(notification);
}
}
主要做了一下工作:
- 用broadcaster发送通知,主要用于JMX
- 调用父类ContainerBase的startInternal方法
- 调用setAvailable方法让Servlet生效
setAvailable方法是wrapper接口中的方法,其作用是设置Wrapper所包含的Servlet有效的起止时间,如果设置的将来时间,调用对应的Servlet就会出错,直到过了所设置的时间后才能正常使用,类型为long,如果设置为Long.MAX_VALUE就一直不可用。
上一篇: Python 爬虫实战 1
下一篇: python爬虫之路【1】
推荐阅读
-
解决centos7中tomcat启动与本机访问问题
-
IntelliJ IDEA部署web项目,Tomcat没有出现Artifacts
-
Apache与Tomcat服务器整合的基本配置方法及概要说明
-
Linux下shell脚本监控Tomcat的状态并实现自动启动的步骤
-
Ubuntu 16.04安装Apache Tomcat的方法
-
CentOS6.5下安装JDK1.7+MYSQL5.5+TOMCAT7+nginx1.7.5环境安装文档
-
request.getSession().getServletContext().getRealPath("upload/" ); 获取不到 tomcat 服务器目录
-
Eclipse中serverRuntimeEnvironment中没有Tomcat选项问题解决办法
-
图解linux安装tomcat(附常用命令)
-
Centos7.3安装和配置Tomcat8