概述
在前面章节中已经看到,使用 Bootstrap 类来初始化连接器、上下文、包装器以及其它组件。获得了它们的对象后,就可以使用 setXX()方法来关联它们。例如可以如下初始化连接器和上下文:
Connector connector = new HttpConnector();
Context context = new StandardContext();
将连接器和上下文相关联起来可以如下实现:
connector.setContainer(context);
可以使用相应的 set 方法来配置这些对象的属性。例如可以使用 setPath()和setDocBase() 方法来设置 path 和 docBase 属性:
context.setPath("/myApp") ;
context.setDocBase("myApp");
另外,可以初始化各种组件,然后使用相应的 addXX()方法将其添加到上下文容器中。
例如,下面是如何在上下文对象中添加生命周期监听器和加载器:
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
Loader loader = new WebappLoader();
context.setLoader(loader);
一旦必要的关联和添加设置完毕,就可以调用连接器的 initialize() 和 start()方法和上下文的 start 方法:
connector.initialize();
((Lifecycle) connector).start ();
((Lifecycle) context).start();
这种方式来配置应用程序有一个很明显的缺点:所有的东西都是硬编码的。要更改一个组件或者一个属性的值都需要重新编译整个 Bootstrap 类。幸运的是,Tomcat 的设计者选择了一种更优雅的方式来进行配置,例如使用名为 server.xml 的XML 文档。Server.xml 中的每一个元素都被转换为一个 Java 对象,元素的属性用来设置对象属性。这样,就可以通过编辑 server.xml 来改变 Tomcat 的配置。例如,上下文容器元素就可以这样在 server.xml 中表示:<context/>
。
设置path 和docBase属性可以如下使用XML节点属性:
<context docBase="myApp" path="/myApp"/>
Tomcat 使用开源工具 Digester 将XML元素转换为Java对象。Digester 将会在本章第一节介绍。
接下来的一节介绍了如何配置一个 web 应用程序,一个上下文(Context)用来表示一个web 应用程序,所以配置初始化该上下文实例即可达到配置该 web 应用目的。配置 web 应用所使用的XML 文件是web.xml,该文件必须存放在该应用程序的 WEB-INF 目录下面。
15.1 Digester
Digester 是 Apache Jakarta 项目下面的开源项目。可以在http://jakarta.apache.org/commons/digester/下载到 Digester。Digester由3个包组成,被封装在 commons-digeser.jar 文件中:
》org.apache.commons.digester提供了基于规则的任意 XML 文档处理
》org.apache.commons.digester.rss演示了如何使用Digester 解析 XML 文档
》org.apache.commons.digester.xmlrules此包为Digester提供了基于XML的规则定义
我们不会介绍着3个包的所有内容,而是重点介绍在 Tomcat 中使用的几个重要类型。
本节首先介绍 Digester库中最重要的类Digester。
15.1.1 Digester类
org.apache.commons.digester.Digester 类是 Digester 库里的主类。使用它来解析 XML 文档。对于该文档中的每一个元素,Digester 都检查它是否需要做点事情。有程序员决定 Digester 实例在调用 parser()方法之前需要做什么。
我们如何告诉一个 Digester 对象在遇到一个 XML 元素时该怎么做?很简单,只要定义模式并且将模式跟一条或多条规则相关联即可。XML文档中的根元素具有与元素的名称相同的模式,例如:Listing15.1 所示的 XML 文档:
Listing 15.1: The example.xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Brian" lastName="May">
<office>
<address streeName="Wellington Street" streetNumber="110"/>
</office>
</employee>
该文档的根元素是employee,该元素的模式为 employee。office 元素是的子元素。一个子元素的模式是它的名字加上其父元素作为前缀的样子。所以 office 元素的模式是 employee/office,而 address 元素的模式是:
父元素模式+ “/” +元素名称
Address 元素的父元素师,元素的模式 employee/office。因此,<address>
的模式 employee/office/address。
现在我们已经明白了如何从 XML 文档中获得模式,接下来讨论下规则(rules)。
一个规则定义了 Digester 遇到特别的模式的时候必须做的动作。一个规则用org.apache.commons.digester.Rule类表示。Digester类包括零个或多个Rule对象。在一个 Digester 实例中,规则及其模式被存储在接口org.apache.commons.digester.Rules 定义的类型中。每一次给 Digester 实例添加规则时,都将 Rule对象添加到 Rules对象。
其中,在 Rule 中有begin() 和 end() 方法。当解析一个 XML 文档时,遇到开始元素,Digester 实例调用 Rule 对象的 begin()方法,而遇到结束元素时调用 stop()方法。
当解析如 Listing15.1 所示的 example.xml 所示的文档时,下面是 Digester对象处理流程:
》第一次遇到 employee 开始元素,检查是否已经有存在的规则模式 employee。如果存在,Digester将从添加到Digester的第一个规则的begin()方法开始执行Rule对象的begin()方法
》然后检查 office 元素,所以 Digester 对象检查是否存在规则模式 employee/office。如果有,则调用 Rule 对象的 begin()方法
》接下来检查模式 employee/office/address,如果找到了规则,则调用规则的begin()方法
》接下来 Digester 遇到了 address 结束符,则调用对应规则的end()方法
》接下来解析遇到了 office 结束符,就调用相应规则的 end()方法
》最后遇到了employee 结束符,调用相应规则的 end()方法
我们可以使用什么规则?Digester 预先定义了一些规则,甚至在不了解 Rule类时都可以使用这些规则。然而,如果这些规则不足够,那么我们需要建立自己的规则。预先定义的规则包括创建对象,设置属性值等。
15.1.1.1 创建对象
如果想让 Digester 给特定的规则创建对象,可以调用它的 addObjectCreate ()方法。该方法有4个重载实现,其中2个最常用方法的签名如下:
public void addObjectCreate(java.lang.String pattern,java.lang.Class clazz)
public void addObjectCreate(java.lang.String pattern,java.lang.String className)
传递一个模式和类对象或类名。例如,我们想让Digester 根据employee模式,创建一个 Employee 对象(类为 ex15.pyrmont.digestertest.Employee)可以使用如下代码:
digester.addObjectCreate("employee",ex15.pyrmont.digestertest.Employee.class);
或
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
方法 addObjectCreate()的另两个实现允许在 XML 文档中定义类名,而不是通过方法传参。这个特性很强大,它使得类名可以在运行时决定,下面是这两个方法的签名:
public void addObjectCreate(java.lang.String pattern,java.lang.String className, java.lang.String attributeName)
public void addObjectCreate(java.lang.String pattern,java.lang.String attributeName, java.lang.Class clazz)
在这两个实现中,attributeName 参数作为XML 文档中元素的属性,名字有className 指定。例如,使用下面的代码来定义创建对象规则:
digester.addObjectCreate("employee", null, "className");
其中属性名为 className。
然后在 XML 中传递类名:
<employee firstName="Brian" lastName="May"
className="ex15.pyrmont.digestertest.Employee">
或者可以如下在 addObjectCreate()方法中定义默认的类名:
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee", "className");
如果 employee元素有className 属性,就用该属性的值来进行类的初始化,如果没有该属性,则使用该默认值来进行初始化。
使用addObejectCreate()方法创建的对象被压到一个内部堆栈中,并定义peek、push和pop方法来操作所创建的对象。
15.1.1.2 设置属性
另一重要的方法是 addSetProperties(),Digester对象可以通过它设置对象属性。该方法的一个实现签名如下:
public void addSetProperties(java.lang.String pattern)
传递一个模式给该方法,例如下面的代码:
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
上面的 Digester 实例有两个规则:创建对象,设置属性。都是关于 employee模式的。根据被添加的顺序来执行这些规则。XML 文档中如下的 employee 元素:
<employee firstName="Brian" lastName="May">
Digester 实例首先根据第一个规则创建一个 ex15.pyrmont.digestertest.Employee 类的实例。然后Digester使用第二条规则来根据 XML 文档调用 setFirstName 和 setLasetName来设置 Employee对象的属性值。对象属性的值跟XML文档中相应元素的属性值是一致的。如果 Employee 类没有定义该属性,则会产生错误。
15.1.1.3方法调用
Digester类允许添加规则,当看到相应的模式时,则调用堆栈中最顶层对象的方法。该方法名为 addCallMethod(),它的一个实现的签名如下:
public void addCallMethod (java.lang.String pattern,java.lang.String methodName)
15.1.1.4 关联对象
Digester 实例中有一个栈用来临时存储对象。当调用 addObjectCreate()创建对象后,将对象压入堆栈中。可以把堆栈想象成一口井,将对象放入到堆栈中犹如把东西放入井中一样;而pop()方法相当于取出井中最上边的元素。
当通过 addObjectCreate()方法创建两个对象时,第一个对象被放入井中,然后是第二个。addSetNext()用于建立第一个对象和第二个对象之间的关系,通过调用第一个对象的具体方法,把第二个对象作为参数传递给第一个对象。下面是 addSetNext()方法的签名:
public void addSetNext(java.lang.String pattern,java.lang.String methodName)
参数pattern定义了触发该规则的模式,参数methodName是第一个对象中要被调用的方法名。该模式的形式应该如 firstObject/secondObject。
例如,一个 employee 可以有一个 office,要创建它们之间的关系,首先需要使用两个 addObjectCreate()方法:
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
digester.addObjectCreate("employee/office","ex15.pyrmont.digestertest.Office");
第一个addObjectCreate()方法根据 employee 元素创建一个Employee 类实例。第二个 addObjectCreate()方法根据下面的创建一个 Office 实例。
这两个addObjectCreate()方法将两个对象压入到堆栈中,现在Employee对象在栈底部,Office 对象在栈顶部。要建立它们之间的关系,则使用 addSetNext ()方法方法:
digester.addSetNext("employee/office", "addOffice");
其中 addOffice 是 Employee 类的方法,该方法必须接受一个 Office 对象作为参数。在第2个 Digester例子中将会仔细介绍 addSetNext()方法。
15.1.1.5 验证XML文档
可以使用 Digester 来对 XML 文档的结构进行验证。是否验证一个 XML 文档结构的合法性取决于Digester中定义的 validating 属性,该属性的默认值为 false。
setValidating()方法用来设置是否要验证 XML 文档,该方法的签名如下:
public void setValidating(boolean validating)
如果想要验证 XML文档结构语法,则传递一个 true 值给该方法。
15.1.2 Digester实例一
第一个例子说明了如何使用 Digester 动态的创建对象并设置它的属性。考虑Listing15.2 所示的类 Employee 用 Digester 来初始化:
Listing 15.2: The Employee Class
package ex15.pyrmont.digestertest;
import java.util.ArrayList;
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);
}
}
Employee 类有三个属性:firstName、lastName 和 office。firstName 和 lastName类型为 String 类型,office 类型为 ex15.pyrmont.digester.Office 类型。Office 属性用作 Digester 的第二个例子。
Employee 还有一个方法:printName()方法将 first name 和 last name 打印到console 上面。
接下来写一测试类用 Digester创建 Employee对象并设置它的属性,如Listing15.3 所示:
Listing 15.3: The Test01 Class
package ex15.pyrmont.digestertest;
import java.io.File;
import org.apache.commons.digester.Digester;
public class Test01 {
public static void main(String[] args) {
String path = System.getProperty("user.dir") + File.separator +"etc";
File file = new File(path, "employee1.xml");
Digester digester = new Digester();
// add rules
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addCallMethod("employee", "printName");
try {
Employee employee = (Employee) digester.parse(file);
System.out.println("First name : " + employee.getFirstName());
System.out.println("Last name : " + employee.getLastName());
}catch(Exception e) {
e.printStackTrace();
}
}
}
首先定义定义 XML 所在的路径并将其传递给 File 类的构造函数,然后创建一个Digester 对象并添加 employee 的如下规则:
digester.addObjectCreate("employee","ex15.pyrmont.digestertest.Employee");
digester.addSetProperties("employee");
digester.addCallMethod("employee", "printName");
接下来调用 Digester 类的 parse()方法,把该 XML 文档作为参数。该方法的返回值是 Digester 类的内部栈的第一个对象:
Employee employee = (Employee) digester.parse(file);
这样通过 Digester 获得了一个 Employee 对象,接下来看该对象的属性是否设置成功,调用 getFirstName()方法和 getLastName()方法即可:
System.out.println("First name : " + employee.getFirstName());
System.out.println("Last name : " + employee.getLastName());
现在看 Listing15.4 所示的 employee1.xml 文档,根为 employee,该元素有2个属性,firstName 和 lastName:
Listing 15.4: The employee1.xml file
<?xml version="1.0" encoding="ISO-8859-1"?>
<employee firstName="Brian" lastName="May">
</employee>
运行 Test01 类得到如下运行结果:
Creating Employee
Setting firstName : Brian
Setting lastName : May
My name is Brian May
First name : Brian
Last name : May
当调用 Digester 对象的 parse()方法时,它打开 XML文档开始解析。首先,Digester 看到了 employee 的开始元素。这样触发了关于 employee 所添加的三个规则。第一个是创建一个对象,所以 Digester 调用 Employee 类的构造函数初始化一个 Employee 类的对象,该构造函数打印出字符串“Creating Employee”。
第二个规则是设置 Employee对象的属性,该元素有两个属性:firstName 和lastName。该规则调用这两个属性的 set 方法。这两个 set 方法打印出如下字符串:
Setting firstName : Brian
Setting lastName : May
第三个规则调用 printName()方法,打印出如下内容:
My name is Brian May
接下来,最后两行调用 getFirstName()和 getLastName()方法打印结果:
First name : Brian
Last name : May
15.1.3 Digester实例二
Digester 类的第二个例子说明了如何创建两个对象,并建立它们之间的关系。关系的定义需要事先定义好。例如,一个employee在一个或多个office里工作。office 用 Office 类表示。可以创建一个 Employee 类和 Office 类的对象并建立它们之间关系。Office 类 Listing15.5 所示:
Listing 15.5: The Office Class
package ex15.pyrmont.digestertest;
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;
}
}
建立它们之间的关系,注意该例子使用的是 Listing15.2所示的 Employee 类,它由 addOffice()方法来建立它们之间的关系。
如果不适用 Digester 的话,可以使用如下 Java 代码来实现:
Employee employee = new Employee();
Office office = new Office();
employee.addOffice(office);
每个office 都有一个 address,而 address 如 Listing15.6 所示:
Listing 15.6: The Address Class
package ex15.pyrmont.digestertest;
public class Address {
private String streetName;
private String streetNumber;
public Adress () {
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;
}
}
要将一个 address 赋值给 office,可以使用 Office 类的 setAddress()方法。如果不适用 Digester,可以使用如下代码实现:
Office office = new Office();
Address address = new Address();
office.setAddress (address);
第二个 Digester 例子说明了如何创建多个对象并建立它们之间的关系。例子中会使用到 Employee、Office 和 Address 类,Test02 使用 Digester 并向上添加规则:
Listing 15.7: The Test02 Class
package ex15.pyrmont.digestertest;
import java.io.File;
import java.util.*;
import org.apache.commons.digester.Digester;
public class Test02 {
public static void main(String[] args) {
String path = System.getProperty("user.dir") + File.separator +"etc";
File file = new File(path, "employee2.xml");
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 {
Employee employee = (Employee) digester.parse(file);
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();
}
}
}
要看Digester是如何工作的,可以使用 Listing15.8 所示的 XML 文档:
Listing 15.8: The employee2.xml file
<?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>
Test02 类的运行结果如下:
Creating Employee
Setting firstName : Freddie
Setting lastName : Mercury
..Creating Office
..Setting office description : Headquarters
....Creating Address
....Setting streetName : Wellington Avenue
....Setting streetNumber : 223
..Setting office address : ....223 Wellington Avenue
Adding Office to this employee
..Creating Office
..Setting office description : Client site
....Creating Address
....Setting streetName : Downing Street
....Setting streetNumber : 10
..Setting office address : ....10 Downing Street
Adding Office to this employee
-------------------------------------------------
Headquarters
Address : 223 Wellington Avenue
--------------------------------
Client site
Address : 10 Downing Street
--------------------------------
15.1.4 Rule类
Rule 类有多个方法,其中两个最重要的是 begin() 和 end() 方法。当 Digester 实例遇到一个 XML 元素的开始标志时,调用所匹配规则的 begin()方法。Rule类的 begin()方法签名如下:
public void begin(org.xml.sax.Attributes attributes)throws java.lang.Exception
当 Digester 实例遇到 XML 元素的 end()时,调用所有匹配规则的 end()方法,Rule类的 end()方法签名如下:
public void end() throws java.lang.Exception
Digester 对象在前面的例子中是如何做到这些的呢?每次调用 addObjectCreate()、addCallMethod()、addSetNext()方法或该类其它方法时,都会间接地调用 Digester 类的 addRule()方法,该方法会将一个 Rule对象以及它的匹配模式添加到 Didgester 内部的规则集合中。addRule()方法的签名如下:
public void addRule(java.lang.String pattern, Rule rule)
该方法在 Digester 类中的实现如下:
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));
}
这4个实现都调用了addRule()方法,ObjectCreateRule 类是 Rule 类的子类。大家可能会对 ObjectCreateRule 类中begin() 和 end()方法实现感兴趣:
public void begin(Attributes attributes) throws Exception {
// 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 (digester.log.isDebugEnabled()) {
digester.log.debug("[ObjectCreateRule]{" + digester.match +"}New " + realClassName);
}
// Instantiate the new object and push it on the context stack
Class clazz = digester.getClassLoader().loadclass(realClassName);
Object instance = clazz.newInstance();
digester.push(instance);
}
public void end() throws Exception {
Object top = digester.pop();
if (digester.log.isDebugEnabled()) {
digester.log.debug("[ObjectCreateRule]{" + digester.match +
"} Pop " + top.getdass().getName());
}
}
在 begin()方法中的最后三行创建了对象并将其压到 Digester 类的内部堆栈中,end()方法使用 pop()方法从堆栈中获取对象。
Rule类的其它子类工作方式是类似的,如果热衷想知道它们是如何工作的可以查看它们的源代码。
15.1.5 Digester实例三:使用RuleSet
往 Digester 实例添加规则的另一种方式是调用 addRuleSet()方法,该方法的签名如下:
public void addRuleSet(RuleSet ruleSet)
org.apache.commons.digester.RuleSet 表示了 Rule对象,该接口定义了两个方法:addRuleInstance() 和 getNamespaceURI()。addRuleInstance()方法签名如下:
public void addRuleInstance(Digester digester)
addRuleInstance()方法把在当前 RuleSet 中定义的 Rule 对象添加到 Digester实例中,参数就是该对象。
getNamespaceURI()方法返回用于请求所有规则对象的名字空间 URI,它的签名如下:
public java.lang.String getNamespaceURI()
因此,在创建Digester 对象后,可以创建一 RuleSet 对象并传递给 addRuleSet()方法。有一个基本类 RuleSetBase 实现了 RuleSet 接口,RuleSetBase 是一个抽象类,它提供了 getNamespaceURI()实现,我们需要做的只是提供 addRuleInstances() 的实现即可。
这里修改前面Test02 类来介绍 EmployeeRuleSet 类的使用:
Listing 15.9: The EmployeeRuleSet Class
package ex15.pyrmont.digestertest;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.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");
}
}
注意addRuleInstances()方法在EmployeeRuleSet中的实现跟Test02添加了相同的规则,如 Listing15.10 所示的 Test03 创建了一个 EmployeeRuleSet 并将其添=加到 Digester 对象上:
Listing 15.10: The Test03 Class
package ex15.pyrmont.digestertest;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.commons.digester.Digester;
public class Test03 {
public static void main(String[] args) {
String path = System.getProperty("user.dir") +File.separator + "etc";
File file = new File(path, "employee2.xml");
Digester digester = new Digester();
digester.addRuleSet(new EmployeeRuleSet());
try {
Employee employee = (Employee) digester.parse(file);
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 ();
}
}
}
运行时,Test03 跟 Test02 产生了相同的输出,注意 Test03 类比较短,因为它把添加规则对象的操作隐藏到了 EmployeeRuleSet 类中。
接下来我们会看到,Catalina 使用 RuleSetBase 的子类来初始化服务器和其它组件。在下一节中,我们会看到 Digester 在 Catalina 中扮演着重要角色。
15.2 ContextConfig类
跟其它类型容器不同,StandardContext 必须有一个监听器,该监听器用于配置StandardContext对象并将其变量configured设置为true或false。在前面章节中,使用 SimpleContextConfig 类作为 StandardContext 的监听器。该类是一非常简单的类,它唯一目的是设置 configured 变量,以便StandardContext 的 start()方法可以继续。
在Tomcat实际部署中,StandardContext 的标准监听器是org.apache.catalina.startup.ContextConfig 类的实例。较之简单的SimpleContextConfig 类,ContextConfig 做了很多有用的工作,以至于StandardContext实例少不了它。例如,ContextConfig 实例给 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 在找不到它们的情况下仍然可以工作。
ContextConfig 为每一个 Servlet 元素创建了一个 StandardWrapper 实例。因此,在本章的应用Demo中可以看到配置是很简单的。不需要自己再完成包装器初始化任务。
因此,在我们的启动类中,必须初始化 ContextConfig 类并且通过org.apache.catalina.Lifecycle 接口的addLifecycleListener()方法将其添加到StandardContext上:
LifecycleListener listener = new ContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
StandardContext 在它启动时会触发以下事件:
•BEFORE_START_EVENT
•START_EVENT
•AFTER_START_EVENT
停止时 StandardContext 会触发以下事件:
•BEFORE_STOP_EVENT
•STOP_EVENT
•AFTER_STOP_EVENT
ContextConfig 回应两个事件 :START_EVENT 和 STOP_EVENT。lifecycleEvent()方法在 StandardContext 每次触发事件时都会被调用。该方法如 Listing15.11所示。我们在 Listing15.11 中添加了注释以助于对 stop()方法的理解。
Listing 15.11: The lifecycleEvent method of ContextConfig
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();
}
如我们看到的在lifecycleEvent()的最后,它调用自己的start()方法或者stop()方法。start()方法如 Listing15.12 所示。注意在它内部的 start()方法调用了defaultConfig()和 applicationConfig()方法。它们会在下一节中介绍。
Listing 15.12: The start method of ContextConfig
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);
}
}
15.2.1 defaultConfig方法
defaultConfig()方法读取并解析默认的%CATALINA_HOME%/conf 目录下面的web.xml,如 Listing15.13 所示:
Listing 15.13: The defaultConfig method
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);
}
}
}
}
defaultConfig()方法首先创建一个 File 对象指向默认的 web.xml:
File file = new File(Constants.DefaultWebXml);
DefaultWebXML 的值可以在 org.apache.catalina.startup.Constants 中找到:
public static final String DefaultWebXml = "conf/web.xml";
然后defaultConfig()方法处理 web.xml 文件。它对 webDigester 对象加锁,然后解析该文件:
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);
webDigester 变量指向一个 Digester 对象的实例,该实例用于处理 web.xml 并添加规则。这些将在下面“创建web应用Digester”小节中介绍。
15.2.2 applicationConfig方法
applicationConfig()方法 和defaultConfig()方法基本相似。一个应用的部署文件在该应用目录下的 WEB-INF 目录下面。applicationConfig()方法如 Listing15.14 所示:
Listing 15.14: The applicationConfig method of ContextConfig
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);
}
}
}
}
15.2.3 创建web应用Digester
在 ContextConfig 类中存在一个名为 webDigester 的 Digester 对象:
private static Digester webDigester = createWebDigester();
该 Digester用于解析默认web.xml和应用程序web.xml。处理web.xml 的规则在调用 createWebDigester()方法的时候会被添加。createWebDigester()方法如Listing15.15 所示:
Listing 15.15: The createWebDigester method
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);
}
注意: createWebDigester()方法在 webDigester 中调用 addRuleSet()时传递一个 org.apache.catalina.startu.WebRuleSet实例。WebRuleSet 是org.apache.commons.digester.RuleSetBase 类的子类。如果你熟悉Servlet 应用程序部署文件的语法并且读过本章前面的 Digester 部分,就可以很容易的理解它是如何工作的。WebRuleSet如Listing15.16 所示:
Listing 15.16: The WebRuleSet class
package org.apache.catalina.startup;
import java.lang.reflect.Method;
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.Rule;
import org.apache.commons.digester.RuleSetBase;
import org.xml.sax.Attributes;
/**
* <p><strong>RuleSet</strong> for processing the contents of a web
application
* deployment descriptor (<code>/WEB-INF/web.xml</code>) resource.</p>
*
* @author Craig R. McClanahan
* @version $Revision: 1.1 $ $Date: 2001/10/17 00:44:02 $
*/
public class WebRuleSet extends RuleSetBase {
// ------------------------------------- Instance Variables
/**
* The matching pattern prefix to use for recognizing our elements.
*/
protected String prefix = null;
// ------------------------------------------- Constructor
/**
* Construct an instance of this <code>RuleSet</code> with
* the default matching pattern prefix.
*/
public WebRuleSet () {
this("");
}
/**
* Construct an instance of this <code>RuleSet</code> with
* the specified matching pattern prefix.
*
* @param prefix Prefix for matching pattern rules (including the
* trailing slash character)
*/
public WebRuleSet(String prefix) {
super();
this.namespaceURI = null;
this.prefix = prefix;
}
// ------------------------------------------- Public Methods
/**
* <p>Add the set of Rule instances defined in this RuleSet to the
* specified <code>Digester</code> instance, associating them with
* our namespace URI (if any). This method should only be called
* by a Digester instance.</p>
*
* @param digester Digester instance to which the new Rule instances
* should be added.
*/
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/filter",
"org.apache.catalina.deploy.FilterDef");
digester.addSetNext(prefix + "web-app/filter", "addFilterDef",
"org.apache.catalina.deploy.FilterDef");
digester.addCallMethod(prefix + "web-app/filter/description",
"setDescription", 0);
digester.addCallMethod(prefix + "web-app/filter/display-name",
"setDisplayName", 0);
digester.addCallMethod(prefix + "web-app/filter/filter-class",
"setFilterClass", 0);
digester.addCallMethod(prefix + "web-app/filter/filter-name",
"setFilterName", 0);
digester.addCallMethod(prefix + "web-app/filter/large-icon",
"setLargeIcon", 0);
digester.addCallMethod(prefix + "web-app/filter/small-icon",
"setSmallIcon", 0);
digester.addCallMethod(prefix + "web-app/filter/init-param",
"addInitParameter", 2);
digester.addCallParam(prefix +
"web-app/filter/init-param/param-name", 0);
digester.addCallParam(prefix +
"web-app/filter/init-param/param-value", 1);
digester.addObjectCreate(prefix + "web-app/filter-mapping",
"org.apache.catalina.deploy.FilterMap");
digester.addSetNext(prefix + "web-app/filter-mapping",
"addFilterMap", "org.apache.catalina.deploy.FilterMap");
digester.addCallMethod(prefix +
"web-app/filter-mapping/filter-name", "setFilterName", 0);
digester.addCallMethod(prefix +
"web-app/filter-mapping/servlet-name", "setServletName", 0);
digester.addCallMethod(prefix +
"web-app/filter-mapping/url-pattern", "setURLPattern", 0);
digester.addCallMethod (prefix +
"web-app/listener/listener-class", "addApplicationListener", 0);
...
digester.addRule(prefix + "web-app/servlet",
new WrapperCreateRule(digester));
digester.addSetNext(prefix + "web-app/servlet",
"addChild", "org.apache.catalina.Container");
digester.addCallMethod(prefix + "web-app/servlet/init-param",
"addInitParameter", 2);
digester.addCallParam(prefix +
"web-app/servlet/init-param/param-name", 0);
digester.addCallParam(prefix +
"web-app/servlet/init-param/param-value", 1);
digester.addCallMethod(prefix + "web-app/servlet/jsp-file",
"setJspFile", 0);
digester.addCallMethod(prefix +
"web-app/servlet/load-on-startup", "setLoadOnStartupString", 0);
digester.addCallMethod(prefix +
"web-app/servlet/run-as/role-name", "setRunAs", 0);
digester.addCallMethod(prefix +
"web-app/servlet/security-role-ref", "addSecurityReference", 2);
digester.addCallParam(prefix +
"web-app/servlet/security-role-ref/role-link", 1);
digester.addCallParam(prefix +
"web-app/servlet/security-role-ref/role-name", 0);
digester.addCallMethod(prefix + "web-app/servlet/servlet-class",
"setServletdass", 0);
digester.addCallMethod(prefix + "web-app/servlet/servlet-name",
"setName", 0);
digester.addCallMethod(prefix + "web-app/servlet-mapping",
"addServletMapping", 2);
digester.addCallParam(prefix +
"web-app/servlet-mapping/servlet-name"/ 1);
digester.addCallParam(prefix +
"web-app/servlet-mapping/url-pattern", 0);
digester.addCallMethod (prefix +
"web-app/session-config/session-timeout", "setSessionTimeout",
1,
new Class[] { Integer.TYPE });
digester.addCallParam(prefix +
"web-app/session-config/session-timeout", 0);
digester.addCallMethod(prefix + "web-app/taglib",
"addTaglib", 2);
digester.addCallParam(prefix + "web-app/taglib/taglib-location",
1);
digester.addCallParam(prefix + "web-app/taglib/taglib-uri", 0);
digester.addCallMethod(prefix +
"web-app/welcome-file-list/welcome-file", "addWelcomeFile", 0);
}
}
// --------------------------------------------- Private Classes
/**
* A Rule that calls the <code>setAuthConstraint(true)</code> method of
* the top item on the stack, which must be of type
* <code>org.apache.catalina.deploy.SecurityConstraint</code>.
*/
final class SetAuthConstraintRule extends Rule {
public SetAuthConstraintRule(Digester digester) {
super(digester);
}
public void begin(Attributes attributes) throws Exception {
SecurityConstraint securityConstraint =
(SecurityConstraint) digester.peek();
securityConstraint.setAuthConstraint(true);
if (digester.getDebug() > 0)
digester.log("Calling
SecurityConstraint.setAuthConstraint(true)");
}
}
...
final class WrapperCreateRule extends Rule {
public WrapperCreateRule(Digester digester) {
super(digester);
}
public void begin(Attributes attributes) throws Exception {
Context context =
(Context) digester.peek(digester.getCount() - 1);
Wrapper wrapper = context.createWrapper();
digester.push(wrapper);
if (digester.getDebug() > 0)
digester.log("new " + wrapper.getClass().getName());
}
public void end() throws Exception {
Wrapper wrapper = (Wrapper) digester.pop();
if (digester.getDebug() > 0)
digester.log("pop " + wrapper.getclass().getName());
}
}
15.3 应用Demo
本章的应用Demo说明了如何使用 ContextConfig 实例作为监听器来配置StandardContext 对象。如 Listing15.17 所示的 BootStrap类:
Listing 15.17: The Bootstrap class
package ex15.pyrmont.startup;
//explain Digester and StandardContext
// use ContextConfig so we don't need to instantiate wrapper
import org.apache.catalina.Connector;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.ContextConfig;
public final class Bootstrap {
// invoke: http://localhost:8080/app1/Modern or
// http://localhost:8080/app2/Primitive
// note that we don't instantiate a Wrapper here,
// ContextConfig reads the WEB-INF/classes dir and loads all servlets.
public static void main(String[] args) {
System.setProperty("catalina.base", System.getProperty("user.dir"));
Connector connector = new HttpConnector();
Context context = new StandardContext();
// StandardContext's start method adds a default mapper
context.setPath("/app1");
context.setDocBase("app1");
LifecycleListener listener = new ContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
Host host = new StandardHost();
host.addChild(context);
host.setName("localhost");
host.setAppBase("webapps");
Loader loader = new WebappLoader();
context.setLoader(loader);
connector.setContainer(host);
try {
connector.initialize();
((Lifecycle) connector).start();
((Lifecycle) host).start();
Container[] c = context.findChildren();
int length = c.length;
for (int i=0; i<length; i++) {
Container child = c[i];
System.out.println(child.getName());
}
// make the application wait until we press a key.
System.in.read();
((Lifecycle) host).stop();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
15.3.1 运行Demo
在 Windows 下面可以在工作目录下输入如下命令运行该程序:
java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./lib/commons-digester.jar;./lib/commons-logging.jar;./lib/commons-beanutils.jar;./ex15.pyrmont.startup.Bootstrap
在 Linux 下面需要使用分号来分隔开两个库:
java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./lib/commons-digester.jar:./lib/commons-logging.jar:./lib/commons-beanutils.jar:./
ex15.pyrmont.startup.Bootstrap
使用如下 URL 可以调用 PrimitiveServlet和ModernServlet:
http://localhost:8080/app1/Primitive
http://localhost:8080/app1/Modern
15.4小结
Tomcat用于不同的配置。使用server.xml文件轻松配置是通过使用Digester对象将XML元素转换为Java对象来实现的。另外,一个 web.xml 文档被用于配置 servlet/JSP 应用。Tomcat 必须能够解析 web.xml 文档并基于 XML 文档配置上下文(Context)对象,Digester 优雅地解决了这个问题。