在前面的文章中,如果我们要启动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 ( #改为@ )