深入浅出Tomcat/4 - Tomcat容器
Container是一个Tomcat容器的接口,Tomcat有四种容器
· Engine
· Host
· Context
· Wrapper
Engine代表整个Catalina的Servlet引擎,Host则代表若干个上下文的虚拟主机。Context则代表一个Web应用,而一个Context则会用有多个Wrapper。Wrapper是一个单独的Servlet。
下图是几种容器实现的类继承图,我们可以看到最下层以Standard开头的几个类
· StandardEngine
· StandardHost
· StandardContext
· StandardWrapper
以上几个类是Tomcat对几种容器的默认实现。

以上几个类是Tomcat对几种容器的默认实现。
Engine
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
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
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
Wrapper 代表一个Servlet,它负责管理一个Servlet,包括Servlet 的装载、初始化、执行以及资源回收。Wrapper的父容器一般是Context,Wrapper是最底层的容器,它没有子容器了,所以调用它的addChild 将会抛illegalargumentexception。Wrapper的实现类是StandardWrapper,StandardWrapper还实现了拥有一个Servlet 初始化信息的ServletConfig,由此看出StandardWrapper 将直接和Servlet 的各种信息打交道。
Container的启动
前面的类图讲过,前面提到的容容器都实现或继承了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
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
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
下面是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;
}
}
深入浅出Tomcat/4 - Tomcat容器的更多相关文章
- Tomcat就是个容器,一种软件
1.tomcat就是一个容器而已,一个软件,运行在java虚拟机. 2.tomcat是一种能接收http协议的软件,java程序猿自己也可以写出http解析的服务器啊. 3.tomcat支持servl ...
- tomcat(5)servlet容器
[0]README 0.0)本文部分文字描写叙述转自:"深入剖析tomcat",旨在学习 tomcat(5)servlet容器 的基础知识. 0.1)intro to servle ...
- JSP学习 —— 开篇:JSP,servlet容器,Tomcat,servlet容器之间的关系
JSP(JAVA SERVER PAGE)的缩写,其本身就是servlet的简化,是一种动态网页标准,其特点是在HTML代码中嵌入JAVA代码,JSP标签或用户标签来生成网页.至于它为什么会出现,主要 ...
- Session or Cookie?是否需要用Tomcat等Web容器的Session
Cookie是HTTP协议标准下的存储用户信息的工具,浏览器把用户信息存放到本地的文本文件中. Session是基于Cookie实现的. 2011年4月,武汉群硕面试的时候(实习生),面试官也问过这个 ...
- 从头看看Tomcat启动Spring容器的原理
通过带注解Spring Boot可以启动一个web容器,并初始化bean容器.那么Tomcat启动并初始化spring容器的原理是怎样的? Tomcat启动web程序时会创建一对父子容器(图1): 有 ...
- How tomcat works(深入剖析tomcat)servlet容器
How tomcat works (5)servlet容器阅读笔记 第四章阅读了tomcat默认连接器的实现,当时connector中的使用的容器是自定义的容器,也是非常之简单奥,一个人就干完了所有的 ...
- Tomcat 核心组件 Container容器相关
前言 Engine容器 Host容器 前言 Connector把封装了Request对象以及Response对象的Socket传递给了Container容器,那么在Contianer容器中又是怎么样的 ...
- [solr] - solr5.2.1环境搭建 - 使用tomcat做为容器
这里忽略solr其他依赖环境的搭建,这里搭建solr5.2.1.使用Java1.7.0_17,tomcat使用6.0.36版本的. 1.下载solr压缩文件 Solr是Apache基金组织在lucen ...
- 下载Tomcat时Tomcat网站上的core和deployer的区别
下载Tomcat时Tomcat网站上的core和deployer的区别 做JavaEE开发的朋友,无论是学习者还是已经工作的朋友,总是会用到Tomcat这个Servlet容器,那么大家从Tomcat官 ...
- nginx+keepalived+tomcat之tomcat性能调优
body{ font-family: Nyala; font-size: 10.5pt; line-height: 1.5;}html, body{ color: ; background-color ...
随机推荐
- SELinux app权限配置
摘要:1.SEAndroidapp分类SELinux(或SEAndroid)将app划分为主要三种类型(根据user不同,也有其他的domain类型):1)untrusted_app 第三方app,没 ...
- 《node.js权威指南》读书笔记
第一章 node.js介绍 非阻塞型I/O机制 当在访问数据库取得搜索结果的时候,在开始访问数据库之后,数据库返回结果之前,存在一段等待时间. 在传统的单线程处理机制中,在执行了访问数据库的代码之后, ...
- JHipster生成微服务架构的应用栈(二)- 认证微服务示例
本系列文章演示如何用JHipster生成一个微服务架构风格的应用栈. 环境需求:安装好JHipster开发环境的CentOS 7.4(参考这里) 应用栈名称:appstack 认证微服务: uaa 业 ...
- Linux 最小化安装后IP的配置(手动获取静态IP地址)
一.图形化界面配置(假设为电脑A) 如果你的Linux安装有图形化界面,那么通过以下方式来配置: 我这里是有两块网卡,第一个网卡在上篇中已经通过DHCP来配置了:Linux 最小化安装后IP的配置(D ...
- c/c++ 数组 数组的引用,指针数组的引用
c/c++ 数组 知识点 1,数组的声明和初始化,对应代码里的test1和test2 2,char数组,对应代码里的test3 3,数组不可以拷贝和复制,对应代码里的test4 4,指针数组, 数组的 ...
- 重启Zabbix Server
重启zabbix server:systemctl restart zabbix-server #启动服务 systemctl start zabbix-server systemctl start ...
- [Hive_12] Hive 的自定义函数
0. 说明 UDF //user define function //输入单行,输出单行,类似于 format_number(age,'000') UDTF //user define table-g ...
- php学习----错误处理和代码重用
php错误处理 一.错误分类:1.语法错误 2.运行时错误 3.逻辑错误 错误代号(部分): 所有看到的错误代码在php中都被定义为系统常量(可以直接使用) 1)系统错误 E_PARSE:编译错误,代 ...
- Servlet中的request与response
了解这方面的知识可以查看以下博客 https://www.cnblogs.com/zhangyinhua/p/7629221.html https://www.cnblogs.com/zhaojian ...
- Announcing the Updated NGINX and NGINX Plus Plug‑In for New Relic (Version 2)
In March, 2013 we released the first version of the “nginx web server” plug‑in for New Relic monitor ...