欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1. Catalina介绍
Catalina包含了前面讲到的所有容器组件,以及后续章节将会涉及的安全、会话、集群、部署、管理等Servlet容器架构的各个方面。它通过松耦合的方式集成Coyote,以完成按照请求协议进行数据读写。同时,它还包括我们的启动入口、Shell程序等。如果以一个简单的模块依赖图来描述Catalina在整个Tomcat中的位置,如下所示:
Tomcat本质上是一款Servlet容器,因此Catalina是Tomcat的核心,其他模块均为Catalina提供支撑。通过Coyote模块提供链接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。
2. Digester解析
- Digester是一款用于
将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。Digester相对于SAX提供了更加友好的接口,隐藏了xml节点具体层次的细节,让开发者能加载专注于处理过程。 - Digester最早是Web框架Apache Struts的一部分,后来由于其通用性移植到了Apache Common项目中。
2.1 Digester解决了SAX哪些问题
SAX基于事件解析Xml,最重要的就是要编写一个解析器类。
public class SaxHandler extends DefaultHandler {
/**
* 解析一个xml标签字符时触发回调
(该触发回调发生在startElement之后endElement之前)
* @param ch xml文档完整字符数组
* @param start 当前标签中字符在ch开始位置
* @param length 当前标签中字符的长度
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length){
..省略实现
}
/**
* 解析一个xml标签结束时触发回调
* @param uri
* @param localName
* @param name 当前标签的名称
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String name){
..省略实现
}
/**
* 解析一个xml标签开始时触发回调
* @param uri
* @param localName
* @param name 当前标签的名称
* @param attributes 标签中的属性对象
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
..省略实现
}
/**
* 解析一个xml文件开始时回调
*/
@Override
public void startDocument() throws SAXException{
..省略实现
}
/**
* 解析一个xml文件结束时回调
*/
@Override
public void endDocument() throws SAXException{
..省略实现
}
2.1.1 SAX缺陷
- 如果解析XML上不同标签节点存在依赖关系时,
需要开发者自行维护依赖关系,说具体就是如果需要子标签对象手动设置到父标签属性中,子标签需要程序开发者手动保存下来。 - 不同xml的标签属性,字符都不同,而SAX只提供了所有标签通用回调事件,不能针对不同标签定制。
2.2 Digester设计
-
Digester继承DefaultHandler表明Digester是对SAX的扩展实现
-
Digester内部存类型为Rules的属性,Rules为了一个其实现类为RulesBase,RulesBase内部维护HashMap,其中key对应匹配xml规则的字符串,value表示表示针对此xml规则解析规则列表。每个解析规则使用Rule类来表示。
public class Digester extends DefaultHandler2 {
...省略代码
protected Rules rules = null;
...省略代码
}
public class RulesBase implements Rules {
...省略代码
//String 表示匹配xml匹配规则(可以用正则表达式)
//List<Rule> 表示针对对应xml规则解析规则列表
protected HashMap<String,List<Rule>> cache = new HashMap<>();
...省略代码
- 同时Digester
内部维护了一个栈数据结构,用来处理当前解析的xml标签节点,解析前会将xml标签对象入栈(push),解析后会将xml标签对象出栈(pop),栈最顶部的对象永远都是现在正在解析的对象。这样就可以将有父子关系的节点对象在栈中保存下来。
2.3 对象栈
Digester的对象栈(Digester同名类)主要在匹配模式满足时,由处理规则进行操作。它提供了常见的栈操作。
- clear:清空对象栈。
- peek:该操作有数个重载方法,可以实现得到位于栈顶部的对象或者从顶部数第n个对象,但是不会将对象从栈中移除。
- pop:将位于栈顶部的对象移除并返回。
- push:将对象放到栈顶部。
Digester的设计模式是指,在文件读取过程中,如果遇到一个XML节点的开始部分,则会触发处理规则事件创建Java对象,并将其放入栈。当处理该节点的子节点时,该对象都维护在栈中。当遇到该节点的结束部分时,该对象将会从栈中取出并清除。当然,这种设计模式需要解决几个问题,这些问题及Digester的解决方案如下。
-
如何在创建的对象之间建立关联?最终得到的结果应该是一个分层次的Java对象树。
Digester提供了一个处理规则实现(SetNextRule),该规则会调用位于栈顶部对象之后对象(即父对象)的某个方法,同时将顶部对象(子对象)作为参数传入。通过此种方式可以很容易在XML各Java对象之间建立父子关系,无论是一对一还是一对多的关系。 -
如何持有创建的首个对象,即XML的转换结果?从上面的对象创建过程可知,当XML转换结束时,由于遇到了XML节点的结束部分,对象将从栈中移除。Digester对于曾经放入栈中的第一个对象将会持有一个引用,同时作为parse()方法的返回值。还有一种方式,可以在调用parset()方法之前,传入一个已创建的对象引用,Digester会动态地为这个对象和首个创建的对象建立父子关系。通过这种方式,
传入的对象将会维护首个创建对象的引用以及所有子节点,当然传入对象也会在调用parse()方法时返回。Tomcat创建Servlet容器时采用的是后者。
2.4 xml匹配模式
Digester主要特征是自动遍历XML文档,而使开发者不必关注解析过程。与之对应,需要确定当读取到某个约定的XML节点时需要执行何种操作。Digester通过匹配模式指定相关约定。Digester的匹配模式非常简单,具体如下所示。
| 匹配模式 | XML节点 | 描述 |
|---|---|---|
| a | <a> | 匹配所有名字为“a”的根节点,注意嵌套的同名子节点无法匹配 |
| a/b | <a><b></b>/a> | 匹配所有父节点为根节点“a”的名称为“b”的节点 |
例子:
<a>
<b>
<c></c>
</b>
<b>
<c></c>
<c></c>
<c></c>
</b>
</a>
a匹配<a>标签
a/b匹配<a><b>
a/b/c匹配<a><b><c>
*/b 匹配<a><b>
当然,匹配模式还支持模糊匹配,如果我们希望所有节点都采用同一个处理规则,那么直接指定匹配规则为“*”即可,我们还可以指定“*b”来处理所有的名称为“b”的节点,而不限制其层次或者上级节点的名称。
当同一个匹配模式指定多个处理规则,或者多个匹配规则匹配同一个节点时,均会出现一个节点执行多个处理规则的情况。此时,Digester的处理方式是,开始读取节点时按照注册顺序执行处理规则,而完成读取时按照反向顺序执行,即先进后出的规则。
2.5 处理规则
匹配模式确定了何时触发处理操作,而处理规则则定义了模式匹配时的具体操作。处理规则需要实现接口org.apache,commons.digester..Rule,该接口定义了模式匹配时触发的事件方法。
-
begin():当读取到匹配节点的开始部分时调用,会将该节点的所有属性作为从参数传入。 -
body():当读取到匹配节点的内容时调用,注意指的不是子节点,而是嵌入内容为普通文本。 -
end():当读取到匹配节点的结束部分时调用,如果存在子节点,只有当子节点处理完毕后该方法才会被调用。 -
finish():当整个parse()方法完成时调用,多用于清楚临时数据和缓存数据
2.5.1 给指定标签设置规则
-
创建一个定义规则类,该类需要继承Rule
-
调用digester.addRule API 函数
public class ConnectorCreateRule extends Rule {
@Override
public void begin(String namespace, String name, Attributes attributes){
//do something
}
@Override
public void begin(String namespace, String name, Attributes attributes){
//do something
}
}
//表示匹配到<Server><Service><Connector>结构的标签时,使用创建的自定义接口
digester.addRule("Server/Service/Connector",new ConnectorCreateRule());
2.5.2 为单个标签添加规则组(多个标签规则)
当需要某个xml规则添加多个规则时可以使用RuleSet
-
创建一个RuleSet实现类,该类需要继承RuleSetBase
-
构造方法中需要定义xml规则
-
实现addRuleInstances方法对传入的规则添加规则
public class MyRuleSet
extends RuleSetBase {
public MyRuleSet()
{
this("");
}
public MyRuleSet(String prefix)
{
super();
this.prefix = prefix;
this.namespaceURI = "http://www.mycompany.com/MyNamespace";
}
protected String prefix = null;
public void addRuleInstances(Digester digester)
{
digester.addObjectCreate( prefix + "foo/bar",
"com.mycompany.MyFoo" );
digester.addSetProperties( prefix + "foo/bar" );
}
}
digester.addRuleSet( new MyRuleSet( "baz/" ) );
2.5.3 内置的规则接口
Digester内置了一些规则,可以调用使用DigesterAPI 直接调用,给指定xml添加内置规则。
我们可以通过Digester类的addRule()方法为某个匹配模式指定一个处理规则,同时可以根据需要实现自己的规则。针对大多数常见的场景,Digester为我们提供了默认的处理规则实现类,如表所示(注意Tomcat并未包含表中列出的所有的规则类)。
| 规则类 | 描述 |
|---|---|
| ObjectCreateRule | 当begin()方法调用时,该规则会将指定的Java类实例化,并将其放入对象栈。具体的Java类可由该规则的构造方法传入,也可以通过当前处理XML节点的某个属性指定,属性名称通过构造方法传人。当end()方法调用时,该规则创建的对象将从栈中取出 |
| FactoryCreateRule | ObjectCreateRule规则的一个变体,用于处理Java类无默认构造方法的情况,或者需要在Digester处理该对象之前执行某些操作的情况 |
| SetPropertiesRule | 当begin()方法调用时,Digester使用标准的Java Bean属性操作方式(setter)将当前XML节点的属性值设置到栈顶部的对象中(Java Bean)属性名与XML节点属性名匹配) |
| SetPropertyRule | 当begin()方法调用时,Digester会设置栈顶部对象指定属性的值,其中属性名和属性值分别通过XML节点的两个属性指定 |
SetNextRule | 当end()方法调用时,Digester会找到位于栈顶部对象之后的对象调用指定的方法,同时将栈顶部对象作为参数传入,用于设置父对象的子对象,以在栈对象之间建立父子关系,从而形成对象树 |
| SetTopRule | 与SetNextRule对应,当end()方法调用时,Digester会找到位于栈顶部的对象,调用其指定方法,同时将位于顶部对象之后的对象作为参数传入,用于设置当前对象的父对象 |
| CallMethodRule | 该规则用于在end()方法调用时执行栈顶部对象的某个方法,参数值由CallParamRule获取 |
| CallParamRule | 该规则与CallMethodRule配合使用,作为其子节点的处理规则创建方法参数,参数值可取自某个特殊属性,也可以取自节点的内容 |
| NodeCreateRule | 用于将XML文档树的一部分转换为DOM节点,并放入栈 |
2.6 Digester常用API
public void setValidating(boolean validating) // 是否根据DTD校验XML
public void push(Object object) // 将对象压入栈
public Object peek() // 获取栈顶对象
public Object pop() // 弹出栈顶对象
public Object parse(InputSource input) // 解析输入源
public void addRule(String pattern, Rule rule) //针对指定xml标签设置规则解析器
2.7 示例程序
<?xml version="1.0" encoding="utf-8" ?>
<department name="deptname001" code="deptcode001">
<user name="username001" code="usercode001"></user>
<user name="username002" code="usercode002"></user>
<extension>
<property-name>director</property-name>
<property-value>joke</property-value>
</extension>
</department>
public class Department {
private String name;
private String code;
private Map<String, String> extension = new HashMap<String, String>();
private List<User> users = new ArrayList<User>();
public void addUser(User user){
this.users.add(user);
}
public void putExtension(String name,String value){
this.extension.put(name,value);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Map<String, String> getExtension() {
return extension;
}
public void setExtension(Map<String, String> extension) {
this.extension = extension;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
public class User {
private String name;
private String code;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
public class DigesterRule {
public Department execute(String filePath) throws Exception {
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
// addObjectCreate方法的意思是碰到xml文件中的department节点则创建一个Department对象
digester.addObjectCreate("department", "com.wuhao.web.tomcat.digester.Department");
// addSetProperties方法的意思是根据department节点中的属性信息调用相应属性的setter方法
digester.addSetProperties("department");
// addObjectCreate方法的意思是碰到xml文件中的department节点则创建一个Department对象
digester.addObjectCreate("department/user", "com.wuhao.web.tomcat.digester.User");
digester.addSetProperties("department/user");
digester.addSetNext("department/user", "addUser", "com.wuhao.web.tomcat.digester.User");
digester.addCallMethod("department/extension", "putExtension", 2);
digester.addCallParam("department/extension/property-name", 0);
digester.addCallParam("department/extension/property-value", 1);
URL url = this.getClass().getClassLoader().getResource(filePath);
return (Department) digester.parse(new File(url.getFile()));
}
}
public class Test {
@org.junit.Test
public void testJavaRule() throws Exception {
Department department = new DigesterRule().execute("tomcat/department.xml");
System.out.println(department);
}
}
3. 采用Digester解析Server对象
路径:Bootstrap.main -> Bootstrap.load(args) -> Catalina.load()反射调用
// catalina的加载信息
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Parse main server.xml
// 解析服务器的server.xml文件 使用Digester 技术进行xml文档对象解析
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
...
}
接下来看,createStartDigester()方法:
protected Digester createStartDigester() {
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
// Ignore className on all elements
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
// Ignore Connector attribute used internally but set on Server
List<String> connectorAttrs = new ArrayList<>();
connectorAttrs.add("portOffset");
fakeAttributes.put(Connector.class, connectorAttrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true);
// Configure the actions we will be using
// 1. 创建Server实例
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");
digester.addSetProperties("Server");
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");
// 2.创建全局J2EE企业命名上下文
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
// 3.为Server添加生命周期监听器
digester.addRule("Server/Listener",
new ListenerCreateRule(null, "className"));
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 4.构建Service实例
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
// 5.为Service添加生命周期监听器
digester.addObjectCreate("Server/Service/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
//Executor
// 6.为Service添加Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor");
// 7.为Service添加Connector
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addSetProperties("Server/Service/Connector",
new String[]{"executor", "sslImplementationName", "protocol"});
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
digester.addRule("Server/Service/Connector", new AddPortOffsetRule());
// 8.为Connector添加虚拟主机SSL配置
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new CertificateCreateRule());
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/Certificate", new String[]{"type"});
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"setOpenSslConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"addCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
// 9.为Connector添加生命周期监听器
digester.addObjectCreate("Server/Service/Connector/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 10.为Connector添加升级协议
digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol");
// Add RuleSets for nested elements
// 11.添加子元素解析规则
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
return digester;
}
通过上面解析就可以将conf/Server.xml文件全部解析成对应类和对象,包含父子关系。 核心步骤如下:
-
创建Server实例
- Catalina中Server的默认实现类为
org.apache.catalina.core.StandardServer,但是我们可以通过属性className:指定自己的实现类。Digester创建Server实例后,设置Server的相关属性,并将其设置到Catalina对象中(调用setServer)
- Catalina中Server的默认实现类为
-
创建全局J2EE企业命名上下文
- Catalina根据GlobalNamingResources配置创建全局的J2EE企业命名上下文(JNDI),设置属性并将其设置到Server实例当中(setGlobalNamingResources)
-
为Server添加生命周期监听器
- Server元素支持配置Listener节点,用于为当前的Server实例添加LifecycleListener监听器,具体的监听器类型由className属性指定。Catalina默认配置了5个监听器, |类|描述| |--|--| |AprLifecycleListener|在Server初始化之前加载APR库,并于Server停止之后销段 |VersionLoggerListener|在Server初始化之前打印操作系统、JVM以及服务器的版本信息| |JreMemoryLeakPreventionListener|在Server初始化之前调用,以解决单例对象创建导致的VM内存泄露问题以及锁文件问题| |GlobalResourcesLifecycleListener|在Server)启动时,将NDI资源注册为MBean进行管理| |ThreadLocalLeakPreventionListener|用于在Context停止时重建Executor池中的线程,避免导致内存泄露|
-
构建Service实例
- 为Server添加Service实例。Catalina默认的Service实现为org.apache.catalina.core,StandardService,同时,我们也可以通过className属性指定自己的实现类。创建完成后,通过addService()方法添加到Server实例中。
-
为Service添加生命周期监听器
- 具体监听器类由className属性指定。默认情况下,Catalina未指定Service监听器。
-
为Service添加Executor
- 默认实现为org,apache.catalina,core,StandardThreadExecutor,同样也可以通过className属性指定自己的实现类。通过该配置我们可以知道,Catalina共享Exector的级别为Service.Catalina默认情况下未配置Executor,即不共享。
-
为Service添加Connector
- 同时设置相关属性。注意设置属性时,将executor和sslImplementationName,属性排除。因为在Connector创建时(即ConnectorCreateRule类中),会判断当前是否指定了executor.属性,如果是,则从Service中查找该名称的executor并设置到Connector中。同样,Connector创建时,也会判断是否添加了sslImplementationName属性,如果是,则将属性值设置到使用的协议中,为其指定一个SSL实现。
-
为Connector添加虚拟主机SSL配置
-
为Connector添加生命周期监听器
- 具体监听器类由className,属性指定。默认情况下,Catalina未指定Connector!监听器。
-
为Connector添加升级协议
- 用于支持HTTP/2,这是8.5.6和9.0版本新增的配置
-
添加子元素解析规则
- 此部分指定了Servlet容器相关的各级嵌套子节点的解析规则,而且每类嵌套子节点的解析封装为一个RuleSet,包括GlobalNamingResources、Engine、Host、Contextl以及Cluster的解析。接下来,我们将重点解析Engine、Host和Context。
3.1 Engine 解析
代码位于EngineRuleSet.addRuleInstances() 方法,源码如下:
public void addRuleInstances(Digester digester) {
// 1.创建Engine实例
digester.addObjectCreate(prefix + "Engine",
"org.apache.catalina.core.StandardEngine",
"className");
digester.addSetProperties(prefix + "Engine");
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
digester.addSetNext(prefix + "Engine",
"setContainer",
"org.apache.catalina.Engine");
//Cluster configuration start
// 2.为Engine添加集群配置
digester.addObjectCreate(prefix + "Engine/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Cluster");
digester.addSetNext(prefix + "Engine/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
// 4.为Engine添加生命周期
digester.addObjectCreate(prefix + "Engine/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Listener");
digester.addSetNext(prefix + "Engine/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 5. 为Engine添加安全配置
digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));
digester.addObjectCreate(prefix + "Engine/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Valve");
digester.addSetNext(prefix + "Engine/Valve",
"addValve",
"org.apache.catalina.Valve");
}
核心步骤如下:
- 创建Engine实例
- 创建Engine实例,并将其通过setContainer()方法添加到Service实例,Catalina默认实现为org.apache.catalina..core.StandardEngine。同时,还为Engine添加了一个生命周期监听器EngineConfig。注意,此类是在创建时默认添加的,并非由server.xml配置实现。该监听器用于打印Engine,启动和停止日志。
- 为Engine添加集群配置
- 具体集群实现类由className属性指定。
- 为Engine添加生命周期
- 与EngineConfig不同,此部分监听器由server.xml配置。默认情况下,Catalina未指定Engine监听器。
- 为Engine添加安全配置
- 添加安全配置,以及拦截器Value,具体拦截器类由classname属性指定。
3.2 Host解析
HostRuleSet.addRuleInstances() 方法,源码如下:
public void addRuleInstances(Digester digester) {
// 1.创建Host实例
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
//Cluster configuration start
// 2.为Host添加集群
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
//Cluster configuration end
// 3.为Host 添加生命周期管理
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 4.为Host添加安全配置
digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
}
核心步骤如下:
- 创建Host实例
- 创建Host实例,并将其通过addChild()方法添加到Engine上,Catalina默认实现为org.apache,catalina.core,StandardHost。同时,还为Host添加了一个生命周期监听器HostConfig,同样,该监听器由Catalina默认添加,而不是由server.xml配置。该监听器在Web应用部署过程中做了大量工作,后续我们会进一步讲解。此外,通过Alis,Host还支持配置别名。
- 为Host添加集群
- 由此可知,集群配置既可以在Engine级别,也可以在Host级别。
- 为Host 添加生命周期管理
- 与HostConfig不同,此部分监听器由server.xml配置。默认情况下,Catalina未指定Host监听器。
- 为Host添加安全配置
- 为Host添加安全配置(具体见RealmRuleSet,.详情请参见第9章)以及拦截器Valve,具体的拦截器类由className属性指定。Catalina为Host默认添加的拦截器为AccessLog Valve,即用于记录访问日志。
3.3 Context解析
Catalinal的Context配置并非来源一处,此处仅指server.xml中的配置。在多数情况下,我们并
不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基
础进行解析创建,具体过程我们随后会详细讲解。当然,如果我们通过IDE(如Eclipse)启动Tomcat
并部署Web应用,其Context配置将会被动态更新到server.xml中。
ContextRuleSet..addRuleInstances() 方法,源码如下:
public void addRuleInstances(Digester digester) {
// 1.Context实例化
if (create) {
digester.addObjectCreate(prefix + "Context",
"org.apache.catalina.core.StandardContext", "className");
digester.addSetProperties(prefix + "Context");
} else {
digester.addSetProperties(prefix + "Context", new String[]{"path", "docBase"});
}
if (create) {
digester.addRule(prefix + "Context",
new LifecycleListenerRule
("org.apache.catalina.startup.ContextConfig",
"configClass"));
digester.addSetNext(prefix + "Context",
"addChild",
"org.apache.catalina.Container");
}
// 2.为Context添加生命周期监听器
digester.addObjectCreate(prefix + "Context/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Listener");
digester.addSetNext(prefix + "Context/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
// 3.为Context 指定类加载器
digester.addObjectCreate(prefix + "Context/Loader",
"org.apache.catalina.loader.WebappLoader",
"className");
digester.addSetProperties(prefix + "Context/Loader");
digester.addSetNext(prefix + "Context/Loader",
"setLoader",
"org.apache.catalina.Loader");
// 4.为Context添加会话管理器
digester.addObjectCreate(prefix + "Context/Manager",
"org.apache.catalina.session.StandardManager",
"className");
digester.addSetProperties(prefix + "Context/Manager");
digester.addSetNext(prefix + "Context/Manager",
"setManager",
"org.apache.catalina.Manager");
digester.addObjectCreate(prefix + "Context/Manager/Store",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Manager/Store");
digester.addSetNext(prefix + "Context/Manager/Store",
"setStore",
"org.apache.catalina.Store");
digester.addObjectCreate(prefix + "Context/Manager/SessionIdGenerator",
"org.apache.catalina.util.StandardSessionIdGenerator",
"className");
digester.addSetProperties(prefix + "Context/Manager/SessionIdGenerator");
digester.addSetNext(prefix + "Context/Manager/SessionIdGenerator",
"setSessionIdGenerator",
"org.apache.catalina.SessionIdGenerator");
// 5.为Context添加初始化参数
digester.addObjectCreate(prefix + "Context/Parameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
digester.addSetProperties(prefix + "Context/Parameter");
digester.addSetNext(prefix + "Context/Parameter",
"addApplicationParameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
// 6.为Context添加安全配置以及Web资源配置
digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));
digester.addObjectCreate(prefix + "Context/Resources",
"org.apache.catalina.webresources.StandardRoot",
"className");
digester.addSetProperties(prefix + "Context/Resources");
digester.addSetNext(prefix + "Context/Resources",
"setResources",
"org.apache.catalina.WebResourceRoot");
digester.addObjectCreate(prefix + "Context/Resources/CacheStrategy",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/CacheStrategy");
digester.addSetNext(prefix + "Context/Resources/CacheStrategy",
"setCacheStrategy",
"org.apache.catalina.WebResourceRoot$CacheStrategy");
digester.addObjectCreate(prefix + "Context/Resources/PreResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PreResources");
digester.addSetNext(prefix + "Context/Resources/PreResources",
"addPreResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/JarResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/JarResources");
digester.addSetNext(prefix + "Context/Resources/JarResources",
"addJarResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/PostResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PostResources");
digester.addSetNext(prefix + "Context/Resources/PostResources",
"addPostResources",
"org.apache.catalina.WebResourceSet");
// 7.为Context添加资源链接
digester.addObjectCreate(prefix + "Context/ResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink");
digester.addSetProperties(prefix + "Context/ResourceLink");
digester.addRule(prefix + "Context/ResourceLink",
new SetNextNamingRule("addResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink"));
// 8.为Context 添加Value
digester.addObjectCreate(prefix + "Context/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Valve");
digester.addSetNext(prefix + "Context/Valve",
"addValve",
"org.apache.catalina.Valve");
// 9.为Context 添加守护资源配置
digester.addCallMethod(prefix + "Context/WatchedResource",
"addWatchedResource", 0);
digester.addCallMethod(prefix + "Context/WrapperLifecycle",
"addWrapperLifecycle", 0);
digester.addCallMethod(prefix + "Context/WrapperListener",
"addWrapperListener", 0);
digester.addObjectCreate(prefix + "Context/JarScanner",
"org.apache.tomcat.util.scan.StandardJarScanner",
"className");
digester.addSetProperties(prefix + "Context/JarScanner");
digester.addSetNext(prefix + "Context/JarScanner",
"setJarScanner",
"org.apache.tomcat.JarScanner");
digester.addObjectCreate(prefix + "Context/JarScanner/JarScanFilter",
"org.apache.tomcat.util.scan.StandardJarScanFilter",
"className");
digester.addSetProperties(prefix + "Context/JarScanner/JarScanFilter");
digester.addSetNext(prefix + "Context/JarScanner/JarScanFilter",
"setJarScanFilter",
"org.apache.tomcat.JarScanFilter");
// 10.为Context添加Cookie处理器
digester.addObjectCreate(prefix + "Context/CookieProcessor",
"org.apache.tomcat.util.http.Rfc6265CookieProcessor",
"className");
digester.addSetProperties(prefix + "Context/CookieProcessor");
digester.addSetNext(prefix + "Context/CookieProcessor",
"setCookieProcessor",
"org.apache.tomcat.util.http.CookieProcessor");
}
核心步骤如下:
- Context实例化
- Context的解析会根据create属性的不同而有所区别,这主要是由于Context来源于多处。通过server.xml配置Contexth时,create为true,因此需要创建Context实例;而通过HlostConfig自动创建Contexth时,create为false,此时仅需要解析子节点即可。Catalina提供的Context实现类为org.apache.catalina.core,StandardContext。Catalina在创建Context实例的同时,还添加了一个生命周期监听器ContextConfig,用于详细配置Context,如解析web.xml等,相关的内容我们随后会详细讲解。
- 为Context添加生命周期监听器
- 为Context 指定类加载器
- 默认为org.apache.catalina.loader.WebappLoader,可以通过className)属性指定自己的实现类。
- 为Context添加会话管理器
- 默认实现为org,apache,catalina.session.StandardManager,同时为管理器指定会话存储方式和会话标识生成器。Context提供了多种会话管理方式。
- 为Context添加初始化参数
- 通过该配置,为Context添加初始化参数。我们可以在context.xml文件中添加初始化参数,以实现在所有Web应用中的复用,而不必每个Web应用重复配置。当然,只有在Web应用确实允许与Tomcat紧耦合的情况下,我们才推荐使用该方式进行配置,否则会导致Web应用适应性非常差。
- 为Context添加安全配置以及Web资源配置
- Tomcat8新增加了PreResources、JarResources、PostResources这3种资源的配置。这3类资源的用处在讲解Web应用加载时会详细说明。
- 为Context添加资源链接
- 为Context添加资源链接ContextResourceLink,用于J2EE命名服务。
- 为Context 添加Value
- 为Context添加拦截器Valve,具体的拦截器类由className属性指定。
- 为Context 添加守护资源配置
- WatchedResource标签用于为Context添加监视资源,当这些资源发生变更时,Web应用将会被重新加载,默认为WEB-INF/web.xml(具体见conf/context.xml)。
- WrapperLifecycle标签用于为Context添加一个生命周期监听器类,此类的实例并非添加到Context上,而是添加到Context包含的Wrapper上。
- WrapperListener标签用于为Context添加一个容器监听器类(ContainerListener),此类的实例同样添加到Wrapper上。
- JarScanner标签用于为Context添加一个Jar扫描器,Catalina的默认实现为org,apache.tomcat.util.scan.Standard].arScanner。JarScanner扫描Web应用和类加载器层级的Jar包,主要用于TLD扫描和web-fragment.xml扫描。通过JarScanFilter标签,我们还可以为JarScanner指定一个过滤器,只有符合条件的.Jar包才会被处理,默认为org.apache.tomcat.util.scan.Standard们arScanFilter.
- 为Context添加Cookie处理器
- 8.5.6之前的版本默认实现为LegacyCookieProcessor,之后改为Rfc6265 CookieProcessor。
至此,我们已经完成了Servert创建过程的分析。
4. Tomcat解析流程图
各个阶段的代码注释详见tomcat-9.0.60-src源码解析 ,这里不再说明,请大家自行debug观察server.xml的解析过程。
参考文章
tomcat-9.0.60-src源码解析
Tomcat架构解析
Digester(二)Digester使用和原理