Java Votary

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  48 随笔 :: 1 文章 :: 80 评论 :: 0 Trackbacks

#

对spring 了解的不够精通,这两天在解决jms异常的过程中发现,spring中提供了jmsTrasactionManager,同样实现了事务管理接口。这样用 自动的拦截器,就可以象数据库一样自动控制事务。在同时配置了JMS和数据库事务的时候,两者同时有效。这样系统的消息和数据库事务就轻量级的一致了!

数据库的spring配置参见:http://steeven.cnblogs.com/archive/2005/06/14/174410.html
jms部分如下:

    <bean id="remoteJmsConnectionFactory"
        class
="org.activemq.ActiveMQConnectionFactory">
        
<property name="useEmbeddedBroker">
            
<value>true</value>
        
</property>
        
<property name="brokerURL">
            
<value>tcp://localhost:61616</value>
        
</property>
    
</bean>

    
<bean id="jmsTM"
        class
="org.springframework.jms.connection.JmsTransactionManager">
        
<property name="connectionFactory">
            
<ref bean="remoteJmsConnectionFactory" />
        
</property>
    
</bean>

    
<bean id="jmsTransactionInterceptor"
        class
="org.springframework.transaction.interceptor.TransactionInterceptor">
        
<property name="transactionManager">
            
<ref bean="jmsTM" />
        
</property>
        
<property name="transactionAttributeSource">
            
<bean
                
class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
        
</property>
    
</bean>

    
<bean
        
class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
        
<property name="transactionInterceptor">
            
<ref bean="jmsTransactionInterceptor" />
        
</property>
    
</bean>

    
<bean id="destResolver"
        class
="test.message.EnumDestinationResolver" />

    
<!-- for send jms to remote server -->
    
<bean id="remoteJmsTemplate"
        class
="org.springframework.jms.core.JmsTemplate">
        
<property name="connectionFactory">
            
<ref bean="remoteJmsConnectionFactory" />
        
</property>
        
<property name="destinationResolver">
            
<ref local="destResolver" />
        
</property>
    
</bean>

应用程序很简单
@Transactional
public class TestServiceImpl implements TestService {
    
public void someMethod() {
        getJmsTemplate().send(someMessage);
    }

}
posted @ 2005-12-13 23:49 Dion 阅读(1374) | 评论 (0)编辑 收藏

     摘要: hibernate3.0+ejb3 annotaion配置实战+spring1.21 annotation事务控制 我是比较讨厌xml的人,没有强类型,很多配置出错,包括xdoclet都无法检查。刚好现在的主流框架总算开始支持annotation了,所以玩了一下配置,供参考:hibernate3.05hibernate-annotations-3.0beta2spring1.21几个配置...  阅读全文
posted @ 2005-12-13 23:32 Dion 阅读(2240) | 评论 (2)编辑 收藏

Tiger 中的注释,第 2 部分: 定制注释

Write your own annotations in Java 5

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Brett McLaughlin, 作者/编者, O'Reilly Media, Inc

2004 年 9 月 01 日

本系列文章的 第 1 部分介绍了注释 —— J2SE 5.0 中新的元数据工具,并重点讨论了 Tiger 的基本内置注释。一个更强大的相关特性是支持编写自己的注释。本文中,Brett McLauglin 说明了如何创建定制注释,如何用自己的注释注解文档,并进一步定制代码。

本系列的第一篇文章 介绍了什么是元数据,元数据的重要性,以及如何使用 J2SE 5.0(也叫做 Tiger)的基本内置注释。如果习惯了这些概念,您可能已经在想,Java 5 提供的三种标准注释也并不是特别健壮,能使用的只有 DeprecatedSuppressWarningsOverride 而已。所幸的是,Tiger 还允许定义自己的注释类型。在本文中,我将通过一些示例引导您掌握这个相对简单的过程。您还将了解如何对自己的注释进行注解,以及这样做的一些好处。我要 感谢 O'Reilly Media, Inc.,他们非常慷慨地允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

定义自己的注释类型


通过添加了一个小小的语法(Tiger 添加了大量的语法结构),Java 语言支持一种新的类型 —— 注释类型(annotation type)。注释类型看起来很像普通的类,但是有一些特有的性质。最明显的一点是,可以在类中以符号( @ )的形式注释其他 Java 代码。我将一步一步地介绍这个过程。

@interface 声明


定义新的注释类型与创建接口有很多类似之处,只不过 interface 关键字之前要有一个 @ 符号。清单 1 中给出的是一个最简单的注释类型的示例:
清单 1. 非常简单的注释类型

package com.oreilly.tiger.ch06;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
public @interface InProgress { }

清单 1 的含义非常明显。如果编译这个注释类型,并确信其位于类路径中,那么您就可以在自己的源代码方法中使用它,以指出某个方法或类仍在处理中,如清单 2 所示:

清单 2. 使用定制的注释类型
@com.oreilly.tiger.ch06.InProgress
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

清单 1 所示注释类型的使用方法和内置注释类型的使用方法完全相同,只不过要同时使用名称和所在的包来指示定制注释。当然,一般的 Java 规则仍然适用,您可以导入该注释类型,直接使用 @InProgress 引用它。

不要漏掉本系列的另一部分

一定要阅读本系列文章的“ 第 1 部分”,其中介绍了 Java 5.0 中的注释。

添加成员


上面所示的基本用法还远远不够健壮。您一定还记得“第 1 部分”中曾经提到的,注释类型可以有成员变量(请参阅 参考资料)。 这一点非常有用,尤其是准备将注释作为更加复杂的元数据,而不仅仅将它作为原始文档使用的时候。代码分析工具喜欢加工大量的信息,定制注释可以提供这类信息。

注释类型中的数据成员被设置成使用有限的信息进行工作。定义数据成员后不需要分别定义访问和修改的方法。相反,只需要定义一个方法,以成员的名称命名它。数据类型应该是该方法返回值的类型。清单 3 是一个具体的示例,它澄清了一些比较含糊的要求:

清单 3. 向注释类型添加成员

package com.oreilly.tiger.ch06;

/**
* Annotation type to indicate a task still needs to be
* completed.
*/
public @interface TODO {
String value();
}

尽管清单 3 看起来很奇怪,但这是注释类型所要求的格式。清单 3 定义了一个名为 value 的字符串,该注释类型能够接受它。然后,就可以像清单 4 中那样使用注释类型:

清单 4. 使用带有成员值的注释类型

@com.oreilly.tiger.ch06.InProgress
@TODO("Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

这里同样没有多少花样。清单 4 假设已经引入了 com.oreilly.tiger.ch06.TODO ,因此源代码中的注释 需要包名作前缀。此外,需要注意的是,清单 4 中采用了简写的方法:将值 ("Figure out the amount of interest per month") 直接提供给注释,没有指定成员变量名。清单 4 和清单 5 是等价的,后者没有采用简写形式:

清单 5. 清单 4 的“加长”版

@com.oreilly.tiger.ch06.InProgress
@TODO(value="Figure out the amount of interest per month")
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

当然作为编码人员,我们都不愿意跟这种“加长”版搅在一起。不过要注意,只有当注释类型只有 一个 成员变量,而且变量名为 value 时,才能使用简写形式。如果不符合这个条件,那么就无法利用这种特性。

设置默认值


迄 今为止,您已经有了一个很好的起点,但是要做得完美,还有很长的一段路要走。您可能已经想到,下一步就要为注释设置某个默认值。如果您希望用户指定某些 值,但是只有这些值与默认值不同的时候才需要指定其他的值,那么设置默认值就是一种很好的办法。清单 6 用另一个定制注释 —— 来自 清单 4TODO 注释类型的一个全功能版本,示范了这个概念及其实现:
清单 6. 带有默认值的注释类型

package com.oreilly.tiger.ch06;

public @interface GroupTODO {

public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };

Severity severity()
default Severity.IMPORTANT;
String item();
String assignedTo();
String dateAssigned();
}


清单 6 中的 GroupTODO 注释类型中添加了几个新的变量。因为该注释类型的成员变量不是一个,所以将一个变量命名为 value 没有任何意义。只要成员变量多于一个,就应该尽可能准确地为其命名。因为不可能从 清单 5所示的简写形式中获益,所以您需要创建虽然稍微有点冗长,但是更容易理解的注释类型。

清单 6 中出现的另一个新特性是注释类型定义了自己的枚举(枚举,即 enumeration,通常也称为 enums,是 Java 5 的另一个新特性。它并没有多么地不同凡响,对注释类型更是如此)。然后,清单 6 使用新定义的枚举作为一个成员变量的类型。

最后,再回到我们的主题 —— 默认值。建立默认值的过程非常琐碎,需要在成员声明的后面添加关键字 default ,然后提供默认值。正如您所料,默认值的类型必须与成员变量声明的类型完全相同。同样,这也不是什么火箭科学,只不过是词法上的变异。清单 7 给出了一个具体应用中的 GroupTODO 注释,其中 没有 指定 severity 成员:

清单 7. 使用默认值

@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
item="Figure out the amount of interest per month",
assignedTo="Brett McLaughlin",
dateAssigned="08/04/2004"
)
public void calculateInterest(float amount, float rate) {
// Need to finish this method later
}

清单 8 中使用了同一个注释,但这一次给出了 severity 的值:

清单 8. 改写默认值

@com.oreilly.tiger.ch06.InProgress
@GroupTODO(
severity=GroupTODO.Severity.DOCUMENTATION,
item="Need to explain how this rather unusual method works",
assignedTo="Jon Stevens",
dateAssigned="07/30/2004"
)
public void reallyConfusingMethod(int codePoint) {
// Really weird code implementation
}



回页首


对注释的注释


结 束关于注释的讨论之前(至少在本系列文章中),我想简要地讨论一下注释的注释。第 1 部分中所接触的预定义注释类型都有预定义的目的。但是在编写自己的注释类型时,注释类型的目的并不总是显而易见的。除了基本的文档外,可能还要针对某个特 定的成员类型或者一组成员类型编写类型。这就要求您为注释类型提供某种元数据,以便编译器保证按照预期的目的使用注释。

当然,首先想到的就是 Java 语言选择的元数据形式 —— 注释。您可以使用 4 种预定义的注释类型(称为 元注释)对您的注释进行注释。我将对这 4 种类型分别进行介绍。

指定目标


最明显的元注释就是允许何种程序元素具有定义的注释类型。毫不奇怪,这种元注释被称为 Target 。但是在了解如何使用 Target 之前,您还需要认识另一个类,该类被称为 ElementType ,它实际上是一个枚举。这个枚举定义了注释类型可应用的不同程序元素。清单 9 给出了完整的 ElementType 枚举:
清单 9. ElementType 枚举

package java.lang.annotation;

public enum ElementType {
TYPE, // Class, interface, or enum (but not annotation)
FIELD, // Field (including enumerated values)
METHOD, // Method (does not include constructors)
PARAMETER, // Method parameter
CONSTRUCTOR, // Constructor
LOCAL_VARIABLE, // Local variable or catch clause
ANNOTATION_TYPE, // Annotation Types (meta-annotations)
PACKAGE // Java package
}

清单 9 中的枚举值意义很明确,您自己可以分析其应用的目标(通过后面的注解)。使用 Target 元注释时,至少要提供这些枚举值中的一个并指出注释的注释可以应用的程序元素。清单 10 说明了 Target 的用法:

清单 10. 使用 Target 元注释

package com.oreilly.tiger.ch06;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
* Annotation type to indicate a task still needs to be completed
*/
@Target({ElementType.TYPE,
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.ANNOTATION_TYPE})
public @interface TODO {
String value();
}

现在,Java 编译器将把 TODO 应用于类型、方法、构造函数和其他注释类型。这样有助于避免他人误用您的注释类型(或者最好的地方是, 您自己也不会因为疲惫而误用它)。

设置保持性


下一个要用到的元注释是 Retention 。这个元注释和 Java 编译器处理注释的注释类型的方式有关。编译器有几种不同选择:
  • 将注释保留在编译后的类文件中,并在第一次加载类时读取它。
  • 将注释保留在编译后的类文件中,但是在运行时忽略它。
  • 按照规定使用注释,但是并不将它保留到编译后的类文件中。

这三种选项用 java.lang.annotation.RetentionPolicy 枚举表示,如清单 11 所示:

清单 11. RetentionPolicy 枚举

package java.lang.annotation;

public enum RetentionPolicy {
SOURCE, // Annotation is discarded by the compiler
CLASS, // Annotation is stored in the class file, but ignored by the VM
RUNTIME // Annotation is stored in the class file and read by the VM
}

现在可以看出, Retention 元注释类型使用清单 11 所示的枚举值中的一个作为惟一的参数。可以将该元注释用于您的注释,如清单 12 所示:

清单 12. 使用 Retention 元注释

@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
// annotation type body
}

如清单 12 所示,这里可以使用简写形式,因为 Retention 只有一个成员变量。如果要将保持性设为 RetentionPolicy.CLASS ,那么什么也不需要做,因为这就是默认行为。

添加公共文档


下一个元注释是 Documented 。这个元注释也非常容易理解,部分原因是 Documented 是一个标记注释。您应该还记得第 1 部分中曾经提到,标记注释没有成员变量。 Documented 表示注释应该出现在类的 Javadoc 中。在默认情况下,注释 包括在 Javadoc 中,如果花费大量时间注释一个类、详细说明未完成的工作、正确完成了什么或者描述行为,那么您应该记住这一点。

清单 13 说明了 Documented 元注释的用法:

清单 13. 使用 Documented 元注释

package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }


Documented 的一个实用技巧是保持性策略。注意,清单 13 中规定注释的保持性(retention)是 RUNTIME ,这是使用 Documented 注释类型所 必需的。Javadoc 使用虚拟机从其类文件(而非源文件)中加载信息。确保 VM 从这些类文件中获得生成 Javadoc 所需信息的惟一方法是将保持性规定为 RetentionPolicy.RUNTIME 。这样,注释就会保留在编译后的类文件中 并且由虚拟机加载,然后 Javadoc 可以从中抽取出来添加到类的 HTML 文档中。

设置继承


最后一个元注释 Inherited ,可能是最复杂、使用最少、也最容易造成混淆的一个。这就是说,我们简单地看一看就可以了。

首先考虑这样一种情况:假设您通过定制的 InProgress 注释标记一个类正在开发之中,这完全没有问题,对吧?这些信息还会出现在 Javadoc 中,只要您正确地应用了 Documented 元注释。现在,假设您要编写一个新类,扩展那个还在开发之中的类,也不难,是不是?但是要记住,那个超类还在开发之中。如果您使用子类,或者查看它的文档,根本没有线索表明还有什么地方没有完成。您本来希望看到 InProgress 注释被带到子类中 —— 因为这是 继承 的 —— 但情况并非如此。您必须使用 Inherited 元注释说明所期望的行为,如清单 14 所示:

清单 14. 使用 Inherited 元注释


package com.oreilly.tiger.ch06;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* Marker annotation to indicate that a method or class
* is still in progress.
*/
@Documented

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InProgress { }


添加 @Inherited 后,您将看到 InProgress 出现在注释类的子类中。当然,您并不希望所有的注释类型都具有这种行为(因此默认值是 继承的)。比如, TODO 注释就不会(也不应该)被传播。但是对于这里示范的情况, Inherited 可能非常有用。



回页首


结束语


现 在,您也许已经准备回到 Java 世界为所有的事物编写文档和注释了。这不禁令我回想起人们了解 Javadoc 之后发生的事情。我们都陷入了文档过滥的泥潭,直到有人认识到最好使用 Javadoc 来理清容易混淆的类或者方法。无论用 Javadoc 做了多少文章,也没有人会去看那些易于理解的 getXXX()setXXX() 方法。

注 释也可能出现同样的趋势,虽然不一定到那种程度。经常甚至频繁地使用标准注释类型是一种较好的做法。所有的 Java 5 编译器都支持它们,它们的行为也很容易理解。但是,如果要使用定制注释和元注释,那么就很难保证花费很大力气创建的那些类型在您的开发环境之外还有什么意 义。因此要慎重。在合理的情况下使用注释,不要荒谬使用。无论如何,注释都是一种很好的工具,可以在开发过程中提供真正的帮助。



回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 不要遗漏“ Tiger 中的注释,第 2 部分”,即本系列文章的第 2 部分,研究了定制注释。



  • 开放源代码 XDoclet代码生成引擎支持面向属性的 Java 语言编程。



  • JSR 175,将元数据工具合并到 Java 语言中的规范,处于 Java Community Process 的提议最终草案状态。



  • 访问 Sun 的主页,获取 J2SE 5.0 的所有信息



  • 可以 下载 Tiger并自己试用。



  • John Zukowski 的系列文章 Taming Tiger 以实用的基于技巧的形式讲述了 Java 5.0 的新功能。



  • 由 Brett McLaughlin 和 David Flanagan 撰写的 Java 1.5 Tiger: A Developer's Notebook 一书 (O'Reilly & Associates; 2004),以代码为中心、开发人员友好的形式,讲述了几乎所有的 Tiger 的最新功能 — 包括注释。



  • developerWorksJava 技术专区 可以找到数百篇有关 Java 技术的参考资料。



  • 访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。



  • 是否对无需通常的高成本入口点(entry point)或短期评估许可证的 IBM 测试产品感兴趣? developerWorks Subscription为 WebSphere?、DB2?、Lotus?、Rational? 和 Tivoli? 产品提供了低成本的 12 个月单用户许可证,包括基于 Eclipse 的 WebSphere Studio? IDE,用于开发、测试、评估和展示您的应用程序。




回页首


关于作者

作者照片

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机工作。在最近几年里,他已经成为 Java 和 XML 社区最知名的作者和程序员之一。他曾经在 Nextel Communications 实现复杂的企业系统,在 Lutris Technologies 编写应用程序服务器,目前在为 O'Reilly Media, Inc 撰写和编辑 书籍

posted @ 2005-12-13 23:28 Dion 阅读(826) | 评论 (0)编辑 收藏

Tiger 中的注释,第 1 部分: 向 Java 代码中添加元数据

如何使用 Java 5 的内置注释

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


对此页的评价

帮助我们改进这些内容


级别: 初级

Brett McLaughlin, 作者/编者, O'Reilly Media, Inc

2004 年 9 月 01 日

注 释,J2SE 5.0 (Tiger) 中的新功能,将非常需要的元数据工具引入核心 Java 语言。该系列文章分为两部分,在这第 1 部分中,作者 Brett McLaughlin 解释了元数据如此有用的原因,向您介绍了 Java 语言中的注释,并研究了 Tiger 的内置注释。

编程的一个最新的趋势,尤其是在 Java 编程方面,是使用 元数据。简单地说,元数据就是 关于数据的数据。元数据可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。许多元数据工具,如 XDoclet(请参阅 参考资料),将这些功能添加到核心 Java 语言中,暂时成为 Java 编程功能的一部分。

直到可以使用 J2SE 5.0(也叫做 Tiger,现在是第二个 beta 版本),核心 Java 语言才最接近具有 Javadoc 方法的元数据工具。您使用特殊的标签集合来标记代码,并执行 javadoc 命令来将这些标签转化成格式化的 HTML 页面,该页面说明标签所附加到的类。然而,Javadoc 是有缺陷的元数据工具,因为除了生成文档之外,您没有固定、实用、标准化的方式来将数据用于其他用途。HTML 代码经常混入到 Javadoc 输出中这一事实甚至更进一步降低了其用于任何其他目的的价值。

Tiger 通过名为 注释的新功能将一个更通用的元数据工 具合并到核心 Java 语言中。注释是可以添加到代码中的修饰符,可以用于包声明、类型声明、构造函数、方法、字段、参数和变量。Tiger 包含内置注释,还支持您自己编写的定制注释。本文将概述元数据的优点并向您介绍 Tiger 的内置注释。本系列文章的 第 2 部分将研究定制注释。我要感谢 O'Reilly Media, Inc.,他们非常慷慨地 允许我在本文中使用我关于 Tiger 的书籍的“注释”一章中的代码示例(请参阅 参考资料)。

元数据的价值


一 般来说,元数据的好处分为三类:文档编制、编译器检查和代码分析。代码级文档最常被引用。元数据提供了一种有用的方法来指明方法是否取决于其他方法,它们 是否完整,特定类是否必须引用其他类,等等。这确实非常有用,但对于将元数据添加到 Java 语言中来说,文档编制可能是 最不相关的理由。Javadoc 已经提供了非常容易理解和健壮的方法来文档化代码。另外,当已经存在文档编制工具,并且在大多数时候都工作得很好时,谁还要编写文档编制工具?
不要漏掉本系列的另一部分

一定要阅读本系列的“ 第 2 部分”,该部分研究了定制注释。

编译器检查


元数据更重要的优点是编译器可以使用它来执行基本的编译时检查。例如,您将在本文后面的 Override 注释中 看到 Tiger 引入了一个这样的注释,用于允许您指定一种方法覆盖超类中的另一种方法。Java 编译器可以确保在元数据中指明的行为实际发生在代码级别。如果从来没有找出过这种类型的 bug,这样做似乎有点傻,但是大多数年龄很大的 Java 编程老手都曾经花费至少多个晚上来查明他们的代码为什么不能用。当最后认识到方法的参数有错,且该方法实际上 没有 覆盖超类中的方法时,您可能更感到难受。使用元数据的工具有助于轻松地查明这种类型的错误,从而可以节省那些晚上来看长期进行的 Halo 联赛。
JSR 175

JSR 175, Java 编程语言的元数据工具,为将元数据合并到核心 Java 语言中提供了正式理由和说明(请参阅 参考资料)。根据 JSR,注释“不直接影响程序的语义。然而,开发和部署工具可以读取这些注释,并以某种形式处理这些注释,可能生成其他 Java 编程语言源文件、XML 文档或要与包含注释的程序一起使用的其他构件。”

代码分析


可 以证明,任何好的注释或元数据工具的最好功能就是可以使用额外数据来分析代码。在一个简单的案例中,您可能构建代码目录,提供必需的输入类型并指明返回类 型。但是,您可能想,Java 反射具有相同的优点;毕竟,可以为所有这些信息内省代码。这从表面上看似乎是正确的,但是在实际中通常不使用。许多时候,方法作为输入接受的或者作为输出 返回的类型实际上不是该方法想要的类型。例如,参数类型可能是 Object ,但方法可能仅使用 Integer 。这在好些情况下很容易发生,比如在方法被覆盖而超类使用常规参数声明方法时,还有正在进行许多序列化的系统中也容易发生。在这两种情况中,元数据可以指示代码分析工具,虽然参数类型是 Object ,但 Integer 才是真正需要的。这类分析非常有用,但也不能夸大它的价值。

在 更复杂的情况下,代码分析工具可以执行所有种类的额外任务。示例 du jour 是 Enterprise JavaBean (EJB) 组件。甚至简单 EJB 系统中的依赖性和复杂性都非常令人吃惊。您具有了 home 接口和远程接口,以及本地接口和本地 home 接口,还有一个实现类。保持所有这些类同步非常困难。但是,元数据可以提供这个问题的解决放案。好的工具(还是要提一下 XDoclet)可以管理所有这些依赖性,并确保无“代码级”连接、但有“逻辑级”捆绑的类保持同步。元数据在这里确实可以发挥它的作用。



回页首


注释的基本知识


现在已经了解了元数据的好处,我将介绍 Tiger 中的注释。注释采用“at”标记形式 ( @ ),后面是注释名称。然后在需要数据时,通过 name=value 对向注释提供数据。每次使用这类表示法时,就是在生成注释。一段代码可能会有 10 个、50 个或更多的注释。不过,您将发现多个注释都可能使用相同的 注释类型。类型是实际使用的结构,在特定上下文中,注释本身是该类型的具体使用(请参阅侧栏 注释或注释类型?)。
注释或注释类型?

是否对什么是注释与什么是注释类型感到迷惑?了解这个的最简单方法就是对比所熟悉的 Java 语言概念来想。可以定义一个类(例如 Person ),则在 JVM 中将总是仅有该类的一个版本(假设没有进行麻烦的类路径设置)。然而,在任何给定时间,可能会使用该类的 10 个或 20 个 实例。仍然是只有一个 Person 类,但是它以不同的方式使用多次。注释类型和注释也是这样。注释类型类似于类,注释类似于该类的实例。

注释分为三个基本种类:

  • 标记注释没有变量。注释显示简单,由名称标识,没有提供其他数据。例如, @MarkerAnnotation 是标记注释。它不包含数据,仅有注释名称。

  • 单一值注释与标记注释类似,但提供一段数据。因为仅提供很少的一点数据,所以可以使用快捷语法(假设注释类型接受此语法): @SingleValueAnnotation("my data") 。除了 @ 标记外,这应该与普通的 Java 方法调用很像。

  • 完整注释有多个数据成员。因此,必须使用更完整的语法(注释不再像普通的 Java 方法): @FullAnnotation(var1="data value 1", var2="data value 2", var3="data value 3")

除了通过默认语法向注释提供值外,还可以在需要传送多个值时使用名称-值对。还可以通过花括号为注释变量提供值数组。清单 1 显示了注释中的值数组的示例。

清单 1. 在注释中使用按数组排列的值

@TODOItems({ // Curly braces indicate an array of values is being supplied
@TODO(
severity=TODO.CRITICAL,
item="Add functionality to calculate the mean of the student's grades",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.IMPOTANT,
item="Print usage message to screen if no command-line flags specified",
assignedTo="Brett McLaughlin"
),
@TODO(
severity=TODO.LOW,
item="Roll a new website page with this class's new features",
assignedTo="Jason Hunter"
)
})

清单 1 中的示例并没有乍一看那样复杂。 TODOItems 注释类型有一个具有值的变量。这里提供的值比较复杂,但 TODOItems 的使用实际与单一值注释类型相符,只是这里的单一值是数组而已。该数组包含三个 TODO 注释,其中每个注释都是多值的。逗号分隔每个注释内的值,以及单个数组内的值。非常容易,是吧?

但是我讲的可能超前了些。 TODOItemsTODO定制注释, 是本系列文章第 2 部分中的主题。但是我想让您看到,即使复杂注释(清单 1 几乎是最复杂的注释)也不是非常令人害怕的。当提到 Java 语言的标准注释类型时,将很少看到如此复杂的情况。正如将在下面三个部分了解到的,Tiger 的基本注释类型的使用都极其简单。



回页首


Override 注释


Tiger 的第一个内置注释类型是 OverrideOverride 应该仅用于方法(不用于类、包声明或其他构造)。它指明注释的方法将覆盖超类中的方法。清单 2 显示了简单的示例。 清单 2. 操作中的 Override 注释

package com.oreilly.tiger.ch06;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hashCode() {
return toString().hashCode();
}
}

清单 2 应该很容易理解。 @Override 注释对两个方法进行了注释 — toString()hashCode() ,来指明它们覆盖 OverrideTester 类的超类 ( java.lang.Object ) 中的方法的版本。开始这可能看起来没什么作用,但它实际上是非常好的功能。如果不覆盖这些方法,根本 无法 编译此类。该注释还确保当您将 toString() 弄乱时,至少还有某种指示,即应该确保 hashCode() 仍旧匹配。

当编码到很晚且输错了某些东西时,此注释类型真的可以发挥很大的作用,如清单 3 中所示。

清单 3. 使 Override 注释捕获打字稿


package com.oreilly.tiger.ch06;

public class OverrideTester {

public OverrideTester() { }

@Override
public String toString() {
return super.toString() + " [Override Tester Implementation]";
}

@Override
public int hasCode() {
return toString().hashCode();
}
}

在清单 3 中, hashCode() 错误地输入为 hasCode() 。注释指明 hasCode() 应该覆盖方法。但是在编译中, javac 将发现超类 ( java.lang.Object ) 没有名为 hasCode() 的方法可以覆盖。因此,编译器将报错,如图 1 中所示。

图 1. 来自 Override 注释的编译器警告

缺少的功能

在单一值注释类型中,如果 Deprecated 允许包含错误类型消息将更好。然后,当用户使用声明为过时的方法时,编译器可以打印消息。该消息可以指明使用方法的结果如何重要,说明何时将停止方法,甚至建议备用方法。可能 J2SE 的下一版本(“Mustang”,他们这样命名)将提供这种功能。

这个便捷的小功能将帮助快速捕获打字稿。



回页首


Deprecated 注释


下一个标准注释类型是 Deprecated 。与 Override 一样, Deprecated 是标记注释。正如您可能期望的,可以使用 Deprecated 来对不应再使用的方法进行注释。与 Override 不一样的是, Deprecated 应该与正在声明为过时的方法放在同一行中(为什么这样?说实话我也不知道),如清单 4 中所示。 清单 4. 使用 Deprecated 注释

package com.oreilly.tiger.ch06;

public class DeprecatedClass {

@Deprecated public void doSomething() {
// some code
}

public void doSomethingElse() {
// This method presumably does what doSomething() does, but better
}
}

单独编译此类时,不会发生任何不同。但是如果通过覆盖或调用来使用声明为过时的方法,编译器将处理注释,发现不应该使用该方法,并发出错误消息,如图 2 中所示。

图 2. 来自 Deprecated 注释的编译器警告

注意需要开启编译器警告,就像是必须向 Java 编译器指明想要普通的声明为过时警告。可以使用下列两个标记之一和 javac 命令: -deprecated 或新的 -Xlint:deprecated 标记。



回页首


SuppressWarnings 注释


从 Tiger “免费”获得的最后一个注释类型是 SuppressWarnings 。发现该类型的作用应该不难,但是 为什么该注释类型如此重要通常不是很明显。它实际上是 Tiger 的所有新功能的副功能。例如,以泛型为例;泛型使所有种类的新类型安全操作成为可能,特别是当涉及 Java 集合时。然而,因为泛型,当使用集合而 没有 类型安全时,编译器将抛出警告。这对于针对 Tiger 的代码有帮助,但它使得为 Java 1.4.x 或更早版本编写代码非常麻烦。将不断地收到关于根本无关的事情的警告。如何才能使编译器不给您增添麻烦?

SupressWarnings 可以解决这个问题。 SupressWarningsOverrideDeprecated 不同, 具有变量的 — 所以您将单一注释类型与该变量一起使用。可以以值数组来提供变量,其中每个值指明要阻止的一种特定警告类型。请看清单 5 中的示例,这是 Tiger 中通常会产生错误的一些代码。

清单 5. 不是类型安全的 Tiger 代码

public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

图 3 显示了清单 5 中代码的编译结果。

图 3. 来自非标准代码的编译器警告

清单 6 通过使用 SuppressWarnings 注释消除了这种问题。

清单 6. 阻止警告

@SuppressWarings(value={"unchecked"})
public void nonGenericsMethod() {
List wordList = new ArrayList(); // no typing information on the List

wordList.add("foo"); // causes error on list addition
}

非常简单,是吧?仅需要找到警告类型(图 3 中显示为“unchecked”),并将其传送到 SuppressWarnings 中。

SuppressWarnings 中变量的值采用数组,使您可以在同一注释中阻止多个警告。例如, @SuppressWarnings(value={"unchecked", "fallthrough"}) 使用两个值的数组。此功能为处理错误提供了非常灵活的方法,无需进行大量的工作。



回页首


结束语

虽 然这里看到的语法可能都是新的,但您应该知道注释非常容易理解和使用。也就是说,与 Tiger 一起提供的标准注释相当简单,可以添加许多功能。元数据正日益变得有帮助,您肯定会提出非常适用于自己的应用程序的注释类型。在本系列文章的第 2 部分,我将详细说明 Tiger 对编写自己的注释类型的支持。您将了解如何创建 Java 类以及将其定义为注释类型,如何使编译器识别您的注释类型,以及如何使用该类型对代码进行注释。我甚至会更深入地说明奇异但有用的对注释进行注释的任务。 您将快速熟悉 Tiger 中的这一新构造。



回页首


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 不要遗漏“ Tiger 中的注释,第 2 部分”,即本系列文章的第 2 部分,研究了定制注释。



  • 开放源代码 XDoclet代码生成引擎支持面向属性的 Java 语言编程。



  • JSR 175,将元数据工具合并到 Java 语言中的规范,处于 Java Community Process 的提议最终草案状态。



  • 访问 Sun 的主页,获取 J2SE 5.0 的所有信息



  • 可以 下载 Tiger并自己试用。



  • John Zukowski 的系列文章 Taming Tiger 以实用的基于技巧的形式讲述了 Java 5.0 的新功能。



  • 由 Brett McLaughlin 和 David Flanagan 撰写的 Java 1.5 Tiger: A Developer's Notebook 一书 (O'Reilly & Associates; 2004),以代码为中心、开发人员友好的形式,讲述了几乎所有的 Tiger 的最新功能 — 包括注释。



  • developerWorksJava 技术专区 可以找到数百篇有关 Java 技术的参考资料。



  • 访问 Developer Bookstore,获得技术书籍的完整列表,其中包括数百本 Java 相关主题的书籍。



  • 是否对无需通常的高成本入口点(entry point)或短期评估许可证的 IBM 测试产品感兴趣? developerWorks Subscription为 WebSphere?、DB2?、Lotus?、Rational? 和 Tivoli? 产品提供了低成本的 12 个月单用户许可证,包括基于 Eclipse 的 WebSphere Studio? IDE,用于开发、测试、评估和展示您的应用程序。




回页首


关于作者

作者照片

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机工作。在最近几年里,他已经成为 Java 和 XML 社区最知名的作者和程序员之一。他曾经在 Nextel Communications 实现复杂的企业系统,在 Lutris Technologies 编写应用程序服务器,目前在为 O'Reilly Media, Inc 撰写和编辑 书籍

posted @ 2005-12-13 23:26 Dion 阅读(899) | 评论 (0)编辑 收藏

Java Annotation入门

作者:cleverpig





版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:cleverpig(作者的Blog:http://blog.matrix.org.cn/page/cleverpig)
原 文:[http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html]http://www.matrix.org.cn/resource/article/44/44048_Java+Annotation.html[/url]
关键字:Java,annotation,标注


摘要:
本 文针对java初学者或者annotation初次使用者全面地说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简 单的annotation程序,但是对于一些高级的annotation应用(例如使用自定义annotation生成javabean映射xml文件) 还需要进一步的研究和探讨。涉及到深入annotation的内容,作者将在后文《Java Annotation高级应用》中谈到。

同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,

一、为什么使用Annotation:

在JAVA应用中,我们常遇到一些需要使用模版代码。例如,为了编写一个JAX-RPC web service,我们必须提供一对接口和实现作为模版代码。如果使用annotation对远程访问的方法代码进行修饰的话,这个模版就能够使用工具自动生成。
另 外,一些API需要使用与程序代码同时维护的附属文件。例如,JavaBeans需要一个BeanInfo Class与一个Bean同时使用/维护,而EJB则同样需要一个部署描述符。此时在程序中使用annotation来维护这些附属文件的信息将十分便利 而且减少了错误。

二、Annotation工作方式:

在5.0 版之前的Java平台已经具有了一些ad hoc annotation机制。比如,使用transient修饰符来标识一个成员变量在序列化子系统中应被忽略。而@deprecated这个 javadoc tag也是一个ad hoc annotation用来说明一个方法已过时。从Java5.0版发布以来,5.0平台提供了一个正式的annotation功能:允许开发者定义、使用 自己的annoatation类型。此功能由一个定义annotation类型的语法和一个描述annotation声明的语法,读取annotaion 的API,一个使用annotation修饰的class文件,一个annotation处理工具(apt)组成。
annotation并不直接影响代码语义,但是它能够工作的方式被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。annotation可以从源文件、class文件或者以在运行时反射的多种方式被读取。
当然annotation在某种程度上使javadoc tag更加完整。一般情况下,如果这个标记对java文档产生影响或者用于生成java文档的话,它应该作为一个javadoc tag;否则将作为一个annotation。

三、Annotation使用方法:

1。类型声明方式:
通常,应用程序并不是必须定义annotation类型,但是定义annotation类型并非难事。Annotation类型声明于一般的接口声明极为类似,区别只在于它在interface关键字前面使用“@”符号。
annotation 类型的每个方法声明定义了一个annotation类型成员,但方法声明不必有参数或者异常声明;方法返回值的类型被限制在以下的范围: primitives、String、Class、enums、annotation和前面类型的数组;方法可以有默认值。

下面是一个简单的annotation类型声明:
清单1:

    /**
     * Describes the Request-For-Enhancement(RFE) that led
     * to the presence of the annotated API element.
     */
    public @interface RequestForEnhancement {
        int    id();
        String synopsis();
        String engineer() default "[unassigned]";
        String date();    default "[unimplemented]";
    }

代码中只定义了一个annotation类型RequestForEnhancement。

2。修饰方法的annotation声明方式:
annotation 是一种修饰符,能够如其它修饰符(如public、static、final)一般使用。习惯用法是annotaions用在其它的修饰符前面。 annotations由“@+annotation类型+带有括号的成员-值列表”组成。这些成员的值必须是编译时常量(即在运行时不变)。

A:下面是一个使用了RequestForEnhancement annotation的方法声明:
清单2:

    @RequestForEnhancement(
        id       = 2868724,
        synopsis = "Enable time-travel",
        engineer = "Mr. Peabody",
        date     = "4/1/3007"
    )
    public static void travelThroughTime(Date destination) { ... }


B:当声明一个没有成员的annotation类型声明时,可使用以下方式:
清单3:

    /**
     * Indicates that the specification of the annotated API element
     * is preliminary and subject to change.
     */
    public @interface Preliminary { }


作为上面没有成员的annotation类型声明的简写方式:
清单4:

    @Preliminary public class TimeTravel { ... }


C:如果在annotations中只有唯一一个成员,则该成员应命名为value:
清单5:

    /**
     * Associates a copyright notice with the annotated API element.
     */
    public @interface Copyright {
        String value();
    }


更为方便的是对于具有唯一成员且成员名为value的annotation(如上文),在其使用时可以忽略掉成员名和赋值号(=):
清单6:

    @Copyright("2002 Yoyodyne Propulsion Systems")
    public class OscillationOverthruster { ... }


3。一个使用实例:
结合上面所讲的,我们在这里建立一个简单的基于annotation测试框架。首先我们需要一个annotation类型来表示某个方法是一个应该被测试工具运行的测试方法。
清单7:

    import java.lang.annotation.*;

    /**
     * Indicates that the annotated method is a test method.
     * This annotation should be used only on parameterless static methods.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test { }


值得注意的是annotaion类型声明是可以标注自己的,这样的annotation被称为“meta-annotations”。

在 上面的代码中,@Retention(RetentionPolicy.RUNTIME)这个meta-annotation表示了此类型的 annotation将被虚拟机保留使其能够在运行时通过反射被读取。而@Target(ElementType.METHOD)表示此类型的 annotation只能用于修饰方法声明。

下面是一个简单的程序,其中部分方法被上面的annotation所标注:
清单8:

    public class Foo {
        @Test public static void m1() { }
        public static void m2() { }
        @Test public static void m3() {
            throw new RuntimeException("Boom");
        }
        public static void m4() { }
        @Test public static void m5() { }
        public static void m6() { }
        @Test public static void m7() {
            throw new RuntimeException("Crash");
        }
        public static void m8() { }
    }

Here is the testing tool:

    import java.lang.reflect.*;

    public class RunTests {
       public static void main(String[] args) throws Exception {
          int passed = 0, failed = 0;
          for (Method m : Class.forName(args[0]).getMethods()) {
             if (m.isAnnotationPresent(Test.class)) {
                try {
                   m.invoke(null);
                   passed++;
                } catch (Throwable ex) {
                   System.out.printf("Test %s failed: %s %n", m, ex.getCause());
                   failed++;
                }
             }
          }
          System.out.printf("Passed: %d, Failed %d%n", passed, failed);
       }
    }


这 个程序从命令行参数中取出类名,并且遍历此类的所有方法,尝试调用其中被上面的测试annotation类型标注过的方法。在此过程中为了找出哪些方法被 annotation类型标注过,需要使用反射的方式执行此查询。如果在调用方法时抛出异常,此方法被认为已经失败,并打印一个失败报告。最后,打印运行 通过/失败的方法数量。
下面文字表示了如何运行这个基于annotation的测试工具:

清单9:

    $ java RunTests Foo
    Test public static void Foo.m3() failed: java.lang.RuntimeException: Boom
    Test public static void Foo.m7() failed: java.lang.RuntimeException: Crash
    Passed: 2, Failed 2


四、Annotation分类:

根据annotation的使用方法和用途主要分为以下几类:

1。内建Annotation——Java5.0版在java语法中经常用到的内建Annotation:
@Deprecated用于修饰已经过时的方法;
@Override用于修饰此方法覆盖了父类的方法(而非重载);
@SuppressWarnings用于通知java编译器禁止特定的编译警告。

下面代码展示了内建Annotation类型的用法:
清单10:

package com.bjinfotech.practice.annotation;

/**
* 演示如何使用java5内建的annotation
* 参考资料:
* http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
* http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html
* http://mindprod.com/jgloss/annotations.html
* @author cleverpig
*
*/
import java.util.List;

public class UsingBuiltInAnnotation {
        //食物类
        class Food{}
        //干草类
        class Hay extends Food{}
        //动物类
        class Animal{
                Food getFood(){
                        return null;
                }
                //使用Annotation声明Deprecated方法
                @Deprecated
                void deprecatedMethod(){
                }
        }
        //马类-继承动物类
        class Horse extends Animal{
                //使用Annotation声明覆盖方法
                @Override
                Hay getFood(){
                        return new Hay();
                }
                //使用Annotation声明禁止警告
                @SuppressWarnings({"deprecation","unchecked"})
                void callDeprecatedMethod(List horseGroup){
                        Animal an=new Animal();
                        an.deprecatedMethod();
                        horseGroup.add(an);
                }
        }
}


2。开发者自定义Annotation:由开发者自定义Annotation类型。
下面是一个使用annotation进行方法测试的sample:

AnnotationDefineForTestFunction类型定义如下:
清单11:

package com.bjinfotech.practice.annotation;

import java.lang.annotation.*;
/**
* 定义annotation
* @author cleverpig
*
*/
//加载在VM中,在运行时进行映射
@Retention(RetentionPolicy.RUNTIME)
//限定此annotation只能标示方法
@Target(ElementType.METHOD)
public @interface AnnotationDefineForTestFunction{}


测试annotation的代码如下:

清单12:

package com.bjinfotech.practice.annotation;

import java.lang.reflect.*;

/**
* 一个实例程序应用前面定义的Annotation:AnnotationDefineForTestFunction
* @author cleverpig
*
*/
public class UsingAnnotation {
        @AnnotationDefineForTestFunction public static void method01(){}
        
        public static void method02(){}
        
        @AnnotationDefineForTestFunction public static void method03(){
                throw new RuntimeException("method03");
        }
        
        public static void method04(){
                throw new RuntimeException("method04");
        }
        
        public static void main(String[] argv) throws Exception{
                int passed = 0, failed = 0;
                //被检测的类名
                String className="com.bjinfotech.practice.annotation.UsingAnnotation";
                //逐个检查此类的方法,当其方法使用annotation声明时调用此方法
            for (Method m : Class.forName(className).getMethods()) {
               if (m.isAnnotationPresent(AnnotationDefineForTestFunction.class)) {
                  try {
                     m.invoke(null);
                     passed++;
                  } catch (Throwable ex) {
                     System.out.printf("测试 %s 失败: %s %n", m, ex.getCause());
                     failed++;
                  }
               }
            }
            System.out.printf("测试结果: 通过: %d, 失败: %d%n", passed, failed);
        }
}


3。使用第三方开发的Annotation类型
这也是开发人员所常常用到的一种方式。比如我们在使用Hibernate3.0时就可以利用Annotation生成数据表映射配置文件,而不必使用Xdoclet。

五、总结:

1。 前面的文字说明了annotation的使用方法、定义方式、分类。初学者可以通过以上的说明制作简单的annotation程序,但是对于一些高级的 annotation应用(例如使用自定义annotation生成javabean映射xml文件)还需要进一步的研究和探讨。

2。同时,annotation运行存在两种方式:运行时、编译时。上文中讨论的都是在运行时的annotation应用,但在编译时的annotation应用还没有涉及,因为编译时的annotation要使用annotation processing tool。

涉及以上2方面的深入内容,作者将在后文《Java Annotation高级应用》中谈到。

六、参考资源:
·Matrix-Java开发者社区:http://www.matrix.org.cn
·http://java.sun.com/docs/books/tutorial/java/javaOO/annotations.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html
·作者的Blog:http://blog.matrix.org.cn/page/cleverpig
posted @ 2005-12-13 23:22 Dion 阅读(58717) | 评论 (39)编辑 收藏

扩展 Spring 的 JMX 支持

用 Spring 框架定制资源管理

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论

样例代码


对此页的评价

帮助我们改进这些内容


级别: 中级

Claude Duguay , 高级 J2EE 架构师, Capital Stream Inc.

2005 年 11 月 24 日

Spring 框架将体系结构依赖性降至最低,并且将应用程序中的组成部分进行了具体化,但是应用程序仍然是需要管理的。幸运的是,Spring 1.2 包括高级的 JMX 集成支持,并且 JMX 为应用程序提供了一种实用的管理基础架构。在本文中,Claude Duguay 从 Spring JMX 更进一步,向您展示了如何为方法和属性透明地增加通知事件。最后得到的代码使您可以监视状态变化,同时不会搞乱 Java™ 对象。

虽 然 Spring 框架的 JMX 管理基础架构的默认配置已经很不错了,但是仍然有定制的余地,特别是涉及 Model MBean 提供的更高层功能时。在本文中,我使用了一种相对简单的操作 —— 为基于 Spring 的应用程序的方法和属性增加通知事件 —— 以帮助您熟悉对 Spring JMX 的定制。从头到尾完成我的例子后,您将可以根据自己应用程序的需要调整 Spring JMX 管理基础架构。

我首 先对 JMX API、Spring 框架和 Spring JMX 进行简单回顾,然后转入开发扩展。第一个扩展让我可以用一个外部 XML 格式配置 MBean 元数据,这个格式(像 Hibernate 映射文件)可以与 Java 对象一起存储在类路径中。我的第二个扩展为 ModelMBean 类增加一个简单的命名规范,以透明地配置定制的通知消息。在属性改变时或者调用了特定的方法之前或者之后触发新的通知消息。

文章的最后是一个基于 mockup 服务对象的实际例子,需要管理它的启动和停止方法和读写属性。我用一个专门为此设计的小型客户机/服务器应用程序测试了这个实现。应用服务器是一个标准 Java 5.0 MBeanServer,并补充了源自 MX4J 开放源码项目的 HTTP 适配器。关于文章源代码请参阅 下载,而技术下载请参阅 参考资料

JMX 概述

Java Management Extensions(JMX)是管理和监视网络上的服务的、基于 Java 的标准。JMX API 的核心是受管 bean,即 MBean。MBean 为受管资源(如应用程序、服务和设备)提供了设施层。简而言之,MBean 提供了一种灵活的、基于适配器的体系结构,用于开放基于 Java 的(或者 Java 包装的)资源的属性和操作。开放后,就可以用浏览器和 HTTP 连接或者通过像 SMTP 或者 SOAP 这样的协议监视和管理这些资源。

编写和部署的 MBean 是通过 MBeanServer 接口开放的,以使不同的应用程序视图具有交互性。MBeanServer 实例还可以结合到任意的联合关系中,构成更复杂的分布式环境。

JMX 标准提供了四种不同的 MBean:

  • Standard MBean 直接实现用于管理对象的方法,既可以通过实现一个由程序员定义的、类名以 “MBean” 结束的接口,也可以使用一个以一个类作为构造函数参数的 Standard MBean 实例,加上一个可选的接口类规范。这个接口可以开放用于管理的部分对象方法。

  • Dynamic MBean 用属性访问器动态地访问属性,并用一个一般化的 invoke() 方法调用方法。可用的方法是在 MBeanInfo 接口中指定的。这种方式更灵活,但是不具有像 Standard MBean 那样的类型安全性。它极大地降低了耦合性,可管理的 POJO(纯粹的老式 Java 对象)不需要实现特定的接口。

  • Model MBean 提供了一个改进的抽象层,并扩展了 Dynamic MBean 模型以进一步减少对给定实现的依赖性。这对于可能使用多个版本的 JVM 或者需要用松散耦合管理第三方类的情况会有帮助。Dynamic MBean 与 Model MBean 之间的主要区别是,在 Model MBean 中有额外的元数据。

  • Open MBean 是受限的 Model MBean,它限制类型为固定的一组类型,以得到最大的可移植性。通过限制数据类型,可以使用更多的适配器,并且像 SMTP 这样的技术可以更容易适应 Java 应用程序的管理。这种变体还指定了数组和表等标准结构以改进复合对象的管理。

如 果要同时控制客户机和服务器,那么 Standard MBean 是最容易实现的一种变体。它们的优点是有类型,但是如果在更一般化的管理控制台环境中使用时会缺少一些灵活性。如果计划使用 Dynamic MBean,那么您也可以更一步使用 Model MBean,在大多数情况下它会改善抽象层而几乎不会增加复杂性。Open MBean 是是可移植性最高的一种变体,如果需要开放复合对象,那么它是惟一的方法。不幸的是,在 Open MBean 中开放复合结构所需要的代码数量过多,只有在需要高级的商业管理解决方案时才合算。

JMX 还支持使用带过滤器和广播器的事件模型的通知。为此目的,Standard MBean 需要声明一个 MBeanInfo 元数据描述。 Standard MBean 实现通常在内部构造这些内容,开发人员不能直接看到它们。在本文后面,您会看到如何用 Model MBean 元数据的 XML 描述符格式和 Spring 的 JMX 支持进行实际上透明的配置。



回页首


Spring 提供帮助

像 J2EE 一样,Spring 框架在一个体系结构中提供了许多强大的 Java 开发功能。与 J2EE 不同的是,Spring 开放型的技术来源提供了范围广泛的选择,不再有依赖性的负担。例如,Spring 的对象关系映射工具可以复制 Enterprise JavaBean 的行为,同时不会导致不灵活。虽然 EJB 规范限制了这种方式,但是 Spring 提供了大量技术接口,使您可以选择最适合应用程序要求的接口,或者在需要时创建自己的接口。与此类似,利用 Spring 的动态代理类为 Java 对象增加事务性或者安全限制,使它们保持整洁并针对应用程序空间而不是基础架构要求。

Spring 的支持 AOP 的、以复合为中心的(IOC)bean 可以很大程度上使基础架构和业务对象彼此分离。因此,横切关注点(如日志、事务和安全)不会再干扰应用程序代码。

IOC (控制反转)是减少耦合度的主要策略。Spring 的 IOC 实现使用依赖性注入有效地将控制从应用程序代码 “反转”到 Spring 容器。Spring 不是在创建时将类耦合到应用程序的对象图,它使您可以用 XML 或者属性文件(尽管 XML 被认为是最好的方法)配置类及它们的依赖性。然后用标准访问器将引用“注入”到类所依赖的对象中。可以将它看成具体化复合(externalizing composition),在典型应用程序中,它的比重远远大于继承。

AOP 是在应用程序开发中管理横切关注点的关键。就像在传统面向对象编程中实现的那样,这些关注点是作为单独的实例处理的,有可能在应用程序类中产生互不相关的代码(就是混乱)。 Spring 使用 AOP 规范和一个 XML 配置文件具体化横切关注点,因而保持了 Java 代码的纯洁性。



回页首


介绍 Spring JMX

Spring 1.2 中的 JMX 支持使用容易配置的 bean 代理提供了自动 MBeanServer 注册,并支持标准 JSR-160 远程连接器。在最简单的情况下,可以用 Spring JMX 以 MBeanExporter 类注册对象。Spring 自动识别 StandardMBean 或者用 ModelMBean 代理包装对象,在默认情况下使用内省。可以以显式引用使用 BeanExporter 以声明 bean,或者可以用默认策略或更复杂的策略自动检测 bean。

Spring 1.2 提供的大量装配器使得透明地构造 MBean 成为可能,包括使用内省、Standard MBean 接口、元数据(使用类级别注释)和显式声明的方法名。Spring 的基于导出器和装配器的模型容易扩展,并在创建注册的 MBean 时提供所需要的控制能力。

JMX 使用 ObjectName 语言注册和访问管理对象。如果选择使用自动注册,那么 Spring 提供了不同的命名策略。使用“键”命名策略时,可以使用一个属性把 MBean 名与 NameObject 实例关联起来。如果实现 ManagedResource 接口,那么可以使用元数据命名规范。由于 Spring 高度灵活的体系结构和大量扩展点,还可以实现自已的策略。

在默认情况下,Spring 会发现运行的 MBeanServer 实例,如果没有实例在运行或者没有显式声明的话,它会创建一个默认实例。用 Spring 配置直接实例化自己的 MBeanServer 与使用各种连接器同样容易。Spring 通过客户机和服务器连接提供控制,并提供客户机代理以协助客户端编程。

所 有这些功能都是 Spring 1.2 默认提供的。虽然 Spring JMX 提供了大量选项,但是默认的导出器对于许多项目来说已经足够了,使您可以很快地投入运行。不过,使用 JMX 时,在使用隐式 MBean 构造时会注意到一些特性。结果,可能会慢慢地从 Standard MBean 转移到 Model MBean,它允许对应用程序的属性、操作和通知元数据施加更多的控制。要保留松散耦合的好处(也就是 Spring 灵活的体系结构内在的优点),需要在 Java 对象之外实现这个控制。

Spring 的 IOC 使得从外部连接(wire)对象依赖性容易了,在 Spring 的体系结构中很容易利用这种优点。IOC 保持对象依赖性的可注入性,这使得增加、替换或者补充对象的行为(包括 Spring 的 JMX 支持)变得轻而易举。在本文的其余部分,我将重点放到扩展 Spring JMX 以得到更细化的应用程序管理,而不会搞乱应用程序代码或者破坏 Spring 固有的灵活性。



回页首


扩展 Spring JMX

Spring 框架提供了许多处理 JMX 的有用工具,包括用于扩展 JMX 功能的扩展点。我将利用它们获得对 MBeanInfo 声明的更多控制,同时不用对 Java 对象施加注释。为此,我将要以两种方式扩展 Spring JMX:第一种方法可以用一个外部 XML 格式(类似于 JBoss 微内核)配置 MBean 元数据,第二种方法可以与其相关联的 Java 对象一同存储在在类路径中(很像 Hibernate 映射文件)。

推荐的文章

Spring 1.2 的文档有整整一章关于 JMX 的内容,如果想要在 Spring 中使用 JMX 就应该阅读它。有关 Spring 1.2 文档的内容请参阅 参考资料

我将扩展 RequiredModelMBean 类,让它使用一个简单的命名规范,以 <ClassName>.mbean.xml 格式寻找相关的 MBean 部署描述符。定义这种基于 XML 的 MBean 描述符改进了对应用程序元数据的控制,而不会失去基于 POJO 的设计和 Spring 复合的灵活性。为了实现这一点,我将实现自己的装配器并扩展基本的 Spring JMX 导出器。扩展的导出器可以创建扩展的 ModelMBean,它支持截获属性的改变以及 beforeafter 方法执行的通知。我可以用 Spring 的 AOP 机制完成这一切,但是 ModelMBean 已经是底层受管资源的代理,因此我将用更直接的方式扩展 RequiredModelMbean 类。

管理基础

不管使用什么技术,在管理资源时有三个要关注的主要领域:

  • 属性(attribute) (有时称为属性(property)、字段或者变量)。只读属性对于开放度量或者状态很有用。读/写属性使管理员可以改变配置。

  • 动作(action) (可执行调用,对于 Java 代码来说就是方法)。动作用于触发像启动和关闭这样的事件,或者其他特定于应用程序的操作。

  • 事件(event) (向监视系统发出的通知,反映状态改变或者一些操作的执行)。通知确认操作或者状态改变确实发生了。通知还可以用于触发事件,如对于超过设置阀值(比如内 存或者磁盘空间等资源不足)的状态改变做出反应。这种通知可以用于在系统需要关注时向应用程序管理员发送电子邮件或者传呼。

在 扩展 Spring JMX 时,我将用一个简单的案例分别展示这三个需要关注的领域:一个带有开始和停止方法并且有一个读写属性要管理的示例服务。我还要用一个小型的客户机/服务器 应用程序测试这个实现,并开放一个使用 MX4J 适配器的 HTTP 管理接口。所有例子将有必要的限制,但是足以使您理解相应的概念。您会看到在基于 Spring 的应用程序方法和属性中加入 JMX 通知事件有多么容易,结果是可以监视状态改变,同时不会在 Java 对象中加入不必要的代码。

MBeanInfo 模型

如果下载与本文有关的代码,您会发现一个名为 com.claudeduguay.mbeans.model 的包,它包含一组用于建模 MBeanInfo XML 文件的类。这个包包含大量类,因此我不会详细讨论所有类,但是其基本内容还是有必要说明的。

模型的根是 MBeanDescriptor 类,它提供了一个 createMBeanInfo() 方法,负责用应用程序元数据创建一个兼容 JMX 的 ModelMBeanInfo 实例。MBeanDescriptorUtil 类提供了两个静态的 read() 方法,它装载并保存这个 XML 文档模型。这个模型中使用的 DocumentElement 实例基于 JDOM 框架。

我使用的基本 MBeanInfo 相关类和描述符模型是密切相关的。基类中的所有基本属性都是在 XML 中建模的,并可以用简单的格式定义。例如,<mbean> 标记是我的文档的根。它与 ModelMBeanInfo 直接相关,它期待一个 typedescription 属性。类型是受管 bean 的完全限定类名。不过,在使用我的 Sping 解决方案时,这个类完全可以在上下文中派生。<mbean> 标记期待零个或者多个 attributeoperationconstructornotification 子类型。它们每一个都提供了基 MBeanInfo 类 XML 属性。


图 1. MBean XML 格式
图 1. MBean XML 格式

MBean 模型实现利用了 com.claudeduguay.util.jdom 包中几个与 JDOM 相关的工具类。它们主要是解析和构建 DocumentElement 对象的接口,一个工具类使读和写 Document 流更容易。要查看的大多数代码在 com.claudeduguay.mbeans.spring 包中。

已经做了足够的预备工作,让我们开始扩展 Spring JMX!



回页首


改进 ModelMBean 中的通知

我要做的第一件事是扩展 Spring JMX ModelMBean 实现,这样就可以发送通知而不用在管理的资源中直接实现这个行为。为此,还需要扩展 Spring 导出器以创建改进的 ModelMBean 实例。最后,还需要用一个新的装配器从映射文件中提取 MBeanInfo 元数据。

ModelMBeanExtension 类

扩展 RequiredModelMBean 类的一个目的是在管理代理中透明地启用通知。这个应用程序需要三种通知:设置属性值、方法调用之前以及方法调用之后。因为消息是我自己配置的,在每一种情况下,它都可以按照我需要的那样提供信息。要实现这一点,我对类型通知使用了一个命名规范,其中对于样式 <matchingType>.<methodOrAttributeName> 检查点分隔的类型名。匹配的类型必须为 setbefore 或者 after 之一。如果类型是 set,那么就认为是一个属性名,否则,就认为是一个方法名。

扩展的 ModelMBean 代码使用额外的类帮助进行耦合。第一个是 NotificationInfoMap,它是一个用通知元数据构建的、简单的 Map,并与前缀(set|before|after)点名(method|attribute)样式相关联,这样就可以更有效地得到匹配的通知元数据。第二个是工具方法的一个静态集合。清单 1 显示了为通知而扩展的 RequiredModelMBean


清单 1. ModelMBeanExtension

package com.claudeduguay.mbeans.spring;

import java.lang.reflect.*;

import javax.management.*;
import javax.management.modelmbean.*;

public class ModelMBeanExtension extends RequiredModelMBean
{
protected NotificationInfoMap notificationInfoMap;
protected ModelMBeanInfo modelMBeanInfo;
protected Object managedBean;

public ModelMBeanExtension() throws MBeanException {}

public ModelMBeanExtension(ModelMBeanInfo modelMBeanInfo)
throws MBeanException
{
super(modelMBeanInfo);
this.modelMBeanInfo = modelMBeanInfo;
notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
}

public void setModelMBeanInfo(ModelMBeanInfo modelMBeanInfo)
throws MBeanException
{
this.modelMBeanInfo = modelMBeanInfo;
notificationInfoMap = new NotificationInfoMap(modelMBeanInfo);
super.setModelMBeanInfo(modelMBeanInfo);
}

public MBeanNotificationInfo[] getNotificationInfo()
{
return modelMBeanInfo.getNotifications();
}

public void setManagedResource(Object managedBean, String type)
throws MBeanException, RuntimeOperationsException,
InstanceNotFoundException, InvalidTargetObjectTypeException
{
super.setManagedResource(managedBean, type);
this.managedBean = managedBean;
}

protected void maybeSendMethodNotification(
String type, String name) throws MBeanException
{
MBeanNotificationInfo info = notificationInfoMap.
findNotificationInfo(type, name);
if (info != null)
{
long timeStamp = System.currentTimeMillis();
String notificationType = ModelMBeanUtil.
matchType(info, "." + type + "." + name);
sendNotification(new Notification(
notificationType, this, timeStamp,
info.getDescription()));
}
}

protected void maybeSendAttributeNotification(
Attribute attribute)
throws MBeanException, AttributeNotFoundException,
InvalidAttributeValueException, ReflectionException
{
String name = attribute.getName();
MBeanNotificationInfo info = notificationInfoMap.
findNotificationInfo("set", attribute.getName());
if (info != null)
{
Object oldValue = getAttribute(name);
Object newValue = attribute.getValue();
long timeStamp = System.currentTimeMillis();
String notificationType = ModelMBeanUtil.
matchType(info, ".set." + name);
sendNotification(new AttributeChangeNotification(
this, timeStamp, timeStamp,
info.getDescription(), info.getName(),
notificationType, oldValue, newValue));
}
}

public Object invoke(
String name, Object[] args, String[] signature)
throws MBeanException, ReflectionException
{
maybeSendMethodNotification("before", name);
Object returnValue = super.invoke(name, args, signature);
maybeSendMethodNotification("after", name);
return returnValue;
}

public Object getAttribute(String name) throws MBeanException,
AttributeNotFoundException, ReflectionException
{
try
{
Method method = ModelMBeanUtil.findGetMethod(
modelMBeanInfo, managedBean, name);
return method.invoke(managedBean, new Object[] {});
}
catch (IllegalAccessException e)
{
throw new MBeanException(e);
}
catch (InvocationTargetException e)
{
throw new MBeanException(e);
}
}

public void setAttribute(Attribute attribute)
throws MBeanException, AttributeNotFoundException,
InvalidAttributeValueException, ReflectionException
{
try
{
Method method = ModelMBeanUtil.findSetMethod(
modelMBeanInfo, managedBean, attribute.getName());
method.invoke(managedBean, attribute.getValue());
maybeSendAttributeNotification(attribute);
}
catch (InvocationTargetException e)
{
throw new MBeanException(e);
}
catch (IllegalAccessException e)
{
throw new MBeanException(e);
}
}
}

不需要代理代理!

因为 ModelMBean 已经是一种代理,所以不需要使用 Spring 的代理机制和 AOP 截获器来截获感兴趣的方法。ModelMBean 接口需要 setAttributeinvoke 方法的实现以管理对底层受管资源的调用。可以继承 RequiredModelMBean 类,保证它出现在所有 JMX 实现中,并增加我所需要的功能。

我的 ModelMBeanExtension 实现了同样的构造函数,但是在一个实例变量中存储了 ModelMBeanInfo 的一个副本。因为这个值可以通过构造函数或者调用 setModelMBeanInfo 方法设置,所以我覆盖了这个方法以存储这个值,调用超类以完成默认的行为。在默认情况下,RequiredModelMBean 类增加两个一般性通知描述符,因此我覆盖了 getNotificationInfo() 方法,只返回我描述的通知。仍然会发送一般性通知,但是要求特定通知的客户不会看到它们。

为了发送通知,我覆盖了 setAttribute()invoke() 方法并检查调用是否匹配我的通知信息描述符。每次都遍历列表应该不会带来很大的开销,因为大多数类只会发送有限的一组通知,但是我需要测试每一个通知可能的许多通知类型字符串,而重复这一过程看来是个浪费。为了保证不会遇到性能问题,我实例化了一个通知信息映射,这是一个名称/信息映射,可以用来进行快速查询。关键是一个具有类型前缀(setbefore 或者 after)和所涉及的属性和方法的简单字符串。可以使用 findNotificationInfo() 方法在 setAttribute() 调用或者方法调用时查找通知信息实例。

完成了基础架构后,就可以截获对 setAttribute()invoke() 方法的调用了。属性改变需要发送一个 AttributeChangeNotification 实例,它要求有旧的属性值和新的值,以及从通知信息描述符中可以得到的细节。在发送通知时,如果消息顺序是混乱的,则要发送序列号,让客户机应用程序可以对消息排序。为了简化,我使用了当前时间戳而不是管理一个计数器。创建通知对象时,sendNotification() 方法保证它会发布。对于 invoke() 方法使用同样的思路,尽管在这里我使用了更简单的 Notification 对象。可以调用超类中的 invoke() 方法同时检查这两者(beforeafter),并根据查找结果发送 beforeafter 通知。



回页首


扩展 Spring JMX 导出器

为了使用扩展的 ModelMBean,需要覆盖 Spring MBeanExporter 中的 createModelMBean() 方法。因为可以注入装配器属性,所以必须知道它可能不是我所期待的这一事实。可以在构造函数中设置所需要的装配器,但是当装配器改变时需要返回一个普通 ModelMBean。所要做的就是缓存一个 MBeanInfoAssembler 的本地引用,并在创建新的 ModelMBean 时检查它是什么类型的。清单 2 显示了所有这些改变:


清单 2. MBeanDescriptorEnabledExporter

package com.claudeduguay.mbeans.spring;

import javax.management.*;
import javax.management.modelmbean.*;

import org.springframework.jmx.export.*;
import org.springframework.jmx.export.assembler.*;

public class MBeanDescriptorEnabledExporter extends MBeanExporter
{
protected MBeanInfoAssembler mBeanInfoAssembler;

public MBeanDescriptorEnabledExporter()
{
setAssembler(new MBeanDescriptorBasedAssembler());
}

public ModelMBean createModelMBean() throws MBeanException
{
if (mBeanInfoAssembler instanceof MBeanDescriptorBasedAssembler)
{
return new ModelMBeanExtension();
}
return super.createModelMBean();
}

public void setAssembler(MBeanInfoAssembler mBeanInfoAssembler)
{
this.mBeanInfoAssembler = mBeanInfoAssembler;
super.setAssembler(mBeanInfoAssembler);
}
}

在使用这个扩展的类时,可以用标准 Spring 语言改变装配器,并在需要时回到默认的行为。在大多数情况下,如果最终绕过扩展,那么就不值得使用这个版本。不过,如果想要以新的定制装配器使用扩展的 ModelMBean,那么现在可以这样做。



回页首


构建一个定制的装配器

这个定制装配器的主要任务是查找与管理的类有关的元数据映射文件。找到这个文件后,就装载它并生成必要的 ModelMBeanInfo 实例。为此,我只是实现了 Spring MBeanInfoAssembler 实例建立这个文件的相关类路径,用静态 MBeanDescriptorUtil.read() 方法装载它并返回结果,如清单 3 所示:


清单 3. MBeanDescriptorBasedAssembler

package com.claudeduguay.mbeans.spring;

import java.io.*;

import javax.management.modelmbean.*;

import org.springframework.core.io.*;
import org.springframework.jmx.export.assembler.*;

import com.claudeduguay.mbeans.model.*;

public class MBeanDescriptorBasedAssembler
implements MBeanInfoAssembler
{
public ModelMBeanInfo getMBeanInfo(
Object managedBean, String beanKey)
{
String name = managedBean.getClass().getName();
String path = name.replace('.', '/') + ".mbean.xml";

ClassPathResource resource = new ClassPathResource(path);
InputStream input = null;
try
{
input = resource.getInputStream();
MBeanDescriptor descriptor = MBeanDescriptorUtil.read(input);
return descriptor.createMBeanInfo();
}
catch (Exception e)
{
throw new IllegalStateException(
"Unable to load resource: " + path);
}
finally
{
if (input != null)
{
try { input.close(); } catch (Exception x) {}
}
}
}
}

这个 MBeanDescriptorBasedAssembler 忽略 bean 键参数并直接用受管 bean 引用创建所需的 ModelMBeanInfo 实例。



回页首


示例

在本文其余部分,我将着重展示这个 Spring JMX 扩展的使用。为此,使用一个假想的服务,它开放两个方法和一个属性,因此表现了典型的用例。

ExampleService 是一个 Java 对象,它在被调用时只是向控制台进行输出,如清单 4 所示:


清单 4. ExampleService

package com.claudeduguay.jmx.demo.server;

public class ExampleService
{
protected String propertyValue = "default value";

public ExampleService() {}

public String getPropertyValue()
{
System.out.println("ExampleService: Get Property Value");
return propertyValue;
}

public void setPropertyValue(String propertyValue)
{
System.out.println("ExampleService: Set Property Value");
this.propertyValue = propertyValue;
}

public void startService()
{
System.out.println("ExampleService: Start Service Called");
}

public void stopService()
{
System.out.println("ExampleService: Stop Service Called");
}
}

对管理员友好的消息

这个扩展的描述符可以几乎直接关联属性和操作。描述符方法优于内省式方法的主要一点是可以提供更特定的消息。通知描述符的配置选项有赖于类型(XML)属性的命名规范。实际的名字是任意的,但是代码会被类型中的 set.namebefore.nameafter.name 样式触发。在这种情况下,我将 set 通知与 propertyValue (JMX)属性关联,将 beforeafter 通知与 startService()stopService() 方法关联。同样,这些扩展使我可以很好利用描述性的消息。

在清单 5 中,可以看到定义了一个属性和两个方法。通知描述符定义了方法的之前和之后事件以及一个属性设置通知:


清单 5. ExampleService.mbean.xml

<?xml version="1.0"?>
<mbean name="ExampleService" description="Example Service"
type="com.claudeduguay.jmx.demo.server.ExampleService">

<attribute name="propertyValue"
description="Property Value Access" type="java.lang.String"
readable="true" writable="true" />

<operation name="stopService"
description="Stop Example Service" />
<operation name="startService"
description="Start Example Service" />

<notification name="PropertyValueSet"
types="example.service.set.propertyValue"
description="PropertyValue was set" />
<notification name="BeforeStartService"
types="example.service.before.startService"
description="Example Service is Starting" />
<notification name="AfterStartService"
types="example.service.after.startService"
description="Example Service is Started" />
<notification name="BeforeStopService"
types="example.service.before.stopService"
description="Example Service is Stopping" />
<notification name="AfterStopService"
types="example.service.after.stopService"
description="Example Service is Stopped" />

</mbean>



回页首


配置服务器

要在客户机/服务器环境中运行这个例子,需要配置和启动一个 MBeanServer 实例。为此,我使用 Java 5.0 MBeanServer 实例,它保证我可以使用 JVM 中提供的管理扩展,同时管理自己的代码。如果愿意,还可以运行 MBeanServer 的多个实例,您愿意的话也可以自己试一试作为练习。

就像 Java 5.0 一样,Spring 框架使您可以配置自己的 MBeanServer 实例。我选择使用 Java 5.0,因为它支持 JSR-160 连接器,我的客户机代码会需要它。


清单 6. SpringJmxServer

package com.claudeduguay.jmx.demo.server;

import org.springframework.context.*;
import org.springframework.context.support.*;

import mx4j.tools.adaptor.http.*;

/*
* To use the SpringJmxServer, use the following command line
* arguments to activate the Java 1.5 JMX Server.
*
* -Dcom.sun.management.jmxremote.port=8999
* -Dcom.sun.management.jmxremote.ssl=false
* -Dcom.sun.management.jmxremote.authenticate=false
*/

public class SpringJmxServer
{
public static void main(String[] args)
throws Exception
{
String SPRING_FILE =
"com/claudeduguay/jmx/demo/server/SpringJmxServer.xml";
ApplicationContext context =
new ClassPathXmlApplicationContext(SPRING_FILE);
HttpAdaptor httpAdaptor =
(HttpAdaptor)context.getBean("HttpAdaptor");
httpAdaptor.start();
}
}

由于有了 MBeanDescriptorEnabledExporter,服务器的 Spring 配置文件非常简单。除了声明 ExampleService,我增加了开放一个 HTTP 适配器和连接 XSLTProcessorHttpAdaptor 所需要的 MX4J 项。注意这是 Spring 的 IOC 实现非常有用的一个领域。清单 7 显示了我的 SpringJmxServer 实例的 Spring 配置文件:


清单 7. SpringJmxServer.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="exporter" class=
"com.claudeduguay.mbeans.spring.MBeanDescriptorEnabledExporter">
<property name="beans">
<map>
<entry key="Services:name=ExampleService"
value-ref="ExampleService" />
<entry key="MX4J:name=HttpAdaptor"
value-ref="HttpAdaptor" />
<entry key="MX4J:name=XSLTProcessor"
value-ref="XSLTProcessor" />
</map>
</property>
</bean>

<bean id="XSLTProcessor"
class="mx4j.tools.adaptor.http.XSLTProcessor" />
<bean id="HttpAdaptor"
class="mx4j.tools.adaptor.http.HttpAdaptor">
<property name="processor" ref="XSLTProcessor"/>
<property name="port" value="8080"/>
</bean>

<bean id="ExampleService"
class="com.claudeduguay.jmx.demo.server.ExampleService" />

</beans>

如果愿意(假定您遵循了我的设置),那么现在就可以运行这个服务器了。它会注册 ExampleService 并运行 HTTP 适配器。不要忘记使用注释中提到的命令行参数启动 Java 5.0 MBeanServer,否则会得到默认实例,客户机示例就不能工作了。



回页首


运行客户机代码

启动服务器后,可以运行如清单 8 所示的客户机代码看看会发生什么。这段代码实现了 JMX NotificationListener 接口,这样就可以交互式地看到所发生的事情。连接后,可以注册监听器,然后触发几个调用、启动和停止服务、设置和取得属性。在每一种情况下,都应当在控制台上看到一个确认操作的通知消息。


清单 8. SpringJmxClient

package com.claudeduguay.jmx.demo.client;

import java.util.*;

import javax.management.*;
import javax.management.remote.*;

public class SpringJmxClient implements NotificationListener
{
public void handleNotification(
Notification notification, Object handback)
{
System.out.println(
"Notification: " + notification.getMessage());
}

public static void main(String[] args)
throws Exception
{
SpringJmxClient listener = new SpringJmxClient();

String address =
"service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(address);
Map<String,Object> environment = null;
JMXConnector connector =
JMXConnectorFactory.connect(serviceURL, environment);
MBeanServerConnection mBeanConnection =
connector.getMBeanServerConnection();

ObjectName exampleServiceName =
ObjectName.getInstance("Services:name=ExampleService");
mBeanConnection.addNotificationListener(
exampleServiceName, listener, null, null);

mBeanConnection.invoke(
exampleServiceName, "startService", null, null);
mBeanConnection.setAttribute(exampleServiceName,
new Attribute("propertyValue", "new value"));
System.out.println(mBeanConnection.getAttribute(
exampleServiceName, "propertyValue"));
mBeanConnection.invoke(
exampleServiceName, "stopService", null, null);
}
}

由于 HTTP 适配器也是可用的,可以试着使用 MX4J (通过一个到端口 8080 的浏览器连接)管理同样的方法和属性。如果同时让客户机代码运行,那么也会看到这些操作的通知。



回页首


结束语

在 本文中,我展示了如何扩展 Spring 的 JMX 支持以满足应用程序的特定需求。在这里,我使用了 Spring 的基于容器的体系结构和 AOP 框架来为 JMX 方法和属性增加通知事件。当然,我只触及到了 Spring JMX 能力的皮毛。还可以有许多其他扩展,Spring 和 JMX 都是很大的主题,每一个都值得进一步研究。我建议您研究本文的源代码并参阅后面 参考资料 一节以了解更多关于 Spring JMX 和相关技术的内容。




回页首


下载

描述名字大小 下载方法
Source codej-Extending-Spring-JMX.jar22 KB  FTP
Source codej-Extending-Spring-JMX-src.zip3 MB  FTP
关于下载方法的信息获取 Adobe® Reader®


回页首


参考资料

学习

获得产品和技术

讨论
  • 加入本文的论坛 。(您也可以通过点击文章顶部或者底部的论坛链接参加讨论。)

  • developerWorks blogs:参加 developerWorks 社区。


回页首


关于作者

Claude 是 Capital Stream Inc 的高级 J2EE 架构师。他有超过 25 年的软件开发经验,并且在 Java 平台的第一个 beta 版本发布时就开始使用它了。Claude 已发表了超过 75 篇文章和 超过 150 篇书评,主要关注 Java 语言和 XML。

posted @ 2005-12-09 23:23 Dion 阅读(917) | 评论 (0)编辑 收藏

将 Java Swing 应用程序连接到 Geronimo 服务器

创建可以与 Geronimo EJB 应用程序对话的独立客户端

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

讨论

样例代码


对此页的评价

帮助我们改进这些内容


级别: 初级

Neal Sanche , Java 开发人员, Pure Technologies

2005 年 8 月 24 日

在前两篇 developerWorks 文章(参阅 参考资料) 中,作者 Neal Sanche 使用简单的电话簿应用程序来展示如何将 Apache Geronimo 应用服务器连接到数据库,以及如何使用 Geronimo 创建具有 Enterprise JavaBeans (EJB) 后端的基于 Struts 的 Web 应用程序。本文进一步使用电话簿应用程序来展示如何创建独立客户端应用程序以操作电话号码数据库。您还将学习如何配置 Geronimo 以允许来自特定客户端的安全访问。

简介

本文将展示如何开发可以与运行在 Geronimo 应用服务器内部的 EJB 应用程序通信的独立(胖)客户端。基于我的前两篇文章 ——“将数据库连接到 Geronimo 应用服务器的三种方法”(developerWorks,2005 年 6 月)和“利用 Geronimo 深入 EJB Web 应用程序” (developerWorks,2005 年 7 月)—— 本文向您展示一个连接到使用 Geronimo EJB 应用程序构建的小型电话簿数据库的 Swing 客户端。您将阅读简要的设计说明,然后阅读有关运行该应用程序所需的客户端库的信息。接下来我将介绍联系服务器并对服务器上远程无状态会话 bean 执行操作的方法。最后,您将学习如何开发、编译并运行客户端应用程序,以及如何配置服务器以允许来自网络中特定客户端的安全访问。

要最有效地利用本文,您需要熟悉用于构建 Java 桌面应用程序的 Java Swing API 以及 Apache Maven 构建系统(参阅 参考资料 以链接到 Maven Web 站点)。



回页首


设计概述

首先简要介绍一下示例应用程序设计 —— 一个描述电话簿客户端应用程序的统一建模语言 (Unified Modeling Language, UML) 部署图 —— 如 图 1 所示。客户端应用程序通过其 EJB 端口连接到 Geronimo,并与 PhoneBook Session EJB 对话以通过 PhoneBook Entry Container-Managed Persistence (CMP) 操作数据库中的数据。


图 1. 电话簿客户端部署图
电话簿客户端部署图

Geronimo 的默认发行版对 EJB 端口有限制。仅当客户端应用程序运行在同一机器上并且通过环回地址(localhost 或 127.0.0.1) 连接时才能连接到该端口。本文稍后的 配置 Geronimo 的 EJB 端口 一节提供了有关如何让其他机器上的客户端访问服务器的详细信息。



回页首


用于连接到 Geronimo 的客户端库

要让客户端应用程序能够连接到 Geronimo 的 EJB 端口并与 EJB 层通信,客户端类路径中必须要有下列 Java 库:

  • geronimo-spec-j2ee-1.4-rc4.jar
  • geronimo-kernel-1.0-SNAPSHOT.jar
  • geronimo-j2ee-1.0-SNAPSHOT.jar
  • geronimo-security-1.0-SNAPSHOT.jar
  • cglib-nodep-2.1.jar
  • openejb-core-2.0-SNAPSHOT.jar
对构建结构的备注

因为对前面文章中代码的构建结构进行了一些小的修改,所以本文的代码包含了电话簿应用程序和电话簿客户端应用程序。下载 包括了有关如何使用 Maven 构建这两个应用程序的详细说明。

从源代码编译 Geronimo 时,当您使用 Maven 构建脚本来编译电话簿客户端应用程序时,这些库被放置到本地 Maven 资源库中且可供访问。您可以在 project.xml 文件的依赖关系部分中查看所有这些库位于 Maven 资源库的哪个位置。

其中一些库在客户端与服务器的通信中起着非常重要的作用。Geronimo 使用 CGLib 库来执行动态代理生成。这使得服务器动态生成远程调用服务器端组件的代码。如果在调试器中检查客户端上 InitialContext 对象的 lookup() 方法返回的一个对象,可以看到动态生成的对象的类名包括 CGLib。geronimo-spec-j2ee.jar 文件包含所有的 Sun Java 2 Platform, Enterprise Edition (J2EE) 接口和类。没有该文件,客户端将无法理解任何动态代理实例。openejb-core.jar 文件是与服务器的 EJB 端口进行对话所必需的。用于在 Geronimo 服务器中执行远程目录查询的 Java Naming and Directory Interface (JNDI) 类就在该 .jar 文件中。最后的三个 .jar 文件提供了其他支持类,比如与 Geronimo 对话的安全主体。



回页首


执行远程会话本地查询

客 户端通信部分的实现十分简单。将客户端连接到服务器时,Geronimo 与其他任何 J2EE 服务器没有任何不同,遵守健全的通过 JNDI 查询和远程方法调用 (RMI) 进行的通信标准。JNDI 查询是获得对远程对象的引用的标准访问。要通过 JNDI 进行连接,必须使用大量特定于 Geronimo 的属性来创建 InitialContext 实例,该实例用于执行查询。 清单 1 展示了有关如何创建会话的示例。


清单 1. 创建到 Geronimo 托管会话 bean 的远程会话
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public void Connect() {
String hostName = getHostName();
String port = getPort();

Properties props = new Properties();

props.setProperty("java.naming.factory.initial",
"org.openejb.client.RemoteInitialContextFactory");
props.setProperty("java.naming.provider.url", hostName+":"+port);
props.setProperty("java.naming.security.principal", "username");
props.setProperty("java.naming.security.credentials", "passwd");

Context ic;
try {
ic = new InitialContext(props);
PhoneBookSessionHome sessionHome = (PhoneBookSessionHome)
PortableRemoteObject.narrow(
ic.lookup(PhoneBookSessionHome.JNDI_NAME),
PhoneBookSessionHome.class);
phoneBookSession = sessionHome.create();
} catch (Throwable ex) {
ex.printStackTrace();
} finally {
if (ic != null) {
ic.close();
}
}
}

清单 1 所示,创建了 Properties 对象并设置了四个属性。第一个并且是最重要的属性是 java.naming.factory.initial 属性,它必须设置为 org.openejb.client.RemoteInitialContextFactory。其他属性指定提供者 URL 以及安全主体和凭证。提供者 URL 是用冒号隔开的主机名和端口。

前已提及,EJB 端口当前只接受从客户端连接到 127.0.0.1 或 localhost 的连接。默认端口是 4201。尽管如此,主机名和端口都可以进行配置。有关详细信息,请参阅 配置 Geronimo 的 EJB 端口 一节。

创建好属性之后,就可以创建并使用 InitialContext 实例了。通过将属性传递给构造函数完成这一操作。创建好实例之后,可以执行查询。清单 1 包括一行复杂的代码,该行执行查询并对结果执行 PortableRemoteObject.narrow()。对于向用户隐藏有关协议传输 —— RMI 或者可能是 Internet Inter-ORB Protocol (IIOP)—— 的详细信息,这是必需的。完成之后,远程会话就可供使用了。在 清单 1 中,该行只创建了新 PhoneBookSession,并将其存储在一个字段中以供将来使用。

具有对远程会话的引用之后,该引用可用于所有操纵电话簿数据库信息的操作。现在只需要一个应用程序来练习该远程会话。



回页首


客户端的设计和开发

现 在我们来深入研究一个小 Swing 应用程序的设计和开发,这个小应用程序用来浏览、创建、删除和修改电话簿数据库条目。我尽量将 Swing 行话减到最少,以防您比较熟悉的是另一种 GUI 技术,比如 Standard Widget Toolkit (SWT)。事实上,如果需要的话,应用程序架构已经使得将显示从应用程序内部逻辑分离出来并将其连接到另一种 GUI 技术变得非常容易。

应用程序架构如 图 2 所示,这是一个详细显示应用程序静态结构的 UML 类图。


图 2. 客户端应用程序的 UML 类图
客户端应用程序的 UML 类图

图 2 中带绿色阴影的类是主要的应用程序类。Main 类是包含菜单和拆分窗格(其左侧是 PhoneNumberListPanel,右侧是 PhoneBookEditorPanel)的框架。该菜单还允许用户设置连接到哪个服务器的首选项,连接到服务器,并退出程序。Application 类是一个单身类(singleton class),用作应用程序的所有操作的控制器。它是惟一一个执行 EJB 操作的类,并保存对 PhoneBookSession 无状态会话 bean 的引用。

橙色的两个接口定义系统中的主要事件。每当 Application 决定电话号码列表需要更新时,就会激活 DataChangeEventPhoneNumberListModel 注册该事件。因为它是 PhoneNumberListPanel 中的主要数据模型列表视图,所以列表是通过模型更改来更新的。这与 Swing 应用程序的设计方法一致。

PhoneNumberListPanelPhoneBookEditorPanel 类都实现 PhoneBookSelectionListener 接口并注册来自 Application 单身类的事件。当它们收到事件时,它们相应地更新当前的选择。如果是 PhoneBookEditorPanel,当前选择导致 Name 和 Number 字段由来自当前选择的电话簿条目中的数据填充。

如果希望节省编写用户接口代码的时间,通常可以在 Internet 上找到高质量的免费工具。优秀的工具有 JGoodies Forms 1.0.5 和 FormLayoutMaker,FormLayoutMaker 是一个用于可视化创建窗体的小工具(参阅 参考资料 以获得到这些工具的链接)。FormLayoutMaker 工具生成代表 JGoodies 窗体布局约束的 XML 文件。这些工具帮助我快速创建了 Phone Number 编辑面板和 Preferences 面板的窗体。



回页首


构建应用程序

编 译应用程序有两种方法。我使用 Eclipse Visual Editor (VE) 插件 1.2 版本在 Eclipse 中开发了该应用程序。它生成应用程序的大部分代码框架,但它是以一种非入侵的方式完成的(没有代码标记和不可访问的代码块),所以如果没有安装 VE 的话也应该没有问题。可以只加载项目并尝试运行它。

您可能需要设置 MAVEN_REPO 构建变量以指向本地 Maven 资源库。还需要构建与本文一起提供的源代码中包括的 Geronimo 和 PhoneBook 服务器应用程序(参阅 下载)。这是因为,要编译客户端应用程序,包含服务器应用程序中 EJB 接口的 .jar 文件必须发布到本地 Maven 资源库中。PhoneBook 的 Maven 构建脚本通过下列 Maven 构建脚本段完成该操作:


清单 2. Maven 构建脚本段
<goal name="client" prereqs="java:compile">
<ant:jar destfile="target/${pom.artifactId}-client.jar">
<fileset dir="target/classes">
<include name="**/*.class"/>
</fileset>
</ant:jar>
<artifact:install artifact="target/${pom.artifactId}-client.jar"
type="jar" project="${pom}"/>
</goal>

用于构建应用程序的第二种方法就是使用 Maven。在 PhoneBook 目录中解压文件并运行 maven 命令。然后在 PhoneBookClient 目录中进行相同操作。如果一切顺利,就已经在目标子目录中创建了 UberJar —— 一个包含运行客户端所需的所有内容的 JAR 文件。

两种构建方法运行得同样好。使用 Maven 方法的优点是如果您尚未下载依赖关系,则它会自动从 ibiblio Web 站点(参阅 参考资料)上的远程 Maven 资源库中下载这些依赖关系。所以如果 Eclipse 中的依赖关系有问题,就在项目上至少运行一次 Maven 来校正缺少的库。



回页首


运行应用程序

确保 PhoneBook 服务器应用程序部署到 Geronimo 服务器中且正在运行。然后键入下列命令:

java -jar phonebook-client-uber.jar

将会看到应用程序弹出,如 图 3 所示。


图 3. Geronimo 电话簿客户端应用程序
Geronimo 电话簿客户端应用程序

首先,从 File 菜单中选择 Connect。如果是连接到 localhost:4201 端口,则应该获得一个连接;否则,控制台窗口将会显示错误消息。可以通过选择 Edit > Preferences、更改信息并尝试重新连接来更改连接的服务器和端口。一旦连接上之后,可以通过在电话号码编辑器中键入姓名和号码并单击 Save 来创建新记录。该记录将显示在姓名列表中。通过选择条目并单击 Delete 来删除条目。通过选择条目、进行修改并单击 Save 来更改条目。



回页首


配置 Geronimo 的 EJB 端口

当 前,配置 Geronimo 的 EJB 端口的方法需要编辑 XML 文件,然后重新编译 Geronimo。Tom McQueeney 的大型 Geronimo Live blog 上的一篇短文清楚介绍了如何使用 openejb\modules\assembly\src\plan\j2ee-server-plan.xml 文件更改 Geronimo Jetty 监听端口的详细信息(参阅 参考资料 以链接到该 blog)。同一文件还包含 EJB 端口的配置信息(参阅 清单 3)。


清单 3. j2ee-server-plan.xml 文件中的代码段
<gbean name="EJBNetworkService" 
class="org.openejb.server.StandardServiceStackGBean">
<attribute name="name">EJB</attribute>
<attribute name="port">4201</attribute>
<attribute name="address">127.0.0.1</attribute>
<attribute name="allowHosts">127.0.0.1</attribute>
<attribute name="logOnSuccess">HOST,NAME,THREADID,USERID</attribute>
<attribute name="logOnFailure">HOST,NAME</attribute>
<reference name="Executor"><name>DefaultThreadPool</name></reference>
<reference name="Server">
<gbean-name>openejb.server:name=EJBServer,*</gbean-name>
</reference>
</gbean>

您需要编辑 j2ee-server-plan.xml 文件并更改 allowHosts 属性。Geronimo 支持许多不同类型的地址。必须用下列模式之一输入逗号分隔的地址列表:

  • 最后一格为 0 的 IP 地址。例如,192.168.10.0 允许 192.168.10 网络上的任何机器与服务器通信。
  • 任何完全指定的 IP 地址。
  • 分解的 IP 地址。这是一种特殊模式,允许指定地址的网络部分和以大括号扩住的主机地址列表。例如,192.168.10.{5,6,7} 允许以下三个机器访问服务器:192.168.10.5、192.168.10.6 和 192.168.10.7。
  • 网络掩码 IP 地址。这是网络管理员熟悉的一种地址。基于精确的位模式匹配规则(超出本文范围),IP 地址与网络掩码相匹配。例如,192.168.255.255 允许 192.168.* 网络中的所有地址访问服务器。
  • 准确的 IPv6 地址。当将来的 IP 网络到来时,Geronimo 将准备好服务,允许列出特定的 IP 地址。
  • 网络掩码 IPv6 地址。

有 关服务器接受的特定模式的详细信息,请咨询源代码文件 —— ServiceAccessController.java —— 位于来源的 openejb\modules\core\src\java\org\openejb\server 目录中。在此将会找到与支持的每个地址类型相匹配的明确的正则表达式。

对 j2ee-server-plan.xml 文件进行修改之后,重新编译并更新服务器部署,您将具有一个专门满足您需要的服务器。(如果只想查看针对同一机器上的服务器运行的客户端,则无需这样做。默认情况下,Geronimo 被配置来完成这些操作。)



回页首


结束语

本 文提供了一个构建独立(胖)客户端的具体示例,该客户端可以与运行在 Geronimo 应用服务器内部的 EJB 应用程序进行对话。Geronimo 团队已经认真实现了健全的标准,推广用简单的 JNDI 查询方法获得与无状态会话 bean 的远程连接。如果只想让简单的应用程序运行,那么这是一个好消息,因为它只需要您编写少量代码。

按照我所介绍的模式,您能够将许多比较大的 数据库连接到与 Geronimo 服务器位于同一机器上的客户端应用程序。使用本文提供的指示,您还能够配置 Geronimo 服务器以允许从连接到您的网络或 Internet 上的其他机器访问服务器 EJB 端口。不妨尝试一下。




回页首


下载

描述名字大小 下载方法
Source code for the phonebook applicationPhonebookClient.zip227 KB  FTP
关于下载方法的信息获取 Adobe® Reader®


回页首


参考资料



回页首


关于作者

作者照片

Neal Sanche 是一位最近才涉足 Microsoft® .NET 世界的 Java 开发人员,他正在克服困难脱离他原来习惯了的开发环境。他的经验包括开发多个商业 J2EE 应用程序和多个独立 Java 应用程序。在业余时间,他作曲、摄影并撰写技术文章。访问他的 Web 站点 可以看到相应的多个示例。您可以通过 neal@nsdev.org 与 Neal 联系。

posted @ 2005-12-09 23:22 Dion 阅读(1233) | 评论 (0)编辑 收藏

什么是Portlet ?

作者:Sunil Patil

译者:observer





版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:Sunil Patil;observer
原文地址:http://www.onjava.com/pub/a/onjava/2005/10/19/challenging-java-dominance.html
中文地址:http://www.matrix.org.cn/resource/article/44/44029_Portlet.html
关键词: Portlet Java


Portlets
“Portlets 是一种Web组件-就像servlets-是专为将合成页面里的内容聚集在一起而设计的。通常请求一个portal页面会引发多个portlets被调 用。每个portlet都会生成标记段,并与别的portlets生成的标记段组合在一起嵌入到portal页面的标记内。”(摘自Portlet规范, JSR 168)

本文探讨了以下内容:
1.        Portal页面的元素
2.        Portal是什么?
3.        Portlets是什么?
4.        开发“Hello World” Portlet
5.        在Pluto上部署HelloWorld Portlet
6.        如何创建Portal页面
7.        结束语
8.        资源


  Portlet规范将portlet定义为一种“基于Java技术的web组件,由处理请求和生成动态内容的portlet容器管理”。这段话听起来是不是有些费解?本文将说明portlets是什么以及能用它们做什么。


图1显示了在访问一个portal服务器时浏览器中页面的样子。

image
图1 典型的portal服务器的页面(点击查看原图)

   如果仔细查看浏览器里的页面,就会看到页面是由不同的“窗口”组成的。一个窗口用于刷新天气,另一个用于新闻,还有一个用于刷新股价,等等。这里的每一 个窗口都代表了一个portlets。如果看得再仔细些,还会发现每个窗口都有一个标题条和一些按钮,包括最小化和最大化按钮。

  在系 统里,这些窗口是相互独立开发、各不同的应用。新闻portlet的开发者创建应用并打包成war格式的文件,随后portal服务器的管理员在服务器上 部署该war文件并创建页面,接下来每个用户会选择在他的页面里有哪些应用。例如,如果用户对股价不感兴趣而对体育感兴趣,他可以用“体育”窗口替换“股 价”窗口。

  Portlet技术需要学习许多新概念,本文不可能全都涵盖,因此本文分为两部分。在第一部分里我们详细说明portals和portlets,并开发一个简单的“Hello World”portlet;在第二部分我们将探讨一些高级主题。

  我们将用Apache的Pluto服务器(Portlet API 1.0规范的参考实现)来测试我们的示例portlets,我们还会花些时间探讨如何安装和使用Pluto服务器。

Portal页面的元素

图2显示了Portal页面的各种元素。

image
图2 portal页面的元素

  每个portlet页面由一个或多个portlet窗口组成,每个portlet窗口又分为两部分:一个是外观,它决定了portlet窗口的标题条、控制和边界的样式;另一个是portlet段,它由portlet应用填充。

  Portal服务器决定了portal页面的整体观感,像标识、标题条颜色、控制图标等。通过修改几个JSP和css模板文件就可以改变portal的整个观感。我们将在“如何创建portal页面”部分对此做深入讨论。

Portal是什么?

   在了解portlet之前有必要先了解portal。在Portlet规范里是这样讲的:“portal是一种web应用,通常用来提供个性化、单次登 录、聚集各个信息源的内容,并作为信息系统表现层的宿主。聚集是指将来自各个信息源的内容集成到一个web页面里的活动”。

  Portal的功能可以分为三个主要方面:
1.        Portlet 容器:Portlet容器与servlet容器非常类似,所有的portlet都部署在portlet容器里,portlet容器控制portlet的生 命周期并为其提供必要的资源和环境信息。Portlet容器负责初始化和销毁portlets,向portlets传送用户请求并合成响应。
2.        内容聚集:Portlet规范中规定portal的主要工作之一是聚集由各种portlet应用生成的内容,我们将在“如何创建Portal页面”部分对此做进一步讨论。
3.        公共服务:portlet服务器的一个强项是它所提供的一套公共服务。这些服务并不是portlet规范所要求的,但portal的商业实现版本提供了丰富的公共服务以有别于它们的竞争者。在大部分实现中都有望找到的几个公共服务有:
         o 单次登录:只需登录portal服务器一次就可以访问所有其它的应用,这意味着你无需再分别登录每一个应用。例如一旦我登录了我的intranet网站,我就能访问mail应用、IM消息应用和其它的intranet应用,不必再分别登录这些应用。
   Portal服务器会为你分配一个通行证库。你只需要在mail应用里设定一次用户名和密码,这些信息将以加密的方式存储在通行证库中。在你已登录到 intranet网站并要访问mail应用的时候,portal服务器会从通行证库中读取你的通行证替你登录到mail服务器上。你对其它应用的访问也将 照此处理。
          o个性化:个性化服务的基本实现使用户能从两方面个性化她的页面:第一,用户可以根据她的自身喜好决定标题条的颜 色和控制图标。第二,用户可以决定在她的页面上有哪些portlets。例如,如果我是个体育迷,我可能会用一个能提供我钟爱球队最新信息的 portlet来取代股票和新闻portlets。
        一些在个性化服务方面领先的商业实现版本允许你建立为用户显示什么样的应用所 依据的标准(如收入和兴趣)。在这种情况下,可以设定一些像“对任何收入为X的用户显示馈赠商品的portlet”和“对任何收入为X的用户显示打折商品 的portlet”这样的商业规则。

        此外还有一些公共服务,比如机器翻译,是由portal服务器将portlet生成的内容翻译为用户要求的语言。大部分的商业portal服务器都支持手持设备访问并具有针对不同的浏览终端生成不同内容的能力。

Portlets是什么?

  与servlets类似,portlets是部署在容器内用来生成动态内容的web组件。从技术角度讲portlet是一个实现了javax.portlet.Portlet接口的类,它被打包成war文件格式部署到portlet容器里。

  Portlets在以下方面与servlets相似:
1.        portlets由特定的容器管理。
2.        portlets生成动态内容。
3.        portlet的生命周期由容器管理。
4.        portlets通过请求/响应模式与web客户端交互。

  Portlets在以下方面与servlets相异:
1.        portlets只能生成标记段,而不是整个文档。
2.        portlets没有可供直接访问的URL地址。不过你还是能够让别人通过URL访问到portlet,你可以把包含该portlet的页面的URL发给他。
3.        portlets 不能随意地生成内容,这是因为portlet生成的内容最终要成为portal页面的一部分。如果portal服务器要求的是html/text类型,那 么所有的portlets都应生成html/text类型的内容。再比方说,如果portal服务器要求的是WML类型,那么所有的portlets都应 生成WML类型的内容。

  portlets还提供了一些附加的功能:
1.        设置参数的持久化存储:portlets提供了一个PortletPreferences对象用来保存用户的设置参数。这些参数被存入一个持久化数据库,这样服务器重启后数据依然有效。开发者不必关心这些数据存储的具体实现机制。
2.         请求处理:portlets提供了更为细粒度的请求处理。对于用户在portlet上动作时向该portlet发出的请求(一种称为活跃期的状态),或者 因用户在其它portlet上动作而引发的刷新页面请求,Portal服务器提供了两种不同的回调方法来处理。
3.        Portlet 模式:portlets用模式的概念来表示用户在做什么。在使用mail应用的时候,你可能会用它来读信、写信或检查信件――这些都是mail应用的预定 功能,Portlets通常以VIEW模式提供这些功能。但还有一些活动,像指定刷新时间或(重新)设置用户名和密码,这些活动允许用户定制应用的行为, 因此它们用的是EDIT模式。Mail应用的帮助功能用的是HELP模式。

  如果仔细想想其实这里面并没有什么新东西,它们反而大部分都是普通的业务需求。Portlet规范的作用在于它提供了一个抽象层,这才是它对所有与之相关的人-最终用户、开发者和管理员-的价值所在。

  作为一个开发者,我会将所有与VIEW模式有关的业务逻辑放入doView()方法,将与应用配置有关的业务逻辑放入doEdit()方法,将与帮助有关的逻辑放入doHelp()方法

  这就简化了管理员对portlet应用的访问控制管理,因为他只需改变portlet的访问权限就能决定用户能做什么。例如,如果mail应用的一个用户能够在EDIT模式下设定用户名和密码,那么就可以断定他具有EDIT模式访问权限。

   不妨考虑这样一种情形:我是一个intranet网站的管理员,我的公司买了一个能显示新闻信息的第三方portlet应用,该应用允许用户指定跟踪新 闻更新的URL地址,我想借助它为用户显示公司的内部新闻。另一个需求是我不想让用户通过该应用来跟踪任何其它的新闻信息来源。作为管理员,我可以为所有 的用户指定一个用于内部新闻更新的URL地址,同时通过改变portlet应用的部署描述符来取消其它人修改该地址的权限。

  由于所有的portlet应用都具有相似的UI界面,因此采用portlets可使网站对最终用户更具吸引力。如果她想阅读任何一个应用的帮助信息,她可以点击帮助按钮;她也知道点击编辑按钮能让她进入应用的配置屏。标准化的用户界面使你的portlet应用更引人。

4.         窗口状态:窗口状态决定了portal页面上留给portlet生成内容的空间。如果点击最大化按钮,portlet将占据整个屏幕,成为用户唯一可用的 portlet;而在最小化状态,portlet只显示为标题条。作为开发者应当根据可用空间的大小来定做内容。

5.        用 户信息:通常portlets向发出请求的用户提供个性化的内容,为了能更加行之有效,portlets需要访问用户的属性信息,如姓名、email、电 话等。Portlet API为此提供了用户属性的概念,开发者能够用标准的方式访问这些属性,并由管理员负责在这些属性与真实的用户信息数据库(通常是LDAP服务器)之间建 立映射关系。

  我们将在本文的第二部分深入讨论这些特点-请求处理、用户信息和portlet模式。

开发"Hello World" Portlet

  现在我们就来开发一个简单的HelloWorld portlet。
1.        创建一个名为HelloWorld的web项目,它与通常的servlet项目类似,有一个/WEB-INF/web.xml文件作为项目的部署描述符。

2.        在build path里加入portlet-api-1.0.jar文件,该jar文件是Pluto发行包的一部分。

3.        在Source文件夹中按如下内容创建HelloWorld.java文件:
public class HelloWorld extends GenericPortlet{
  protected void doView(RenderRequest request,
  RenderResponse response) throws
  PortletException, IOException {
        response.setContentType("text/html");
        response.getWriter().println("Hello Portlet");
        }
}


   每个portlet都要实现Portlet接口,该接口为portlet定义了生命周期方法。由于不想覆盖所有这些方法,我们只对 GenericPortlet类进行扩展,它是一个实现了Portlet接口的适配器类。GenericPortlet类提供了所有生命周期方法的默认实 现,所以我们只需实现我们所需要的方法。

  我们在 HelloWorld portlet里要做的只是显示“Hello Portlet”,所以我们将覆盖GenericPortlet类的doView()方法,该方法以PortletRequest 和 PortletResponse作为参数。在doView()方法中首先调用response.setContentType()以通知portlet容 器该portlet将要生成何种类型的内容-如果不这样做就会导致IllegalStateException异常。一旦设置了内容的类型,就可以从 response对象中获得PrintWriter并开始写入。

4.        每个portlet应用在/WEB-INF文件夹中都有一个portlet.xml文件,它是portlet应用的部署描述符。按以下内容创建portlet.xml文件:
<portlet>
  <description>HelloWorldDescription
        </description>
    <portlet-name>HelloWorld
        </portlet-name>
    <display-name>Hello World
        </display-name>

    <portlet-class>com.test.HelloWorld
        </portlet-class>
    <expiration-cache>-1
        </expiration-cache>
        <supports>
          <mime-type>text/html</mime-type>
      <portlet-mode>VIEW
          </portlet-mode>
        </supports>
    <supported-locale>en
        </supported-locale>

        <portlet-info>
          <title>Hello World</title>
          <short-title>Hello World
          </short-title>
          <keywords>Hello,pluto</keywords>
      </portlet-info>
</portlet>


   <portlet-name>元素声明了portlet的名字,<portlet-class>元素指定了portlet的全 限定类名,<expiration-cache>元素以秒为单位指定了内容超期的时间。这里面有一点需要注意:你在portlet上的某些动 作可能会导致内容刷新,这与缓存时间无关。
  <supports>元素指定对于给定的<mime-type>有哪些模 式可供支持。在示例中我们假定HelloWorld只能生成text/html类型的内容,且只有view模式可支持该内容类型。如果要增加对其它内容类 型的支持,需要添加新的<support>元素并指定支持该MIME类型的模式有哪些。通常portlet对于text/html类型有 VIEW、EDIT和HELP模式可供支持,而对于WML MIME类型则只有VIEW模式。
  还可以用<supported- locale>元素来指定portlet支持哪些本地化。<title>元素用来指定portlet的标题。如果要对标题做国际化处 理,可以用元素<resource-bundle>指定资源(比例properties文件)的文件名。在这种情况下,容器将根据用户所在的 地区从适当的properties文件中选择标题。

5.        每个portlet应用都是一个web应用,因此除了portlet.xml文件之外还需要有web.xml文件。
<web-app>
  <display-name>Hello World Portlet
  </display-name>
  <welcome-file-list
    <welcome-file>index.jsp
        </welcome-file>
  </welcome-file-list>
</web-app>


6.        接下来将这些文件进行编译并打包为war文件。你可以自己完成这些工作,或者下载带有build.xml 的示例代码(参见“资源”部分)来创建war文件。
在Pluto上部署HelloWorld Portlet

   Pluto尚处于开发阶段的早期,因此还没有一套易于使用的管理工具。为了能使用Pluto服务器,需要将编译和源代码两个版本都下载。需要注意的是以 下说明是针对Windows平台的,Unix用户通过修改斜杠符号和执行sh shell脚本(不是bat批命令文件)会得到类似的结果。

1.        创建一个文件夹,比如C:\PlutoInstallation。
2.        从Pluto的网站下载pluto-1.0.1-rc1.zip和pluto-src-1.0.1-rc1.zip。
3.        将pluto-1.0.1-rc1.zip解压到C:\PlutoInstallation.文件夹,它应被解压到C:\PlutoInstallation\pluto-1.0.1-rc1文件夹下。
4.        执行C:\PlutoInstallation\pluto-1.0.1-rc1\bin\startup.bat启动Pluto,现在可以通过地址http://localhost:8080/pluto/portal访问Pluto服务器。
5.        将pluto-src-1.0.1-rc1.zip解压到C:\PlutoInstallation\PlutoSrc文件夹。
6.         进入C:\PlutoInstallation\PlutoSrc文件夹,执行maven distribute:all.,编译并下载运行常规管理任务所必需的相关资源文件。现在可以将HelloWorldPortlet.war作为 portlet进行安装了。
7.        首先将HelloWorldPortlet.war文件拷贝到C:\PlutoInstallation\portlets目录,如果没这个目录就创建它。
8.        将C:\PlutoInstallation\plutosrc\build.properties.sample更名为build.properties。
9.        编辑build.properties,将maven.tomcat.home指向Pluto编译版的安装位置,在本例中应改为maven.tomcat.home=C:/PlutoInstallation/pluto-1.0.1-rc1。
10.         为了安装portlet,进入C:\plutoInstallation\plutosrc\deploy文件夹,执行maven deploy -Ddeploy=c:\PlutoInstallation\portlets\HelloWorldPortlet.war,应能看到“build successful”信息。
11.        在C:\PlutoInstallation\pluto-1.0.1-rc1\webapps文件夹下,应该有一个HelloWorldPortlet文件夹。
12.        现在进入C:\PlutoInstallation\pluto-1.0.1-rc1\webapps\HelloWorld\WEB-INF\ folder文件夹,打开portlet的web.xml文件,你会发现里面自动多了几行,如下所示:
<servlet>
  <servlet-name>HelloWorld</servlet-name>
     <display-name>HelloWorld Wrapper</display-name>
      <description>Automated generated
      Portlet Wrapper</description>
      <servlet-class>org.apache.pluto.core.PortletServlet
      </servlet-class>
      <init-param>
         <param-name>portlet-class</param-name>
         <param-value>com.test.HelloWorld
         </param-value>
      </init-param>
      <init-param>
         <param-name>portlet-guid</param-name>
         <param-value>HelloPluto.HelloWorld
         </param-value>
      </init-param>
</servlet>

13.         接下来我们将该portlet加到页面里。进入C:\PlutoInstallation\pluto-1.0.1-rc1\webapps\pluto \WEB-INF\data文件夹,可以看到有两个XML文件:pageregistry.xml和 portletentityregistry.xml。
14.        portletentityregistry.xml包含了portlet的定义,在该文件中加入以下几行:
 <application id="5">
   <definition-id>HelloWorld</definition-id>
     <portlet id="1">
       <definition-id>HelloWorld.HelloWorld</definition-id>
     </portlet>
</application>

  应用的<definition-id>应为web应用所在文件夹的名字,portlet的<definition-id>应与web.xml中生成的portlet-guid相一致。
15.        pageregistry.xml定义了页面中包含了哪些portlets,对该文件做如下改动:
  <fragment name="p2" type="portlet">
    <property name="portlet" value="5.1"/>
</fragment>

16.        执行shutdown命令和startup命令重启Pluto服务器,返回到地址http://localhost:8080/pluto/portal并点击“Test Link”-此时页面中将出现我们的

HelloWorld portlet。

图3的右侧显示了HelloWorld portlet看上去的样子。

image
图3 portlet的屏幕截图

如何创建Portal页面

图4显示了portal容器如何将分离的portlets组装为页面。

image
图4 创建Portal页面

   大部分的portal服务器基本上都是部署于应用服务器上的web应用,通过servlet来处理访问portal服务器的请求。查看一下Pluto的 安装目录就会发现Pluto不过是一个部署于Tomcat服务器上的一个普通web应用,再看看C:\PlutoInstallation\pluto- 1.0.1-rc1\webapps\pluto\WEB-INF\web.xml会发现所有发往Pluto服务器的请求都被映射到 org.apache.pluto.portalImpl.Servlet上。

  在本文开始部分“Portal页面的元素”中,我们提到portal页面由两部分组成。一部分是由页面中的portlets生成的内容,另一部分是由portal服务器生成的内容。

  在Pluto里,只要用户发出请求,就会由servlet进行控制,根据用户所请求的页面来确定需要显示的portlets的列表。一旦生成了列表,servlet就将控制转给这些portlets线程并收集由它们生成的内容。

   对于由portal服务器生成的内容(像portal网站的观感及每个portlet的外观和控制之类)则取决于C:\ PlutoInstallation\pluto-1.0.1-rc1\webapps\pluto\WEB-INF\aggregation文件夹下的 JSP文件。RootFragment.jsp是主JSP文件,它决定了整体的观感和对齐方式;它还包含了Heads以定义在生成的页面中的< HEAD>标签里的内容。TabNavigation.jsp用来选择在banner中该显示什么(默认情况下在banner显示列表中也包扩了 pluto.png图片)。TabNavigation.jsp用来确定portal网站的导航方案。这意味着只需改动该文件夹下少量的几个JSP文件, 就能改变整个portal网站的观感。

  Pluto根据pageregistry.xml中的设置确定页面中有多少行,并用 RowFragment.jsp去填充。ColumnFragment.jsp用来填充每个栏目。PortletFragmentHeader.jsp用 来填充每个portlet的页头,像标题条及最大化和最小化控制。footer.jsp用来填充JSP的页脚。如果去看一下portal页面的HTML代 码就会发现每个portlet窗口无非都是嵌入<TD>标签的内容块。

结束语

  任何一种新技术要想获得成功都应具备以下条件:首先,它能提升现有技术;其次,它能解决现有技术遇到的普遍问题;再次,它能提供多于一个的抽象层(有人说,每抽象出一层,问题就解决一半)。

   由于portlet与现有的应用服务器架构兼容,这对Portlet API来说是一次发展servlet技术的好机会。你可以从portlet里调用EJB,或者用它启动和参与由应用服务器控制的全局性事务。换句话说,在 以商业逻辑为核心的领域里,portlet完全可以做得和servlet一样好。

  Portlets提供了一个抽象层,现在你不必再担 心客户端使用了什么样的HTTP方法,也不必自己编写程序去捕获像点击按钮这样的客户端事件。最后但绝不是最次要的一点是,portlets以提供像单次 登录、个性化等服务的方式解决了servlets不能解决的大部分问题。

资源
·本文的示例代码
·JSR 168的首页:http://www.jcp.org/en/jsr/detail?id=168
·Pluto的首页:http://portals.apache.org/pluto/
·onjava.com:onjava.com
·Matrix-Java开发者社区:http://www.matrix.org.cn


Sunil Patil从事J2EE技术工作已有5年,他感兴趣的领域包括对象关系映射工具、UI框架以及portals。
posted @ 2005-12-05 20:11 Dion 阅读(10024) | 评论 (5)编辑 收藏

利用XMLBean轻轻松松读写XML

作者:叶枫




版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:叶枫(http://blog.matrix.org.cn/page/叶枫)
原 文:[http://www.matrix.org.cn/resource/article/44/44027_XMLBean.html]http://www.matrix.org.cn/resource/article/44/44027_XMLBean.html[/url]
关键字:XML XMLBean Parser

一、关于XML解析

  XML在Java应用程序里变得越来越重要, 广泛应用于数据存储和
交换. 比如我们常见的配置文件,都是以XML方式存储的. XML还应用
于Java Message Service和Web Services等技术作为数据交换.
因此,正确读写XML文档是XML应用的基础.
  Java提供了SAX和DOM两种方式用于解析XML,但即便如此,要读写一个
稍微复杂的XML,也不是一件容易的事.

二、XMLBean简介

    Hibernate已经成为目前流行的面向Java环境的对象/关系数据库映射工具.
在Hibernate等对象/关系数据库映射工具出现之前,对数据库的操作是
通过JDBC来实现的,对数据库的任何操作,开发人员都要自己写SQL语句
来实现. 对象/关系数据库映射工具出现后,对数据库的操作转成对
JavaBean的操作,极大方便了数据库开发. 所以如果有一个类似的工具能够
实现将对XML的读写转成对JavaBean的操作,将会简化XML的读写,即使对XML
不熟悉的开发人员也能方便地读写XML. 这个工具就是XMLBean.

三、准备XMLBean和XML文档

   XMLBean是Apache的一个开源项目,可以从http://www.apache.org下载,
最新的版本是2.0. 解压后目录如下:
xmlbean2.0.0
     +---bin
     +---docs
     +---lib
     +---samples
     +---schemas


另外还要准备一个XML文档(customers.xml),
在本文的例子里,我们将对这个文档进行读写操作. 文档源码如下:

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
    <customer>
            <id>1</id>
            <gender>female</gender>
            <firstname>Jessica</firstname>
            <lastname>Lim</lastname>
            <phoneNumber>1234567</phoneNumber>
            <address>
                <primaryAddress>
                        <postalCode>350106</postalCode>
                        <addressLine1>#25-1</addressLine1>
                        <addressLine2>SHINSAYAMA 2-CHOME</addressLine2>
                </primaryAddress>
                <billingAddress>
                        <receiver>Ms Danielle</receiver>
                        <postalCode>350107</postalCode>
                        <addressLine1>#167</addressLine1>
                        <addressLine2>NORTH TOWER HARBOUR CITY</addressLine2>
                </billingAddress>
            </address>
    </customer>
    <customer>
            <id>2</id>
            <gender>male</gender>
            <firstname>David</firstname>
            <lastname>Bill</lastname>
            <phoneNumber>808182</phoneNumber>
            <address>
                <primaryAddress>
                        <postalCode>319087</postalCode>
                        <addressLine1>1033 WS St.</addressLine1>
                        <addressLine2>Tima Road</addressLine2>
                </primaryAddress>
                <billingAddress>
                        <receiver>Mr William</receiver>
                        <postalCode>672993</postalCode>
                        <addressLine1>1033 WS St.</addressLine1>
                        <addressLine2>Tima Road</addressLine2>
                </billingAddress>
            </address>
    </customer>
</Customers>


这是一个客户的数据模型,每个客户都有客户编号(ID),姓名,性别(gender),
电话号码(phoneNumber)和地址,其中地址有两个: 首要地址(PrimaryAddress)
和帐单地址(BillingAddress),每个地址有邮编,地址1,和地址2组成.
其中帐单地址还有收件人(receiver).

    此外,还要准备一个配置文件(文件名customer.xsdconfig),这个文件的
作用我后面会讲,它的内容如下:

<xb:config xmlns:xb="http://xml.apache.org/xmlbeans/2004/02/xbean/config">

  <xb:namespace>
    <xb:package>sample.xmlbean</xb:package>
  </xb:namespace>

</xb:config>


四、XMLBean使用步骤

    和其他面向Java环境的对象/关系数据库映射工具的使用步骤一样,
在正式使用XMLBean前,我们要作两个准备.

    1. 生成XML Schema文件

       什么是XML Schema文件? 正常情况下,每个XML文件都有一个Schema文件,
       XML Schema文件是一个XML的约束文件,它定义了XML文件的结构和元素.
       以及对元素和结构的约束. 通俗地讲,如果说XML文件是数据库里的记录,
       那么Schema就是表结构定义.

       为什么需要这个文件? XMLBean需要通过这个文件知道一个XML文件的
       结构以及约束,比如数据类型等. 利用这个Schema文件,XMLBean将会产生
       一系列相关的Java Classes来实现对XML的操作. 而作为开发人员,则是
       利用XMLBean产生的Java Classes来完成对XML的操作而不需要SAX或DOM.

       怎样产生这个Schema文件呢? 如果对于熟悉XML的开发人员,可以自己来
       写这个Schema文件,对于不熟悉XML的开发人员,可以通过一些工具来完成.
       比较有名的如XMLSPY和Stylus Studio都可以通过XML文件来生成Schema
       文件. 加入我们已经生成这个Schema文件(customer.xsd):
      

       <?xml version="1.0" encoding="UTF-8"?>
       <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
                  elementFormDefault="qualified">
         <xs:element name="Customers">
           <xs:complexType>
             <xs:sequence>
               <xs:element maxOccurs="unbounded" name="customer"
                           type="customerType"/>
             </xs:sequence>
           </xs:complexType>
         </xs:element>
       <xs:complexType name="customerType">
             <xs:sequence>
               <xs:element name="id" type="xs:int"/>
               <xs:element name="gender" type="xs:string"/>
               <xs:element name="firstname" type="xs:string"/>
               <xs:element name="lastname" type="xs:string"/>
               <xs:element name="phoneNumber" type="xs:string"/>
               <xs:element name="address" type="addressType"/>
             </xs:sequence>
       </xs:complexType>
         <xs:complexType name="addressType">
             <xs:sequence>
               <xs:element name="primaryAddress" type="primaryAddressType"/>
               <xs:element name="billingAddress" type="billingAddressType"/>
             </xs:sequence>
         </xs:complexType>

         <xs:complexType name="primaryAddressType">
             <xs:sequence>
               <xs:element name="postalCode" type="xs:string"/>
               <xs:element name="addressLine1" type="xs:string"/>
               <xs:element name="addressLine2" type="xs:string"/>
             </xs:sequence>
         </xs:complexType>
         <xs:complexType name="billingAddressType">
             <xs:sequence>
                   <xs:element name="receiver" type="xs:string"/>
               <xs:element name="postalCode" type="xs:string"/>
               <xs:element name="addressLine1" type="xs:string"/>
               <xs:element name="addressLine2" type="xs:string"/>
             </xs:sequence>
         </xs:complexType>
       </xs:schema>
      


    2. 利用scomp来生成Java Classes

       scomp是XMLBean提供的一个编译工具,它在bin的目录下. 通过这个工具,
       我们可以将以上的Schema文件生成Java Classes.
       scomp的语法如下:-

      

       scomp [options] [dirs]* [schemaFile.xsd]* [service.wsdl]* [config.xsdconfig]*
      


       主要参数说明:
       -src [dir]                  -- 生成的Java Classes存放目录
     -srconly                  -- 不编译Java Classes,不产生Jar文件
     -out [jarFileName]  -- 生成的Jar文件,缺省是xmltypes.jar
       -compiler                 -- Java编译器的路径,即Javac的位置
       schemaFile.xsd    -- XML Schema文件位置
       config.xsdconfig   -- xsdconfig文件的位置, 这个文件主要用来制定生成的Java Class
                              的一些文件名规则和Package的名称,在本文,package是sample.xmlbean

       在本文,我是这样运行的:
      

       scomp -src build\src  -out build\customerXmlBean.jar schema\customer.xsd
             -compiler C:\jdk142_04\bin\javac customer.xsdconfig
      


       这个命令行的意思是告诉scomp生成customerXmlBean.jar,放在build目录下,同时
       生成源代码放在build\src下, Schema文件是customer.xsd,xsdconfig文件是customer.xsdconfig.

       其实, 生成的Java源代码没有多大作用,我们要的是jar文件.我们先看一下build\src\sample\xmlbean下生成的Classes.
      

          CustomersDocument.java    -- 整个XML文档的Java Class映射
       CustomerType.java              -- 节点sustomer的映射
       AddressType.java                 -- 节点address的映射
       BillingAddressType.java        -- 节点billingAddress的映射
       PrimaryAddressType.java      -- 节点primaryAddress的映射
    


       好了,到此我们所有的准备工作已经完成了. 下面就开始进入重点:利用刚才生成的jar文件读写XML.

五、利用XMLBean读XML文件

    新建一个Java Project,将XMLBean2.0.0\lib\下的Jar文件和刚才我们生成的customerXmlBean.jar加入
    到Project的ClassPath.

    新建一个Java Class: CustomerXMLBean.  源码如下:
    

    package com.sample.reader;

    import java.io.File;
    
    import sample.xmlbean.*;
    import org.apache.commons.beanutils.BeanUtils;
    import org.apache.xmlbeans.XmlOptions;
    public class CustomerXMLBean {
    private String filename = null;
    
    public CustomerXMLBean(String filename) {
            super();
            this.filename = filename;
    }

    public void customerReader() {
            try {
              File xmlFile = new File(filename);
              CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
              CustomerType[] customers = doc.getCustomers().getCustomerArray();
          
              for (int i = 0; i < customers.length; i++) {
                CustomerType customer = customers[i];
                println("Customer#" + i);
                println("Customer ID:" + customer.getId());
                println("First name:" + customer.getFirstname());
                println("Last name:" + customer.getLastname());
                println("Gender:" + customer.getGender());
                println("PhoneNumber:" + customer.getPhoneNumber());
                // Primary address
                PrimaryAddressType primaryAddress = customer.getAddress().getPrimaryAddress();
                println("PrimaryAddress:");
                println("PostalCode:" + primaryAddress.getPostalCode());
                println("AddressLine1:" + primaryAddress.getAddressLine1());
                println("AddressLine2:" + primaryAddress.getAddressLine2());
                // Billing address
                BillingAddressType billingAddress = customer.getAddress().getBillingAddress();
                println("BillingAddress:");
                println("Receiver:" + billingAddress.getReceiver());
                println("PostalCode:" + billingAddress.getPostalCode());
                println("AddressLine1:" + billingAddress.getAddressLine1());
                println("AddressLine2:" + billingAddress.getAddressLine2());
            
              }
            } catch (Exception ex) {
                    ex.printStackTrace();
            }
    }
    private void println(String str) {
          System.out.println(str);
    }
   public static void main(String[] args) {
      String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers.xml";
                  
     CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                   customerXMLBean.customerReader();
    }

    }
    


    运行它,参看输出结果:
    

       Customer#0
       Customer ID:1
       First name:Jessica
       Last name:Lim
       Gender:female
       PhoneNumber:1234567
       PrimaryAddress:
       PostalCode:350106
       AddressLine1:#25-1
       AddressLine2:SHINSAYAMA 2-CHOME
       BillingAddress:
       Receiver:Ms Danielle
       PostalCode:350107
       AddressLine1:#167
       AddressLine2:NORTH TOWER HARBOUR CITY

       Customer#1
       Customer ID:2
       First name:David
       Last name:Bill
       Gender:male
       PhoneNumber:808182
       PrimaryAddress:
       PostalCode:319087
       AddressLine1:1033 WS St.
       AddressLine2:Tima Road
       BillingAddress:
       Receiver:Mr William
       PostalCode:672993
       AddressLine1:1033 WS St.
       AddressLine2:Tima Road
    

    怎么样,是不是很轻松? XMLBean的威力.

六、利用XMLBean写XML文件

    利用XMLBean创建一个XML文档也是一件轻而易举的事.我们再增加一个Method,
    请看一下的Java Class:
    

    public void createCustomer() {
    try {
        // Create Document
        CustomersDocument doc = CustomersDocument.Factory.newInstance();
        // Add new customer
        CustomerType customer = doc.addNewCustomers().addNewCustomer();
        // set customer info
        customer.setId(3);
        customer.setFirstname("Jessica");
        customer.setLastname("Lim");
        customer.setGender("female");
        customer.setPhoneNumber("1234567");
        // Add new address
        AddressType address = customer.addNewAddress();
        // Add new PrimaryAddress
        PrimaryAddressType primaryAddress = address.addNewPrimaryAddress();
        primaryAddress.setPostalCode("350106");
        primaryAddress.setAddressLine1("#25-1");
        primaryAddress.setAddressLine2("SHINSAYAMA 2-CHOME");

        // Add new BillingAddress
        BillingAddressType billingAddress = address.addNewBillingAddress();
        billingAddress.setReceiver("Ms Danielle");
        billingAddress.setPostalCode("350107");
        billingAddress.setAddressLine1("#167");
        billingAddress.setAddressLine2("NORTH TOWER HARBOUR CITY");

        File xmlFile = new File(filename);
        doc.save(xmlFile);
        } catch (Exception ex) {
                ex.printStackTrace();
        }

  }
    

    修改main method.
    

    public static void main(String[] args) {
    String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
        CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
        customerXMLBean.createCustomer();
    }
    

    运行,打开customers_new.xml:
    

    <?xml version="1.0" encoding="UTF-8"?>
    <Customers>
    <customer>
            <id>3</id>
            <gender>female</gender>
            <firstname>Jessica</firstname>
            <lastname>Lim</lastname>
            <phoneNumber>1234567</phoneNumber>
            <address>
                    <primaryAddress>
                         <postalCode>350106</postalCode>
                         <addressLine1>#25-1</addressLine1>
                                       <addressLine2>SHINSAYAMA 2-CHOME</addressLine2>
                    </primaryAddress>
                    <billingAddress>
                        <receiver>Ms Danielle</receiver>
                        <postalCode>350107</postalCode>
                       <addressLine1>#167</addressLine1>
                       <addressLine2>NORTH TOWER HARBOUR CITY</addressLine2>
                    </billingAddress>
                    </address>
            </customer>
    </Customers>
    



七、利用XMLBean修改XML文件

    我们再增加一个Method:
    

      public void updateCustomer(int id,String lastname) {
         try {
        File xmlFile = new File(filename);
        CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
        CustomerType[] customers = doc.getCustomers().getCustomerArray();
      
        for (int i = 0; i < customers.length; i++) {
           CustomerType customer = customers[i];
          if(customer.getId()==id){
                customer.setLastname(lastname);
                break;
            }
        }
        doc.save(xmlFile);
         } catch (Exception ex) {
          ex.printStackTrace();
         }
           }
    

    main method:
    

    public static void main(String[] args) {
     String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
                    
    CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                    
    customerXMLBean.updateCustomer(3,"last");
    }
    

    运行之后,我们将会看到客户编号为3的客户的lastname已经改为last.

八、利用XMLBean删除一个customer

    再增加一个Method:
    

    public void deleteCustomer(int id) {
     try {
      File xmlFile = new File(filename);
     CustomersDocument doc = CustomersDocument.Factory.parse(xmlFile);
    CustomerType[] customers = doc.getCustomers().getCustomerArray();

   for (int i = 0; i < customers.length; i++) {
        CustomerType customer = customers[i];
        if(customer.getId()==id){
                        customer.setNil() ;
                        break;
               }
   }
   doc.save(xmlFile);
   } catch (Exception ex) {
        ex.printStackTrace();
        }
   }


         main method:
    

    public static void main(String[] args) {
    String filename = "F://JavaTest//Eclipse//XMLBean//xml//customers_new.xml";
                    
    CustomerXMLBean customerXMLBean = new CustomerXMLBean(filename);
                    
    customerXMLBean.deleteCustomer(3);
    }

        
运行,我们将会看到客户编号为3的客户的资料已经被删除.

九、查询XML

    除了本文在以上讲述的,利用XMLBean能轻轻松松完成XML的读写操作外,结合XPath和XQuery,
   XMLBean还能完成象SQL查询数据库一样方便地查询XML数据. 关于XML查询以及如何创建XML数据库, 我将在另一篇文章里讨论.



十、结束语
    XMLBean能帮助我们轻易读写XML,这将有助于我们降低XML的学习和使用,有了这个基础,
    开发人员将为学习更多地XML相关技术和Web Services,JMS等其他J2EE技术打下良好地基础.


关于作者:
叶枫:热爱Java和Oracle. 在软件开发有近10年, 目前在国外一家美国大公司担任SA, 负责技术研究。作者Blog:http://blog.matrix.org.cn/page/叶枫
posted @ 2005-12-05 19:52 Dion 阅读(1100) | 评论 (0)编辑 收藏

Java JDBC里如何取得Oracle存储过程返回的动态结果集
作者:叶枫


版权声明:本文可以自由转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:叶枫(http://blog.matrix.org.cn/page/叶枫)
原 文:[http://www.matrix.org.cn/resource/article/43/43999_JDBC_Oracle.html]http://www.matrix.org.cn/resource/article/43/43999_JDBC_Oracle.html[/url]
关键字:JDBC Oracle Cursor



1. 关于oracle和结果集

其实在大多数情况下,我们并不需要从oracle存储过程里返回一个或多个结果集,
除非迫不得已。

如果大家用过MS SQL Server或Sybase SQL Server,那么从存储过程返回一个
动态的结果集是一件非常容易的事情,只要在存储过程结束时写上

“select column1,column2,.... from table_list where condition“

就可以了。

但在Oracle中不能这样做. 我们必须使用Oracle Cursor.
在Oracle PL/SQL中,Cursor用来返回一行或多行记录,借助Cursor,我们可以从结果集中取得所有记录.

Cursor并不难,但是要从Oracle存储过程中返回结果集, 就需要用到Cursor变量,Cursor变量Oracle PL/SQL
的类型是REF CURSOR, 我们只要定义了REF CURSOR 类型就可以使用Cursor变量. 比如我们可以这样定义:
TYPE ref_cursor IS REF CURSOR;
了解了Cursor以及Cursor变量,下面就介绍如何使用Cursor变量给JDBC返回结果集.

2. 定义表结构

在以下例子里,我们要用到一张表Hotline.

Create table hotline(
country varchar2(50),
pno varchar2(50));


3. 定义存储过程

create or replace package PKG_HOTLINE is

type HotlineCursorType is REF CURSOR;

function getHotline return HotlineCursorType;

end;

create or replace package body PKG_HOTLINE is
function getHotline return HotlineCursorType is
hotlineCursor HotlineCursorType;
begin
open hotlineCursor for select * from hotline;
return hotlineCursor;
end;
end;


在这个存储过程里,我们定义了HotlineCursorType 类型,并且在存储过程中
简单地查找所有的记录并返回HotlineCursorType.

4. 测试存储过程

在Oracle SQL/Plus里登陆到数据库. 按以下输入就看到返回的结果集.

SQL> var rs refcursor;
SQL> exec :rs := PKG_HOTLINE.getHotline;
SQL> print rs;


5. Java调用

简单地写一个Java Class.

....
public void openCursor(){
Connection conn = null;
ResultSet rs = null;
CallableStatement stmt = null;
String sql = “{? = call PKG_HOTLINE.getHotline()}“;

try{
conn = getConnection();
stmt = conn.prepareCall(sql);
stmt.registerOutParameter(1,OracleTypes.CURSOR);
stmt.execute();
rs = ((OracleCallableStatement)stmt).getCursor(1);
while(rs.next()){
String country = rs.getString(1);
String pno = rs.getString(2);
System.out.println(“country:“+country+“|pno:”+pno);
}

}catch(Exception ex){
ex.printStackTrace();
}finally{
closeConnection(conn,rs,stmt);
}

}
.....


好了,大功告成.
posted @ 2005-12-02 21:59 Dion 阅读(3184) | 评论 (0)编辑 收藏

仅列出标题
共5页: 上一页 1 2 3 4 5 下一页