深入浅出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 ...
随机推荐
- python base64 decode incorrect padding错误解决方法
个人觉得原因应该是不同的语言/base64库编码规则不太统一的问题. python中base64串的长度需为4的整数倍,故对长度不为4整数倍的base64串需要用"='补足 如下代码: da ...
- spring入门详细教程(五)
前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...
- Elasticsearch拼音分词和IK分词的安装及使用
一.Es插件配置及下载 1.IK分词器的下载安装 关于IK分词器的介绍不再多少,一言以蔽之,IK分词是目前使用非常广泛分词效果比较好的中文分词器.做ES开发的,中文分词十有八九使用的都是IK分词器. ...
- Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换. 1 背景知识 1.1 什么是调度器 ...
- 我在 Mac 上都用什么
我在 Mac 上都用什么 Homebrew Homebrew 是统一管理 macOS 中应用的最佳方法之一,而且大量优秀的应用都可以在 Homebrew 中找到. 就不做过多介绍了, 有兴趣可以看相关 ...
- layui中,同一个页面动态加载table数据表格
效果图: 前端代码: <div class="layui-fluid" id="record-user" hidden="hidden" ...
- GitHub-暂存区与版本回退
参考博文:廖雪峰Git教程 1. 工作区和暂存区 Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 1.1. 工作区(Working Directory) 就是你在电脑里能看到的目录 ...
- LeetCode算法题-Add Strings(Java实现)
这是悦乐书的第223次更新,第236篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第90题(顺位题号是415).给定两个非负整数num1和num2表示为字符串,返回num ...
- HBase数据库相关基本知识
HBase数据库相关知识 1. HBase相关概念模型 l 表(table),与关系型数据库一样就是有行和列的表 l 行(row),在表里数据按行存储.行由行键(rowkey)唯一标识,没有数据类 ...
- 【项目 · Wonderland】立项报告
[软件工程实践 · 团队项目] 第二次作业 团 队 作 业 原 文:http://www.cnblogs.com/andwho/p/7598662.html Part 0 · 简 要 目 录 Part ...