Hierarchical Inherited Rule-Interpreted XML

使用这项 XML 新技术创建动态的 Java 属性
 
级别: 中级

Chad L. Meadows ([email protected]), 首席重用工程师, IBM


2007 年 7 月 12 日

使用名为 Hierarchical Inherited Rule-Interpreted XML 的 XML 新技术,您可以用动态 Java 属性来替代标准 Java 属性。它允许您使用简单的表达式甚至 Groovy 表达式来定义属性,这些属性的值可以基于应用程序的状态获得,它还让您可以将一个属性文件用于应用程序的多个实例。通过本文介绍的这个简单实用的替换 Java 属性文件的示例,了解如何应用此项技术。
Java 属性 (java.lang.Properties) 是 Java 编程中最常用的应用程序配置方法。您通常会使用 Java 属性来确定初始化时如何配置应用程序,有时也会通过 Java 属性来确定某些运行时行为。属性通常用来指定诸如应用程序初始化时需要与之通信的服务器和端口之类的信息,还可以用来指定应用程序运行时属性,例如显示在主页上的新闻条目数的最大值或只显示页面标题的文本。

不管使用多少次,属性始终都是静态不动的。属性在属性文件内定义,然后存储在文件系统中。应用程序通过将所有属性值初始化为属性内包含的值启动并读取属性文件。此时,您还不能更改这些值,直至重启应用程序。可以添加一种触发机制用于保留属性,以便可以重载属性而无需应用程序重启,但是属性值仍被限制为属性文件中记录的值,直至触发器发出重载或者重启的信号。应用程序可能配有多个安装,可能是开发、测试、生产用安装,也可能是多个地理位置上的安装。每个实例通常都要求使用一组惟一的配置参数才能使应用程序正常运行。为了保存每个惟一实例,需要管理一个附加属性文件。

传统的 Java 属性的问题在于配置不灵活,需要花费很多的维护成本。然后,使用 Hierarchical Inherited Rule-Interpreted XML 却可以避免这些限制。可以通过 Hierarchical Inherited Rule-Interpreted XML 用动态 Java 属性替代标准 Java 属性。这样一来,您就能够定义可以根据应用程序状态求值的属性。Hierarchical Inherited Rule-Interpreted XML 还使您可以将一个属性文件用于应用程序的多个实例。

理解基本属性示例

先来看一个带有两个属性的简单属性文件,如 清单 1 所示。使用此属性文件,我将演示使用普通 Java 属性和增强属性之间的差别。


清单 1. 样例属性文件
               
news_source = mynews.myserver.com
news_max_items = 5



清单 2 显示了用于装入属性文件、获得属性值和把值显示到控制台的典型代码。


清单 2. 装入和显示属性的应用程序代码
               
public static void main(String[] args) {
   // Get the URL of the properties file
   URL propertiesURL = StandardExample.class.getClassLoader().
                       getResource("example1/properties.properties") ;

   try {
      // Show the system property values for demonstration and reference
      System.out.println("Current system properties:") ;
      System.out.println(System.getProperties().toString()) ;
      System.out.println("Executing example \n\n") ;

      // Create the instance of the properties object
      Properties properties = new Properties() ;

      // Load the properties as you normally would
      properties.load( propertiesURL.openStream() ) ;

      // Get property values and write out to the console for this example
      System.out.println( properties.get("news_source")) ;
      System.out.println( properties.get("news_max_items")) ;

   } catch (IOException e) {
      e.printStackTrace() ;
   }

}



清单 3 演示了用 XML 表示的同一个属性文件。所有属性都是使用 <property> 元素列出的。id 属性是关键字,而 value 属性包含该关键字的值。在这个特殊的示例中,属性 'news_max_items' 有一个设为 ${5} 而不是简单地设为 5 的值。这说明使用增强属性,值可以是除 java.lang.String 类型以外的值而不仅是此类型的值。在本例中,值被表示为 java.lang.Integer。


清单 3. 与清单 1 中具有相同值的 XML 属性文件
               
<properties>
        <property id='news_source'    value='mynews.myserver.com' />
        <property id='news_max_items' value="${5}" />
<properties>



接下来,清单 4 显示了使用增强属性来执行同一种功能所需的更改。在 清单 2 中的原始代码基础上只更改一行代码即可。将原始的 java.lang.Properties 替换为 com.ibm.dolphin.hirix.properties.HirixProperties,后者是增强属性类。


清单 4. 对原始应用程序进行更改以使用增强属性
               
// Create the instance of the properties object
Properties properties = new HirixProperties() ;



这是开始使用增强属性所需的全部改动。您可以轻松地使用它们代替标准 java.lang.Properties。通过设计,增强属性可轻松替代现有属性。现有应用程序可以根据需要也可开始使用增强属性的复杂特性。




定义动态属性

可以使用 Hierarchical Inherited Rule-Interpreted XML 来定义这些相同的增强属性,从而在运行时对其进行动态解释。动态属性的值是表达式,并以 Java 表达式语言(Java Expression Language,JEXL)为表达式解释程序(有关 JEXL 表达式的更多信息,请参阅 参考资料)。清单 5 显示了如何构造属性文件,该属性文件在运行时使用这些表达式来解析值。


清单 5. 包含动态值的属性文件
               
<!-- This properties file demonstrates how to make the values change based on
     state of the application.
     The 'user' variable is placed into the context by the application -->
<!-- system['key'] is equivalent to System.getProperties.get("key") -->

<properties>
     <!-- Adding an 'if' attribute allows you to control when the value is used
          Properties are tested in order.
          The first property which evaluates true will return its value -->

     <property id='news_source'     value='developer_news.myserver.com'
                                    if="${user.job == 'developer'}"/>

     <property id='news_source'     value='architect_news.myserver.com'
                                    if="${user.job == 'architect'}"/>

     <property id='news_max_items'  value="${5}"
                                    if="${user.job == 'developer'}"/>

     <property id='news_max_items'  value="${10}"
                                    if="${user.job == 'architect'}"/>

     <Demonstrating the use of combining values into a single value -->
     <property id="greeting"
               value="Hello ${user.name}
                      we are retrieving ${news_max_items} items for ${user.job}"/>

     <This property makes use of combining values
          and the system properties as well -->

     <property id="resource_directory" value="${system['user.dir']}/${user.name}" />

<properties>



清单 5 定义了以下动态属性和值。

news_source

news_source 属性看似被定义了两次。读取属性后,将测试发现的第一个值以查看是否已经应用了任何条件。条件是使用 <property> 元素中的 if 属性应用的。如果条件存在,条件的值必须为 true 才能将该值作为结果返回。否则,接着测试下一个 news_source 属性。此过程将一直继续,直至条件通过或者直至不再有其他 news_source 属性,在这种情况下将返回一个 null 结果。

在这里的 清单 5 中应用的条件将测试 user 对象的 job 属性是否等于 developer。如果为 true,则将 developer_news.myserver.com 作为结果返回。否则,如果 job 属性等于 architect,则将 architect_news.myserver.com 的值作为结果返回。

news_max_items

news_max_items 属性与 news_source 应用的条件相同。在此实例中,如果 user 对象的 job 属性值等于 developer,则值 5 将作为 java.lang.Integer 返回。否则,如果 architect 是 job 属性的值,则 10 将作为 java.lang.Integer 返回。

greeting

greeting 属性没有条件;不过,此属性演示了替换字符串内的值的功能。以下三个值被插入此属性的字符串中:

${user.name}:把用户名置入字符串中。
${news_max_items}:为 news_max_items 属性的值。属性还可以引用其他属性。
${user.job}:把用户职位置入字符串中。
resource_directory

resource_directory 属性的值使用一个已定义的系统属性 user.dir,外加字符串内包含的用户名称。

请注意,属性文件使用了一个名为 user 的变量。这是由应用程序设置到上下文中的。为了共享应用程序的 XML 属性文件中的信息,必须将所有引用存储在一个共享上下文内,如 清单 6 所示。方法 getUserInfo1() 将返回一个 Map 对象,然后将把该对象存储到关键字为 user 的上下文中。


清单 6. 将值设置到应用程序的上下文中
               
// update the current context with the current user
properties.getContext().put("user", getUserInfo1()) ;



在 Map 被存储到上下文中后,可以使用语法 ${user} 在属性文件的任意位置访问它。可以使用 ${user.methodName()} 调用对象的所有公共方法。您还可以使用简短形式的 ${user.propertyName}(等价于方法调用 ${user.get('propertyName')})访问 Map 对象内的值。在 清单 7 中,user 变量在环境中被设为方法 getUserInfo1() 创建的 Map。所有属性值都被获取并记录到控制台。所有属性值都将在属性被获取之前使用 user 集的当前状态来生成。在值被记录到控制台后,user 的上下文值随后被更新为由 getUserInfo2() 返回的 Map 值。虽然请求的是与先前一样的属性,但现在实际获得的属性值结果却不同,原因是现在它们的值是通过不同的 user 得到的。


清单 7. 用于演示动态属性值的应用程序代码
               
/**
* @author cmeadows
*
* This example demonstrates the use of HirixProperties where you make the
* values dynamic based on the state of the application.
* In this example the current user is changed during execution.
* After the current user is changed, the same property values are
* retrieved, but different results will be obtained.
*/
public class HirixExample {

   public static void main(String[] args) {
   // Get the URL of the properties file
      URL propertiesURL = HirixExample.class.getClassLoader().
                          getResource("example2/properties.xml");
      try {

         // Create the instance of the properties object
         HirixProperties properties = new HirixProperties() ;

         // Load the properties as you normally would
         properties.load( propertiesURL.openStream() ) ;

         // update the current context with the current user
         properties.getContext().put("user", getUserInfo1()) ;

         // Get property values and write out to the console for this example
         System.out.println( properties.get("news_source")) ;
         System.out.println( properties.get("news_max_items")) ;
         System.out.println( properties.getProperty("greeting")) ;
         System.out.println( "we will save your news in: " +
                              properties.getProperty("resource_directory")) ;

         // update the current context with the current user
         properties.getContext().put("user", getUserInfo2()) ;

         // Get property values and write out to the console for this example
         System.out.println( properties.get("news_source")) ;
         System.out.println( properties.get("news_max_items")) ;
         System.out.println( properties.getProperty("greeting")) ;
         System.out.println( "we will save your news in: " +
                              properties.getProperty("resource_directory")) ;

      } catch (IOException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }
   /**
    * Create a sample user who is a developer
    * @return
    */
   public static Map getUserInfo1 () {
      Map user = new HashMap() ;
      user.put("name", "Chad") ;
      user.put("job", "developer") ;
      return user;
   }
   /**
    * Create a sample user who is an architect
    * @return
    */
   public static Map getUserInfo2 () {
      Map user = new HashMap() ;
      user.put("name", "John") ;
      user.put("job", "architect") ;
      return user;
   }
}



清单 8 显示了执行 清单 7 的应用程序代码的实际输出。值获取自 清单 5 的属性文件中 user 的两个不同实例。您可以看到 user 的不同实例导致获取自属性的值也不同。第 1 行至第 4 行是使用由 getUserInfo1() 返回的 Map 对象的输出,而第 5 行至第 8 行是使用由 getUserInfo2() 返回的 Map 对象的输出。


清单 8. 运行清单 5 的应用程序代码的控制台输出
               
//1//   developer_news.myserver.com
//2//   5
//3//   Hello Chad we are retrieving 5 items for developer
//4//   we will save your news in:
        W:\workspace-callisto\dolphin\dolphin-hirix-example-properties/Chad
//5//   architect_news.myserver.com
//6//   10
//7//   Hello John we are retrieving 10 items for architect
//8//   we will save your news in:
        W:\workspace-callisto\dolphin\dolphin-hirix-example-properties/John


用 Groovy 脚本处理属性

Hierarchical Inherited Rule-Interpreted XML 还使您可以将动态脚本包含在 XML 属性文件内,允许您创建任意类型的值。Groovy 是一种功能强大的脚本语言,允许将 Java 及更多脚本语言的全部功能用作表达式(有关更多信息,请参阅 参考资料)。清单 9 中的示例使用了 Groovy 脚本为属性 user1 创建一个 Map 对象并在属性 user2 中创建一个实际类。


清单 9. 带有 Groovy 脚本的属性文件
               
<!-- system['key'] is equivalent to System.getProperties.get("key") -->

<!-- This is an equivalent properties file with properties grouped by conditions -->

<properties>

     <!-- Define the Map object for the user info.
          Using Groovy you can use a simple
          expression here to define the map and values.

          HirixProperties allows you to return objects as well as strings. -->

   <property id="user1">
      <value>${groovy: [name:'Chad', job:'developer']}<value>
   </property>



   <!-- Define a class using Groovy script.
        You create the class with methods that will be compatible
        with the Map using the property style access. -->

   <property id="user2">
      <value>${groovy:
         class UserInfo {
            public getName() {
               return ("John") ;
            }
            public getJob() {
               return ("architect") ;
            }
         }
         return new UserInfo() ;}
      <value>
   </property>

</properties>



清单 10 显示了如何从 Java 代码访问已定义的属性并打印结果。


清单 10. 处理属性的应用程序代码
               
   ...
         // Get property values and write out to the console for this example
         System.out.println( properties.get("user1")) ;
         System.out.println( properties.get("user2")) ;
   ...



清单 11 显示了运行 清单 10 的应用程序代码的控制台输出。第一行是 Map 对象的输出,第二行是 UserInfo 对象引用。


清单 11. 运行清单 10 的应用程序代码的控制台输出
               
{name=Chad, job=developer}
UserInfo@63026302


你可能感兴趣的:(应用服务器,xml,脚本,IBM,groovy)