前言
由于换工作的原因,需要融入新的开发团队,开展新的业务征途,因此,距离上一次更新博客已有一段时间,现在稍微稳定下来可以继续Tomcat源码的分析。在回顾思路时发现,之前对于Tomcat组件和生命周期的文章主要从宏观角度分析了两者执行的大体流程,对于细节点的分析有所欠缺,而这些细节可能又是后期理解 “Tomcat处理请求响应” 这种重量级流程的前提。为了更好的温故而知新,未来将对Tomcat架构中各个组件及组件间关系和Tomcat的生命周期两篇文章进行更深程度的剖析,本文是其中的第一部分,主要强化的内容如下:
- Digester解析xml文件模式的详细分析,结合之前的文章,读者会了解到Tomcat中涉及到的所有关键解析规则和原理
- 其他Connector、Container相关组件解析的详细过程。在之前的文章中,仅以
<Server>
顶层标签举例,并不涉及<Engine>
、<Host>
、<Context>
等“子标签”,而Tomcat标签的解析越往“子标签” 越复杂,越接近Tomcat处理请求响应的核心,因此为了进一步的深入也需要将“子标签”的解析吃透
在Tomcat架构中各个组件及组件间关系中,我们已对Digester类工作的大体思路进行了分析,并且以<Server>
标签的解析进行了举例,<Service>
是<Server>
的子标签,对应的Digester解析规则如下
addObjectCreate(String pattern, String className, String attributeName)
底层使用的规则为ObjectCreateRule
,方法的第一个参数是pattern,表明了解析到什么标签才会使用配置的规则对标签的内容进行解析,和正则表达式匹配的作用类似。比如上图中的pattern为Server/Service
表示解析到<Server>
下的<Service>
标签时运用规则进行解析,这里用/
表示一种父子关系。 第二个参数className
很明显表示标签对应的java实体类,从上图中来说<Service>
标签对应的实体实际上就是StandardService
,其实该参数是一个可选参数,可以传null,用第三个参数attributeName
在运行时指定该标签对应的类,以图中举例就是说<Service>
标签可以存在一个属性,属性名为className
,当第二个参数没有指定时,Digester
会自动解析该属性,并通过反射生成该类的实例再压入Digester
内部的栈顶。addSetProperties(String pattern)
底层使用的规则为SetPropertiesRule
,方法唯一的参数也是pattern,同样表示遇到何种标签才进行解析,SetPropertiesRule
规则用于解析标签对应的属性。以上图举例,<Service>
标签如下所示图2. server.xml中Service标签
其属性只有name一个,那我们猜想在
StandardService
中可能存在一个该属性对应的set方法,看下StandardService
的代码发现确实如此图3. StandardService中setName方法
这里有一个小坑需要说明一下,实际上标签对应的实体类并不一定存在标签属性对应的set方法,并且也不是存在对应属性的set方法就会调用,理解这个细节我们需要进入到
SetPropertiesRule
类的begin()
方法中图4. SetPropertiesRule类的begin方法
红框处存在三个判断,第一个
digester.isFakeAtrribute(top, name)
,其中top
是当前Digester
内部栈中栈顶元素,对于<Service>
而言栈顶元素就是StandardService
,name
是每一个属性的名称,isFakeAtrribute
具体的逻辑如下图5. isFakeAtrribute方法
该方法实际上就是在一个
Map<Class,List<String>>
的集合中判断某个类是否存在某个名称的属性,如果存在就返回true,进而不去调用该属性的set方法,那么有哪些属性被放在了这个“假属性”集合中呢?我们回头看Catalina
中定义server.xml
解析规则的方法图6. Catalina的createStartDigester方法
Tomcat在创建
Digester
类之前默认添加了key为Object.class
的entry,其value为包含className
的集合,结合图5代码的逻辑可知,如果标签没有设置特定的fake attributes,那么总会返回默认的,包含名称为className
的集合。而正常情况下所有的标签都是没有设置特定的fake attributes的,也就是说Digester
在解析所有标签时都会排除名称为className
的属性我们再来看图4判断中的第二个部分
IntrospectionUtils.setProperty(top, name, value)
,最终调用的逻辑如 代码清单1
public static boolean setProperty(Object o, String name, String value,
boolean invokeSetProperty) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: setProperty(" +
o.getClass() + " " + name + "=" + value + ")");
String setter = "set" + capitalize(name);
try {
Method methods[] = findMethods(o.getClass());
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
Class<?> paramT[] = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1
&& "java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(o, new Object[] { value });
return true;
}
}
// Try a setFoo ( int ) or ( boolean )
for (int i = 0; i < methods.length; i++) {
boolean ok = true;
if (setter.equals(methods[i].getName())
&& methods[i].getParameterTypes().length == 1) {
// match - find the type and invoke it
Class<?> paramType = methods[i].getParameterTypes()[0];
Object params[] = new Object[1];
// Try a setFoo ( int )
if ("java.lang.Integer".equals(paramType.getName())
|| "int".equals(paramType.getName())) {
try {
params[0] = Integer.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( long )
}else if ("java.lang.Long".equals(paramType.getName())
|| "long".equals(paramType.getName())) {
try {
params[0] = Long.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName())
|| "boolean".equals(paramType.getName())) {
params[0] = Boolean.valueOf(value);
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType
.getName())) {
try {
params[0] = InetAddress.getByName(value);
} catch (UnknownHostException exc) {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
ok = false;
}
// Unknown type
} else {
if (log.isDebugEnabled())
log.debug("IntrospectionUtils: Unknown type " +
paramType.getName());
}
if (ok) {
methods[i].invoke(o, params);
return true;
}
}
// save "setProperty" for later
if ("setProperty".equals(methods[i].getName())) {
if (methods[i].getReturnType()==Boolean.TYPE){
setPropertyMethodBool = methods[i];
}else {
setPropertyMethodVoid = methods[i];
}
}
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (invokeSetProperty && (setPropertyMethodBool != null ||
setPropertyMethodVoid != null)) {
Object params[] = new Object[2];
params[0] = name;
params[1] = value;
if (setPropertyMethodBool != null) {
try {
return ((Boolean) setPropertyMethodBool.invoke(o,
params)).booleanValue();
}catch (IllegalArgumentException biae) {
//the boolean method had the wrong
//parameter types. lets try the other
if (setPropertyMethodVoid!=null) {
setPropertyMethodVoid.invoke(o, params);
return true;
}else {
throw biae;
}
}
} else {
setPropertyMethodVoid.invoke(o, params);
return true;
}
}
} catch (IllegalArgumentException ex2) {
log.warn("IAE " + o + " " + name + " " + value, ex2);
} catch (SecurityException ex1) {
log.warn("IntrospectionUtils: SecurityException for " +
o.getClass() + " " + name + "=" + value + ")", ex1);
} catch (IllegalAccessException iae) {
log.warn("IntrospectionUtils: IllegalAccessException for " +
o.getClass() + " " + name + "=" + value + ")", iae);
} catch (InvocationTargetException ie) {
ExceptionUtils.handleThrowable(ie.getCause());
log.warn("IntrospectionUtils: InvocationTargetException for " +
o.getClass() + " " + name + "=" + value + ")", ie);
}
return false;
}
方法中将原始的方法名通过capitalize
进行首字母大写处理,最终加上set前缀赋给setter变量。之后会根据类的实例得到对象所有的方法,并与setter进行匹配,匹配成功则直接invoke调用并返回true,没有找到对应属性的set方法则返回false。正是这种处理解释了上面我们提及的:有些标签即便配置了相关的属性也不会调用对应类的set方法
接下来我们再看第三个解析规则addSetNext(String pattern, String methodName, String paramType)
,底层使用的规则为SetNextRule
,方法的第一个参数指明了触发该规则的具体模式,第二个参数表明调用父标签对应实体的方法名称,第三个参数就是方法参数的类型。在这里因为当前栈顶元素为StandardService
,addSetNext
会调用StandardServer
的addService(Service service)
方法,将当前StandardService
与其父元素StandardServer
建立关联,涉及相关代码如下
我们可以稍微总结一下
Digester
内置的三大解析规则类对应的用途
Rule | 对应方法 | 用途 |
---|---|---|
ObjectCreateRule |
addObjectCreate |
根据匹配解析模式创建对应标签的实体类 |
SetPropertiesRule |
addSetProperties |
根据匹配解析模式为对应标签实体类设置相关属性 |
SetNextRule |
addSetNext |
建立标签对应实体之间子父类关系 |
至此<Service>
标签的规则配置及解析流程分析完毕,我们接着看<Connector>
标签
创建
<Connector>
对象的规则和<Service>
规则不太一样,并没有使用Digester
內建的ObjectCreateRule
,而是自己继承Rule
创建了ConnectorCreateRule
。前文中分析过,当解析到对应标签的开始处会调用规则类的begin()
,我们来看看它做了什么图9. ObjectCreateRule的执行流程
方法中首先取出此时栈顶元素
StandardService
(Connector
尚未创建),再从包含所有<Connector>
标签属性的attributes
中查找是否存在exector属性,存在最终会调用_setExecutor(Connector, Executor)
方法,该方法的主要作用是设置处理端到端连接的线程池,默认情况下server.xml
中并不会事先设置该线程池,但即便不设置,之后在Tomcat启动时也会默认创建一个,在后面分析启动流程时会详细分析,这里暂先按默认未设置线程池流程走。之后会根据protocol
属性创建Connector
对象,基于Tomcat架构中各个组件及组件间关系中给出的server.xml
可知,<Connector>
标签共有两种协议,一种是HTTP/1.1,另一种是AJP/1.3。前者大家很清楚是HTTP协议的1.1版本,后者一般用于web容器之间通信,比HTTP协议在web容器间拥有更高的吞吐量。因为存在两种协议,那就会存在两个Connector
实体,为了突出重点,我们只分析最常用的HTTP协议对应的Connector
初始化流程图10. Connector构造器
Connector
构造器会继续调用setProtocol(String protocol)
方法,并将协议对应的字符串传入图11. 确认协议对应处理器类型
第一个判断涉及到一种apr请求处理方法,可以将其理解为一种高效的IO模式,底层基于JNI技术调用操作系统级别的IO接口,在默认情况下也是关闭的。因此上述代码最终会调用
setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol")
,将Connector
中的成员变量ProtocolHandler
置为Http11Protocol
,该类在处理请求响应的流程中起到了重要作用,后续文章会详细分析,这里记住即可<Connector>
在处理属性是也添加了名为SetAllPropertiesRule
的规则,该规则接收了一个排除属性的数组,其中仅包含executor
属性图12. SetAllPropertiesRule
begin()
方法中不仅按上面提到的流程对属性进行了筛选,而且根据该规则中设置的排除属性数组再一次进行了过滤。同样的,在addSetNext
方法中会调用父标签StandardService
的addConnector(Connector)
,从而建立父子关联关系图13. StandardService中addConnector方法
在方法中使用了
synchronized
代码块解决了并发访问下新增Connector
被覆盖的问题,在Tomcat的生命周期中说到,每一个容器都一个生命周期状态的概念,这里getState()
就获得了此时Connector
的状态,在刚创建时容器的state为NEW
,available
属性值为false,并不会立即启动Connector
容器,至此<Connector>
的解析过程也分析完毕Tomcat从整体架构上可以分为两大部分:监听请求并生成对应Request和Response的
Connector
连接器,以及处理请求和控制tomcat容器运转的Container
。<Engine>
标签就是Container
的顶层组件,每一个Engine
相当于一个Servlet引擎,其下可以存在多个Host
和Context
子容器图14. Engine相关解析规则
乍一看貌似
<Engine>
相关的rule特别多,但仔细一看其实都是套路,按照上面的分析方式都能一一拿下,这里只说一些重点和不同的部分。规则中为<Engine>
添加了一个名为EngineConfig
的Listener,用于对StandardEngine
组件的生命周期监控图15. EngineConfig的所有逻辑
从图中可以看到该类在事件为start和stop时会进行日志的打印,此外并没有进行其他的操作,在
StandardEngine
初始化时存在一个管道Pipeline
和阀门Valve
的概念图16. StandardEngine构造器
Tomcat中为了更高效的处理请求,内部设计了
Pipeline
和Valve
的概念,相当于Servlet中的Filter和FilterChain,管道中可以通过addValve(Valve)
添加或通过removeValve(Valve)
移除多个阀门,而有一种阀门被称为基础阀门,该阀门总是最后一个执行的,比如这里的StandardEngineValve
,关于两者的详细分析会在后续文章开展,这里不做累述。参数backgroundProcessorDelay
和ContainerBase
中的内部类ContainerBackgroundProcessor
有关,该类实现了Runnable
接口,用于检测war包中的类文件是否改动,是否需要重新加载,而参数乘以默认的基数就是执行的间隔时间,具体的处理流程后续文章同样会讲到。setJvmRoute()
是给该机器设置一个唯一的标识,当有多台机器组成cluster时,每台机器都会用这唯一的标识代表自身在集群中的位置回到
EngineRuleSet
的规则定义上,我们发现Tomcat还为每一个StandardEngine
添加了RealmRuleSet
,该规则对应<Engine>
的子标签<Realm>
,此标签引出了一个“域”的概念,我们可以将多个web应用划分成多个域,给每个域设定不同的访问权限,只有拥有对应域访问权限的角色才能访问对应的web应用,因此该规则的设定主要为了安全访问和权限管理一个
<Host>
表示一个虚拟主机,其下可以存在多个<Context>
标签,<Host>
标签对应的规则定义如下图17. Host相关解析规则
<Host>
对应的实体类为StandardHost
,在初始化时也给Host
容器中的管道添加了一个基础阀门StandardHostValve
。同StandardEngine
一样,Tomcat也为StandardHost
添加了一个监听器HostConfig
,但其功能远比EngineConfig
复杂很多
它根据不同的事件类型对web应用的进行相应的检查发布,停止以及和上面提到的
ContainerBackgroundProcessor
线程结合起来监控应用是否需要reload等功能,这部分内容和容器的生命周期关系更加紧密,且可讲的内容较多,将放在生命周期强化的第二部分讲解一个
<Context>
可以认为对应一个webapps下的目录,或者一个war包。代表虚拟主机的<Host>
下可以存在多个<Context>
标签,<Context>
对应的解析规则也是继承RuleSetBase
创建了自己的规则集合ContextRuleSet
图19. ContextRuleSet
标签对应的实体是
StandardContext
,也存在基础阀门StandardContextValve
,添加了对应的监听器ContextConfig
,在对该监听器进行说明之前不知道大家想过没有,到目前为止,我们一直在讨论server.xml
文件的解析,那其他的xml文件,比如context.xml
、web.xml
是什么时候解析的呢?为了回答这一问题,我们来看一下ContextConfig
是如何处理监听事件发生的图20. ContextConfig处理监听事件逻辑
在Tomcat的生命周期中曾给出Tomcat生命周期流转图和全部生命周期状态字段,结合上图两处红框中的Lifecycle类型可知,
Lifecycle.AFTER_INIT_EVENT
发生在Lifecycle.CONFIGURE_START_EVENT
之前,而前者的init()
中就定义了解析web.xml
文件的所有规则图21. ContextConfig中init方法
继续深入
图22. createWebXmlDigester(boolean namespaceAware, boolean validation)
WebRuleSet
同样继承了RuleSet
,web.xml
存在两种形式,一种是我们“通常”意义上,放在每一个war包内的webapps/WEB-INF/web.xml
,该配置文件是以<web-app>
作为根元素的;另一种是为了支持Servlet3.0新特性将web.xml
分成多个小部分,运行时再将各个部分聚集起来解析的配置文件web-fragment.xml
,该文件是以<web-fragment>
作为根元素。代码清单2为WebRuleSet
设置的所有标签的解析规则
@Override
public void addRuleInstances(Digester digester) {
digester.addRule(fullPrefix,
new SetPublicIdRule("setPublicId"));
digester.addRule(fullPrefix,
new IgnoreAnnotationsRule());
digester.addRule(fullPrefix,
new VersionRule());
// Required for both fragments and non-fragments
digester.addRule(fullPrefix + "/absolute-ordering", absoluteOrdering);
digester.addRule(fullPrefix + "/ordering", relativeOrdering);
if (fragment) {
// web-fragment.xml
digester.addRule(fullPrefix + "/name", name);
digester.addCallMethod(fullPrefix + "/ordering/after/name",
"addAfterOrdering", 0);
digester.addCallMethod(fullPrefix + "/ordering/after/others",
"addAfterOrderingOthers");
digester.addCallMethod(fullPrefix + "/ordering/before/name",
"addBeforeOrdering", 0);
digester.addCallMethod(fullPrefix + "/ordering/before/others",
"addBeforeOrderingOthers");
} else {
// web.xml
digester.addCallMethod(fullPrefix + "/absolute-ordering/name",
"addAbsoluteOrdering", 0);
digester.addCallMethod(fullPrefix + "/absolute-ordering/others",
"addAbsoluteOrderingOthers");
}
digester.addCallMethod(fullPrefix + "/context-param",
"addContextParam", 2);
digester.addCallParam(fullPrefix + "/context-param/param-name", 0);
digester.addCallParam(fullPrefix + "/context-param/param-value", 1);
digester.addCallMethod(fullPrefix + "/display-name",
"setDisplayName", 0);
digester.addRule(fullPrefix + "/distributable",
new SetDistributableRule());
configureNamingRules(digester);
digester.addObjectCreate(fullPrefix + "/error-page",
"org.apache.catalina.deploy.ErrorPage");
digester.addSetNext(fullPrefix + "/error-page",
"addErrorPage",
"org.apache.catalina.deploy.ErrorPage");
digester.addCallMethod(fullPrefix + "/error-page/error-code",
"setErrorCode", 0);
digester.addCallMethod(fullPrefix + "/error-page/exception-type",
"setExceptionType", 0);
digester.addCallMethod(fullPrefix + "/error-page/location",
"setLocation", 0);
digester.addObjectCreate(fullPrefix + "/filter",
"org.apache.catalina.deploy.FilterDef");
digester.addSetNext(fullPrefix + "/filter",
"addFilter",
"org.apache.catalina.deploy.FilterDef");
digester.addCallMethod(fullPrefix + "/filter/description",
"setDescription", 0);
digester.addCallMethod(fullPrefix + "/filter/display-name",
"setDisplayName", 0);
digester.addCallMethod(fullPrefix + "/filter/filter-class",
"setFilterClass", 0);
digester.addCallMethod(fullPrefix + "/filter/filter-name",
"setFilterName", 0);
digester.addCallMethod(fullPrefix + "/filter/icon/large-icon",
"setLargeIcon", 0);
digester.addCallMethod(fullPrefix + "/filter/icon/small-icon",
"setSmallIcon", 0);
digester.addCallMethod(fullPrefix + "/filter/async-supported",
"setAsyncSupported", 0);
digester.addCallMethod(fullPrefix + "/filter/init-param",
"addInitParameter", 2);
digester.addCallParam(fullPrefix + "/filter/init-param/param-name",
0);
digester.addCallParam(fullPrefix + "/filter/init-param/param-value",
1);
digester.addObjectCreate(fullPrefix + "/filter-mapping",
"org.apache.catalina.deploy.FilterMap");
digester.addSetNext(fullPrefix + "/filter-mapping",
"addFilterMapping",
"org.apache.catalina.deploy.FilterMap");
digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name",
"setFilterName", 0);
digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name",
"addServletName", 0);
digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern",
"addURLPattern", 0);
digester.addCallMethod(fullPrefix + "/filter-mapping/dispatcher",
"setDispatcher", 0);
digester.addCallMethod(fullPrefix + "/listener/listener-class",
"addListener", 0);
digester.addRule(fullPrefix + "/jsp-config",
jspConfig);
digester.addObjectCreate(fullPrefix + "/jsp-config/jsp-property-group",
"org.apache.catalina.deploy.JspPropertyGroup");
digester.addSetNext(fullPrefix + "/jsp-config/jsp-property-group",
"addJspPropertyGroup",
"org.apache.catalina.deploy.JspPropertyGroup");
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/deferred-syntax-allowed-as-literal",
"setDeferredSyntax", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/el-ignored",
"setElIgnored", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-coda",
"addIncludeCoda", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-prelude",
"addIncludePrelude", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/is-xml",
"setIsXml", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/page-encoding",
"setPageEncoding", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/scripting-invalid",
"setScriptingInvalid", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/trim-directive-whitespaces",
"setTrimWhitespace", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/url-pattern",
"addUrlPattern", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/default-content-type",
"setDefaultContentType", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/buffer",
"setBuffer", 0);
digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/error-on-undeclared-namespace",
"setErrorOnUndeclaredNamespace", 0);
digester.addRule(fullPrefix + "/login-config",
loginConfig);
digester.addObjectCreate(fullPrefix + "/login-config",
"org.apache.catalina.deploy.LoginConfig");
digester.addSetNext(fullPrefix + "/login-config",
"setLoginConfig",
"org.apache.catalina.deploy.LoginConfig");
digester.addCallMethod(fullPrefix + "/login-config/auth-method",
"setAuthMethod", 0);
digester.addCallMethod(fullPrefix + "/login-config/realm-name",
"setRealmName", 0);
digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-error-page",
"setErrorPage", 0);
digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-login-page",
"setLoginPage", 0);
digester.addCallMethod(fullPrefix + "/mime-mapping",
"addMimeMapping", 2);
digester.addCallParam(fullPrefix + "/mime-mapping/extension", 0);
digester.addCallParam(fullPrefix + "/mime-mapping/mime-type", 1);
digester.addObjectCreate(fullPrefix + "/security-constraint",
"org.apache.catalina.deploy.SecurityConstraint");
digester.addSetNext(fullPrefix + "/security-constraint",
"addSecurityConstraint",
"org.apache.catalina.deploy.SecurityConstraint");
digester.addRule(fullPrefix + "/security-constraint/auth-constraint",
new SetAuthConstraintRule());
digester.addCallMethod(fullPrefix + "/security-constraint/auth-constraint/role-name",
"addAuthRole", 0);
digester.addCallMethod(fullPrefix + "/security-constraint/display-name",
"setDisplayName", 0);
digester.addCallMethod(fullPrefix + "/security-constraint/user-data-constraint/transport-guarantee",
"setUserConstraint", 0);
digester.addObjectCreate(fullPrefix + "/security-constraint/web-resource-collection",
"org.apache.catalina.deploy.SecurityCollection");
digester.addSetNext(fullPrefix + "/security-constraint/web-resource-collection",
"addCollection",
"org.apache.catalina.deploy.SecurityCollection");
digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method",
"addMethod", 0);
digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method-omission",
"addOmittedMethod", 0);
digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/url-pattern",
"addPattern", 0);
digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/web-resource-name",
"setName", 0);
digester.addCallMethod(fullPrefix + "/security-role/role-name",
"addSecurityRole", 0);
digester.addRule(fullPrefix + "/servlet",
new ServletDefCreateRule());
digester.addSetNext(fullPrefix + "/servlet",
"addServlet",
"org.apache.catalina.deploy.ServletDef");
digester.addCallMethod(fullPrefix + "/servlet/init-param",
"addInitParameter", 2);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
0);
digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
1);
digester.addCallMethod(fullPrefix + "/servlet/jsp-file",
"setJspFile", 0);
digester.addCallMethod(fullPrefix + "/servlet/load-on-startup",
"setLoadOnStartup", 0);
digester.addCallMethod(fullPrefix + "/servlet/run-as/role-name",
"setRunAs", 0);
digester.addObjectCreate(fullPrefix + "/servlet/security-role-ref",
"org.apache.catalina.deploy.SecurityRoleRef");
digester.addSetNext(fullPrefix + "/servlet/security-role-ref",
"addSecurityRoleRef",
"org.apache.catalina.deploy.SecurityRoleRef");
digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-link",
"setLink", 0);
digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-name",
"setName", 0);
digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
"setServletClass", 0);
digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
"setServletName", 0);
digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
"org.apache.catalina.deploy.MultipartDef");
digester.addSetNext(fullPrefix + "/servlet/multipart-config",
"setMultipartDef",
"org.apache.catalina.deploy.MultipartDef");
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
"setLocation", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-file-size",
"setMaxFileSize", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-request-size",
"setMaxRequestSize", 0);
digester.addCallMethod(fullPrefix + "/servlet/multipart-config/file-size-threshold",
"setFileSizeThreshold", 0);
digester.addCallMethod(fullPrefix + "/servlet/async-supported",
"setAsyncSupported", 0);
digester.addCallMethod(fullPrefix + "/servlet/enabled",
"setEnabled", 0);
digester.addRule(fullPrefix + "/servlet-mapping",
new CallMethodMultiRule("addServletMapping", 2, 0));
digester.addCallParam(fullPrefix + "/servlet-mapping/servlet-name", 1);
digester.addRule(fullPrefix + "/servlet-mapping/url-pattern", new CallParamMultiRule(0));
digester.addRule(fullPrefix + "/session-config", sessionConfig);
digester.addObjectCreate(fullPrefix + "/session-config",
"org.apache.catalina.deploy.SessionConfig");
digester.addSetNext(fullPrefix + "/session-config", "setSessionConfig",
"org.apache.catalina.deploy.SessionConfig");
digester.addCallMethod(fullPrefix + "/session-config/session-timeout",
"setSessionTimeout", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/name",
"setCookieName", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/domain",
"setCookieDomain", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/path",
"setCookiePath", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/comment",
"setCookieComment", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/http-only",
"setCookieHttpOnly", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/secure",
"setCookieSecure", 0);
digester.addCallMethod(fullPrefix + "/session-config/cookie-config/max-age",
"setCookieMaxAge", 0);
digester.addCallMethod(fullPrefix + "/session-config/tracking-mode",
"addSessionTrackingMode", 0);
// Taglibs pre Servlet 2.4
digester.addRule(fullPrefix + "/taglib", new TaglibLocationRule(false));
digester.addCallMethod(fullPrefix + "/taglib",
"addTaglib", 2);
digester.addCallParam(fullPrefix + "/taglib/taglib-location", 1);
digester.addCallParam(fullPrefix + "/taglib/taglib-uri", 0);
// Taglibs Servlet 2.4 onwards
digester.addRule(fullPrefix + "/jsp-config/taglib", new TaglibLocationRule(true));
digester.addCallMethod(fullPrefix + "/jsp-config/taglib",
"addTaglib", 2);
digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-location", 1);
digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-uri", 0);
digester.addCallMethod(fullPrefix + "/welcome-file-list/welcome-file",
"addWelcomeFile", 0);
digester.addCallMethod(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping",
"addLocaleEncodingMapping", 2);
digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/locale", 0);
digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/encoding", 1);
digester.addRule(fullPrefix + "/post-construct",
new LifecycleCallbackRule("addPostConstructMethods", 2, true));
digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-class", 0);
digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-method", 1);
digester.addRule(fullPrefix + "/pre-destroy",
new LifecycleCallbackRule("addPreDestroyMethods", 2, false));
digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-class", 0);
digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-method", 1);
}
其中的fullPrefix
对应的就是上面两种xml文件的根元素。这里需要引入Digester
中另外两个内置解析规则类CallMethodRule
和CallParamRule
,分别来源于digester.addCallMethod(String pattern, String methodName, int paramCount)
和digester.addCallParam(String pattern, int paramIndex)
,前者如果pattern
匹配成功,会调用当前栈顶元素的名为methodName
,属性数量为paramCount
的方法;后者需要与前者配合使用,其含义为找到与pattern
匹配的标签对应的值,该值作为前者调用方法的第paramIndex
参数的值传入,举个例子来说
相信写过web程序的读者都知道
<context-param>
的含义,当解析到第一句时会调用此时digester
内部栈栈顶元素的addContextParam(String param, String value)
方法,该方法有两个参数,当解析到子标签<param-name>
时,将该标签的值对应方法中的第一个参数param
,当解析到子标签<param-value>
时,将该标签的值对应方法中的第二个参数value
从代码清单2中我们还可以知道
<filter>
对应的实体为FilterDef
,<filter-mapping>
对应的实体为FilterMap
,<servlet>
对应的实体又在ServletDefCreateRule
中给出定义,为ServletDef
。需要说明的一点是,这里只是定义了web.xml
的解析规则,Tomcat将这些解析规则封装到了图22中的两个成员变量webRuleSet
和webFragmentRuleSet
中,真正的解析是在图20中,当ContextConfig
监听到事件Lifecycle.CONFIGURE_START_EVENT
,进而调用configureStart()
时才发生的,具体的流程将在生命周期的补充文章中讲解
后记
未来计划用两到三篇文章对Tomcat容器生命周期相关知识点进行补充分析,主要包括两部分内容:
- 容器的初始化和启动流程
- 各个容器相关监听器在Tomcat运行时的作用
完成这些前期准备工作后再用两到三篇文章对Tomcat如何接收处理请求,又如何正确的将请求分发到对应的war包,对应的Servlet中的
网友评论