How Tomcat Works(十八)

时间:2021-01-03 17:42:54

在前面的文章中,如果我们要启动tomcat容器,我们需要使用Bootstrap类来实例化连接器、servlet容器、Wrapper实例和其他组件,然后调用各个对象的set方法将它们关联起来;这种配置应用程序的方法有一个明显的缺陷,即所有的配置都必须硬编码。调整组件配置和属性值都必须要重新编译Bootstrap类。幸运的是,Tomcat的设计者使用了一种更加优雅的配置方式,即使用一个名为server.xml的XML文件来对应用程序进行配置。server.xml文件中的每个元素都会转换为一个java对象,元素的属性会用于设置java对象的属性,这样,就可以通过简单的编辑server.xml文件来修改tomcat的配置。

Tomcat使用了开源库Digester来将xml文件中的元素转换成java对象。

由于一个Context实例表示一个Web应用程序,因此配置Web应用程序是通过对已经实例化的Context实例进行配置完成的。用来配置Web应用程序的XML文件的名称是web.xml,该文件位于Web应用程序的WEB-INF目录下。

下面来介绍Digester库,Digester库是Apache软件基金会的Jatarta项目下的子Commons项目下的一个开源项目,它的主页地址是http://commons.apache.org/proper/commons-digester/

org.apache.commons.digester3.Digester类是Digester库中的主类,该类可用于解析XML文件,对于XML文件中的每个元素,Digester对象都会检查它是否要做事先预定义的事件,在调用Digester对象的parse()方法之前,程序员要先定义好Digester对象执行哪些动作。

因此,程序员要先定义好模式,然后将每个模式与一条或多条规则相关联。

模式通常是xml文件里面元素的路径,类似于xpath的语法路径

规则指明了当Digester对象遇到了某个特殊的模式时要执行的一个或多个动作,规则是org.apache.commons.digester3.Rule类的实例,Digester类开源包含0个或多个Rule对象,在Digester实例中,这些规则和其相关联的模式都存储在由org.apache.commons.digester3.Rules接口表示的一类存储器中,每当把一条规则添加到Digester实例中时,Rule对象都会被添加到Rules对象中。

另外,Rule类有begin()方法和end()方法,在解析xml文件时,当Digester实例遇到匹配某个模式的元素的开始标签时,它会调用相应的Rule对象的begin()方法,而当Digester实例遇到相应元素的结束标签时,它会调用Rule对象的end()方法。

在使用Digester库时,我们需要先导入相关依赖jar

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
<classifier>with-deps</classifier>
</dependency>

第一个示例应用程序演示如何使用Digester库动态的创建对象,并设置相应的属性值。

employee1.xml文件内容如下

<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Brian" lastName="May">
</employee>

我们需要根据上面的xml文件创建Employee对象,并设置相应属性,Employee类代码如下:

public class Employee {
private String firstName;
private String lastName;
private ArrayList offices = new ArrayList(); public Employee() {
System.out.println("Creating Employee");
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
System.out.println("Setting firstName : " + firstName);
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
System.out.println("Setting lastName : " + lastName);
this.lastName = lastName;
}
public void addOffice(Office office) {
System.out.println("Adding Office to this employee");
offices.add(office);
}
public ArrayList getOffices() {
return offices;
}
public void printName() {
System.out.println("My name is " + firstName + " " + lastName);
}
}

现在写一个测试类Test01,它使用Digester类,并为其添加创建Employee对象和设置其属性的规则。

public class Test01 {

    public static void main(String[] args) {

        InputStream inputStream = null;
Digester digester = new Digester();
// add rules
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addCallMethod("employee", "printName"); try {
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("employee1.xml");
Employee employee = (Employee) digester.parse(inputStream);
System.out.println("First name : " + employee.getFirstName());
System.out.println("Last name : " + employee.getLastName());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} }

第二个示例演示如何利用Digester库创建两个对象,并建立他们之间的关系

employee2.xml 文件内容如下

<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Freddie" lastName="Mercury">
<office description="Headquarters">
<address streetName="Wellington Avenue" streetNumber="223"/>
</office>
<office description="Client site">
<address streetName="Downing Street" streetNumber="10"/>
</office>
</employee>

然后我们还需要创建Office类和Address类

Office类代码如下:

public class Office {
private Address address;
private String description;
public Office() {
System.out.println("..Creating Office");
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
System.out.println("..Setting office description : " + description);
this.description = description;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
System.out.println("..Setting office address : " + address);
this.address = address;
}
}

Address类代码如下:

public class Address {
private String streetName;
private String streetNumber;
public Address() {
System.out.println("....Creating Address");
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String streetName) {
System.out.println("....Setting streetName : " + streetName);
this.streetName = streetName;
}
public String getStreetNumber() {
return streetNumber;
}
public void setStreetNumber(String streetNumber) {
System.out.println("....Setting streetNumber : " + streetNumber);
this.streetNumber = streetNumber;
}
public String toString() {
return "...." + streetNumber + " " + streetName;
}
}

下面是Test02类的定义,该类使用一个Digester对象,并为其添加规则

public class Test02 {

    public static void main(String[] args) {

        InputStream inputStream = null;
Digester digester = new Digester();
// add rules
digester.addObjectCreate("employee",
"ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addObjectCreate("employee/office",
"ex15.pyrmont.digestertest.Office");
digester.addSetProperties("employee/office");
digester.addSetNext("employee/office", "addOffice");
digester.addObjectCreate("employee/office/address",
"ex15.pyrmont.digestertest.Address");
digester.addSetProperties("employee/office/address");
digester.addSetNext("employee/office/address", "setAddress");
try {
inputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("employee2.xml");
Employee employee = (Employee) digester.parse(inputStream);
ArrayList offices = employee.getOffices();
Iterator iterator = offices.iterator();
System.out
.println("-------------------------------------------------");
while (iterator.hasNext()) {
Office office = (Office) iterator.next();
Address address = office.getAddress();
System.out.println(office.getDescription());
System.out.println("Address : " + address.getStreetNumber()
+ " " + address.getStreetName());
System.out.println("--------------------------------");
} } catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} }
}

Rule类包含了一些方法,其中最重要的两个方法是begin()方法和end()方法,当Digester实例遇到某个XML元素的开始标签时,它会调用它所包含的匹配Rule对象的begin()方法,方法签名如下:

public void begin( String namespace, String name, Attributes attributes ) throws Exception

当Digester实例遇到某个XML元素的结束标签时,它会调用它所包含的匹配Rule对象的end()方法,方法签名如下:

public void end( String namespace, String name ) throws Exception

Digester对象是如何完成这些工作的呢?当调用Digester对象的addObjectCreate()方法、addCallMethod()方法、addSetNext()方法或其他方法时,都会间接地调用Digester类的addRule()方法;该方法将一个Rule对象和它所匹配的模式添加到Digester对象的Rules集合中。

addRule()方法实现如下:

 public void addRule( String pattern, Rule rule )
{
rule.setDigester( this );
getRules().add( pattern, rule );
}

查看Digester类的addObjectCreate()方法的重载实现如下:

    public void addObjectCreate( String pattern, String className )
{
addRule( pattern, new ObjectCreateRule( className ) );
} public void addObjectCreate( String pattern, Class<?> clazz )
{
addRule( pattern, new ObjectCreateRule( clazz ) );
} public void addObjectCreate( String pattern, String className, String attributeName )
{
addRule( pattern, new ObjectCreateRule( className, attributeName ) );
} public void addObjectCreate( String pattern, String attributeName, Class<?> clazz )
{
addRule( pattern, new ObjectCreateRule( attributeName, clazz ) );
}

这四个重载方法都调用了addRule()方法,ObjectCreateRule类是Rule类的子类,该类的实例可作为addRule()方法的第二个参数使用。

下面是ObjectCreateRule类的begin()方法和end()方法的实现

 @Override
public void begin( String namespace, String name, Attributes attributes )
throws Exception
{
Class<?> clazz = this.clazz; if ( clazz == null )
{
// Identify the name of the class to instantiate
String realClassName = className;
if ( attributeName != null )
{
String value = attributes.getValue( attributeName );
if ( value != null )
{
realClassName = value;
}
}
if ( getDigester().getLogger().isDebugEnabled() )
{
getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} New '%s'",
getDigester().getMatch(),
realClassName ) );
} // Instantiate the new object and push it on the context stack
clazz = getDigester().getClassLoader().loadClass( realClassName );
}
Object instance;
if ( constructorArgumentTypes == null || constructorArgumentTypes.length == 0 )
{
if ( getDigester().getLogger().isDebugEnabled() )
{
getDigester()
.getLogger()
.debug( format( "[ObjectCreateRule]{%s} New '%s' using default empty constructor",
getDigester().getMatch(),
clazz.getName() ) );
} instance = clazz.newInstance();
}
else
{
if ( proxyManager == null )
{
Constructor<?> constructor = getAccessibleConstructor( clazz, constructorArgumentTypes ); if ( constructor == null )
{
throw new SAXException(
format( "[ObjectCreateRule]{%s} Class '%s' does not have a construcor with types %s",
getDigester().getMatch(),
clazz.getName(),
Arrays.toString( constructorArgumentTypes ) ) );
}
proxyManager = new ProxyManager( clazz, constructor, defaultConstructorArguments, getDigester() );
}
instance = proxyManager.createProxy();
}
getDigester().push( instance );
} /**
* {@inheritDoc}
*/
@Override
public void end( String namespace, String name )
throws Exception
{
Object top = getDigester().pop(); if ( proxyManager != null )
{
proxyManager.finalize( top );
} if ( getDigester().getLogger().isDebugEnabled() )
{
getDigester().getLogger().debug( format( "[ObjectCreateRule]{%s} Pop '%s'",
getDigester().getMatch(),
top.getClass().getName() ) );
}
}

begin()方法用于创建一个对象实例,并将其压入到Digester对象的内部栈中;end()方法会将内部栈的栈顶元素弹出栈

要向Digester实例中添加Rule对象,还可以调用其addRuleSet()方法,方法实现如下:

public void addRuleSet( RuleSet ruleSet )
{
String oldNamespaceURI = getRuleNamespaceURI();
String newNamespaceURI = ruleSet.getNamespaceURI();
if ( log.isDebugEnabled() )
{
if ( newNamespaceURI == null )
{
log.debug( "addRuleSet() with no namespace URI" );
}
else
{
log.debug( "addRuleSet() with namespace URI " + newNamespaceURI );
}
}
setRuleNamespaceURI( newNamespaceURI );
ruleSet.addRuleInstances( this );
setRuleNamespaceURI( oldNamespaceURI );
}

org.apache.commons.digester3.RuleSet接口表示Rule对象的集合,该接口定义了两个方法,分别为addRuleInstance()和getNamespaceURI(),addRuleInstance()方法签名如下:

public void addRuleInstance(Digester digester)

addRuleInstance()方法用于添加定义在当前RuleSet对象中的Rule对象集合到作为该方法参数传输的Digester实例中

getNamespaceUR()方法返回将要应用在所有Rule对象(在当前Ruleset中创建的)的命名空间的URI,该方法签名如下

public java.lang.String getNamespaceURI()

因此,在创建了Digester对象之后,可以创建一个RuleSet对象,并将其传输给Digester对象的addRuleSet()方法

为了便于使用,实现RuleSet接口有一个基类RuleSetBase,RuleSetBase类为抽象类,提供了getNamespaceURI()方法的实现,我们只需要提供addRuleInstances()方法的实现就可以了

下面是我们创建的EmployeeRuleSet类的源码(继承自RuleSetBase类)

public class EmployeeRuleSet extends RuleSetBase  {
public void addRuleInstances(Digester digester) {
// add rules
digester.addObjectCreate("employee", "ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addObjectCreate("employee/office", "ex15.pyrmont.digestertest.Office");
digester.addSetProperties("employee/office");
digester.addSetNext("employee/office", "addOffice");
digester.addObjectCreate("employee/office/address",
"ex15.pyrmont.digestertest.Address");
digester.addSetProperties("employee/office/address");
digester.addSetNext("employee/office/address", "setAddress");
}
}

我们注意到,EmployeeRuleSet类中的addRuleInstances()方法的实现的功能类似Test02类,将相同的Rule对象添加到Digester对象中

下面是Test03的代码,里面会创建EmployeeRuleSet类的实例,然后将其添加到之前创建的Digester对象中

public class Test03 {

  public static void main(String[] args) {

    InputStream inputStream = null;
Digester digester = new Digester();
digester.addRuleSet(new EmployeeRuleSet());
try {
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("employee2.xml");
Employee employee = (Employee) digester.parse(inputStream);
ArrayList offices = employee.getOffices();
Iterator iterator = offices.iterator();
System.out.println("-------------------------------------------------");
while (iterator.hasNext()) {
Office office = (Office) iterator.next();
Address address = office.getAddress();
System.out.println(office.getDescription());
System.out.println("Address : " +
address.getStreetNumber() + " " + address.getStreetName());
System.out.println("--------------------------------");
} }
catch(Exception e) {
e.printStackTrace();
}
finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}

与其他类型的容器不同,StandardContext实例必须有一个监听器,该监听器会负责配置StandardContext实例,设置成功后会将StandardContext实例的变量configued值设置为tue。

StandardContext类的标准监听器是org.apache.catalina.startup.ContextConfig类的实例,它会执行很对StandardContext实例来说必不可少的任务,例如安装验证器阀到StandardContext实例的管道对象中,此外还会添加许可器阀(类型为org.apache.catalina.valves.CertificateValve)到管道对象中。

但更重要的是,ContextConfig类的实例还会读取和解析默认的web.xml文件和应用程序自定义的web.xml文件,并将xml元素转换为java对象。

默认的web.xml文件位于CATALINE_HOME目录下的conf目录中,其中定义并映射了很多默认的servlet,配置了很多MIME类型文件的映射,定义了默认的session超时时间,以及定义了欢迎文件的列表。

应用程序的web.xml文件是应用程序自定义的配置文件,位于应用程序目录下的WEB-INF目录中。

ContextConfig实例会为每一个servlet元素创建StandardWrapper实例,因此,正如你在本章应用程序中看到的,配置变简单了,你不在需要实例化Wrapper实例了

因此,我们需要在Bootstrap类中实例化一个ContextConfig类,并调用org.apache.catalina.Lifecycle接口的addLifecycleListener()方法将其添加到StandardContext对象中

LifecycleListener listener = new ContextConfig();
((Lifecycle) context).addLifecycleListener(listener);

在启动和停止StandardContext实例时,会触发相应事件,ContextConfig类会对两种事件做出响应,分别为START_EVENT 和STOP_EVENT

每当StandardContext实例触发事件时,会调用ContextConfig实例的lifecycleEvent()方法

public void lifecycleEvent(LifecycleEvent event) {

        // Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
if (context instanceof StandardContext) {
int contextDebug = ((StandardContext) context).getDebug();
if (contextDebug > this.debug)
this.debug = contextDebug;
}
} catch (ClassCastException e) {
log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
} // Process the event that has occurred
if (event.getType().equals(Lifecycle.START_EVENT))
start();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop(); }

在上面方法中,会继续调用start()方法和stop()方法

private synchronized void start() {

        if (debug > 0)
log(sm.getString("contextConfig.start"));
context.setConfigured(false);
ok = true; // Set properties based on DefaultContext
Container container = context.getParent();
if( !context.getOverride() ) {
if( container instanceof Host ) {
((Host)container).importDefaultContext(context);
container = container.getParent();
}
if( container instanceof Engine ) {
((Engine)container).importDefaultContext(context);
}
} // Process the default and application web.xml files
defaultConfig();
applicationConfig();
if (ok) {
validateSecurityRoles();
} // Scan tag library descriptor files for additional listener classes
if (ok) {
try {
tldScan();
} catch (Exception e) {
log(e.getMessage(), e);
ok = false;
}
} // Configure a certificates exposer valve, if required
if (ok)
certificatesConfig(); // Configure an authenticator if we need one
if (ok)
authenticatorConfig(); // Dump the contents of this pipeline if requested
if ((debug >= 1) && (context instanceof ContainerBase)) {
log("Pipline Configuration:");
Pipeline pipeline = ((ContainerBase) context).getPipeline();
Valve valves[] = null;
if (pipeline != null)
valves = pipeline.getValves();
if (valves != null) {
for (int i = 0; i < valves.length; i++) {
log(" " + valves[i].getInfo());
}
}
log("======================");
} // Make our application available if no problems were encountered
if (ok)
context.setConfigured(true);
else {
log(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
} }

start()方法会进一步调用defaultConfig()方法和applicationConfig()方法

defaultConfig()方法负责读取并解析位于%CATALINA_HOME%/conf目录下的默认的web.xml文件

private void defaultConfig() {

        // Open the default web.xml file, if it exists
File file = new File(Constants.DefaultWebXml);
if (!file.isAbsolute())
file = new File(System.getProperty("catalina.base"),
Constants.DefaultWebXml);
FileInputStream stream = null;
try {
stream = new FileInputStream(file.getCanonicalPath());
stream.close();
stream = null;
} catch (FileNotFoundException e) {
log(sm.getString("contextConfig.defaultMissing"));
return;
} catch (IOException e) {
log(sm.getString("contextConfig.defaultMissing"), e);
return;
} // Process the default web.xml file
synchronized (webDigester) {
try {
InputSource is =
new InputSource("file://" + file.getAbsolutePath());
stream = new FileInputStream(file);
is.setByteStream(stream);
webDigester.setDebug(getDebug());
if (context instanceof StandardContext)
((StandardContext) context).setReplaceWelcomeFiles(true);
webDigester.clear();
webDigester.push(context);
webDigester.parse(is);
} catch (SAXParseException e) {
log(sm.getString("contextConfig.defaultParse"), e);
log(sm.getString("contextConfig.defaultPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log(sm.getString("contextConfig.defaultParse"), e);
ok = false;
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
log(sm.getString("contextConfig.defaultClose"), e);
}
}
} }

applicationConfig()方法与defaultConfig()方法类似,只不过它处理的是应用程序自定义的部署描述符,该部署描述符位于应用目录下的WEB-INF目录中

private void applicationConfig() {

        // Open the application web.xml file, if it exists
InputStream stream = null;
ServletContext servletContext = context.getServletContext();
if (servletContext != null)
stream = servletContext.getResourceAsStream
(Constants.ApplicationWebXml);
if (stream == null) {
log(sm.getString("contextConfig.applicationMissing"));
return;
} // Process the application web.xml file
synchronized (webDigester) {
try {
URL url =
servletContext.getResource(Constants.ApplicationWebXml); InputSource is = new InputSource(url.toExternalForm());
is.setByteStream(stream);
webDigester.setDebug(getDebug());
if (context instanceof StandardContext) {
((StandardContext) context).setReplaceWelcomeFiles(true);
}
webDigester.clear();
webDigester.push(context);
webDigester.parse(is);
} catch (SAXParseException e) {
log(sm.getString("contextConfig.applicationParse"), e);
log(sm.getString("contextConfig.applicationPosition",
"" + e.getLineNumber(),
"" + e.getColumnNumber()));
ok = false;
} catch (Exception e) {
log(sm.getString("contextConfig.applicationParse"), e);
ok = false;
} finally {
try {
if (stream != null) {
stream.close();
}
} catch (IOException e) {
log(sm.getString("contextConfig.applicationClose"), e);
}
}
} }

在ContextConfig类中,使用变量webDigester来引用一个Digester类型的对象

private static Digester webDigester = createWebDigester();

该Digester对象用于解析默认的web.xml文件和应用程序自定义的web.xml文件,在调用createWebDigester()方法时会添加用来处理web.xml文件的规则

/**
* Create (if necessary) and return a Digester configured to process the
* web application deployment descriptor (web.xml).
*/
private static Digester createWebDigester() { URL url = null;
Digester webDigester = new Digester();
webDigester.setValidating(true);
url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_22);
webDigester.register(Constants.WebDtdPublicId_22,
url.toString());
url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_23);
webDigester.register(Constants.WebDtdPublicId_23,
url.toString());
webDigester.addRuleSet(new WebRuleSet());
return (webDigester); }

我们注意到,上面方法中调用了变量webDigester的addRuleSet()方法,传入一个org.apache.catalina.startup.WebRuleSet类型的对象作为参数;WebRuleSet类是org.apache.commons.digester.RuleSetBase的子类。

下面是WebRuleSet类的addRuleInstances()方法实现:

public void addRuleInstances(Digester digester) {

        digester.addRule(prefix + "web-app",
new SetPublicIdRule(digester, "setPublicId")); digester.addCallMethod(prefix + "web-app/context-param",
"addParameter", 2);
digester.addCallParam(prefix + "web-app/context-param/param-name", 0);
digester.addCallParam(prefix + "web-app/context-param/param-value", 1); digester.addCallMethod(prefix + "web-app/display-name",
"setDisplayName", 0); digester.addRule(prefix + "web-app/distributable",
new SetDistributableRule(digester)); digester.addObjectCreate(prefix + "web-app/ejb-local-ref",
"org.apache.catalina.deploy.ContextLocalEjb");
digester.addSetNext(prefix + "web-app/ejb-local-ref",
"addLocalEjb",
"org.apache.catalina.deploy.ContextLocalEjb"); //代码太长,后面部分略 }

---------------------------------------------------------------------------

本系列How Tomcat Works系本人原创

转载请注明出处 博客园 刺猬的温驯

本人邮箱: chenying998179#163.com (#改为@)

本文链接http://www.cnblogs.com/chenying99/p/3249161.html