`

利用动态代理的 Java 验证

阅读更多
从业务对象实现中去耦验证过程
 

  文档选项
  打印本页

   将此页作为电子邮件发送

  样例代码




级别: 初级

Eric Olson (eric.olson@lakeviewtech.com), 软件工程师, Lakeview Technologies


2004 年 9 月 14 日

Java 平台的 1.3 版本引入了动态代理工具。动态代理为 Java 开发人员提供了许多有意义的解决方案,包括可以把验证逻辑轻松地从应用程序的核心业务逻辑中去耦的验证方案。在这篇文章中,Java 开发人员 Eric Olson 向您展示动态代理如何让核心应用程序代码独立于验证例程,而只关注业务逻辑。
验证是许多企业应用程序的关键部分。大多数业务方法都包含验证逻辑,以确保在执行业务逻辑之前,前提条件得到满足。对用户界面输入的值进行处理的代码,要执行验证逻辑,在执行可能影响应用程序其他部分或者其他用户的操作之前,确保用户输入的值是正确的。对于利用其他松散耦合的组件以及断言不严格的服务,并与之交互的应用程序来说,验证是一个特别重要的组件。

验证与业务应用程序的安全性和功能一样重要,核心的应用程序逻辑通常和验证程序混杂在一起。验证过程经常散落在方法调用里,这样就造成很难区分验证逻辑和核心业务逻辑。在大多数情况下,业务对象和方法必须了解验证过程的细节,并在它们的实现里直接处理它们--例如,一个业务对象可能直接从业务方法里抛出验证异常(或者直接编码在方法里,或者作为调用验证服务的结果)。但是,在这种情况下,验证异常实际是验证过程的副产品,理想情况下,应该隐藏业务对象的实现。

在这篇文章里,我将向您展示一种更加去耦、更加集中的验证方法,它利用 Java 平台 1.3 版本中引入的动态代理工具来实现。通过在整篇文章里处理同一个示例,我将展示紧密耦合和松散耦合验证方案的弱点,然后向您展示动态代理如何可以帮助您改进紧密耦合和松散耦合。

紧密耦合的验证

在整篇文章里,我要使用一个简单的 User 对象,其中定义了在系统中处理用户的方法。清单 1 列出了 User 接口及其方法。


清单 1. User 接口及其方法
/**
* Gets the username of the User.
*/
String getUsername();
/**
* Sets the username of the User.
* @throws ValidationException indicates that validation of the proposed
* username variable failed.  Contains information about what went wrong.
*/
void setUsername(String username) throws ValidationException;
/**
* Gets the password of the User.
*/
String getPassword();
/**
* Sets the password of the User.
* @throws ValidationException indicates that validation of the proposed
* password variable failed.  Contains information about what went wrong.
*/
void setPassword(String password) throws ValidationException;



在紧密耦合的数据验证方案中,我会把验证代码直接插入接口的方法实现中,如清单 2 所示。注意,在设置实例变量之前,验证逻辑被硬编码到方法中。


清单 2. 紧密耦合的验证方案
public void setPassword(String password) throws ValidationException {
    if ((password == null) || (password.length() < MIN_PASSWORD_LENGTH)) {
        throw new ValidationException("INVALID_PASSWORD",
                                      "The password must be at least " +
                                      MIN_PASSWORD_LENGTH +
                                      " characters long");
    }
    this.password = password;
}



在这个示例里,验证逻辑和使用它的对象紧密耦合在一起。这种方法的弱点应当是相当明显的:

它没有引入可重用的验证代码。虽然示例里包含了在应用程序其他许多地方都可使用的长度和 null 检查,但是它们采用了无法重用的方式进行编码。


验证规则无法用任何方法进行配置。例如,如果要向 setPassword() 方法中加入另一条验证规则,我只能修改方法本身,重新编译,也可能要重新部署。
虽然不理想,但是紧密耦合的验证代码相当普遍,特别是在比较老的应用程序里。幸运的是,紧密耦合不是我们在编写 User 接口的验证逻辑时的唯一选项。






回页首




松散耦合的验证

您可以让接口实现调用一个独立的服务来执行它的验证逻辑,从而避开紧密耦合。通常情况下,这个服务会有一组验证规则,分配给特定对象的特定方法。因为验证规则从接口的业务逻辑去耦,所以可以在许多对象的许多方法上重用它们。您也可以在外部定义验证规则,在这种情况下,修改验证逻辑,就只是修改验证服务配置的问题了。

清单 3 显示了如何用验证服务把验证逻辑从核心业务逻辑实现中去耦。


清单 3. 使用验证服务
public void setPassword(String password) throws ValidationException {
    BusinessObjectValidationService.validate(this, "setPassword",
                                             new Object[] {password});
    this.password = password;
}



在这里,验证逻辑作为调用对象的外部服务运行。具体来说,在 setPassword() 方法上执行的验证,被配置到验证服务上,而不是由方法自己来执行。这种松散耦合,在许多情况下,可以解决前面例子的弱点:

代码生成工具如何呢?

开发人员有时使用代码生成工具把 boilerplate 验证代码插入业务方法。与动态代理方法类似,代码生成工具让您在编写实现时不必考虑验证。这二种方法之间的区别在于,与动态代理不同,生成的代码总是出现在业务对象里,不能切换到其他实现(例如调用处理程序)里。使用动态代理允许您在运行时动修改调用处理程序,不必重新编译代码或者重新部署应用程序。


验证规则有可能重用,因为它们可以只编写一次,在里面定义不同对象所使用的方法。例如,我可以写一个验证规则,用于断言指定参数不为 null,然后在所有需要同样规则的方法中重用它。


验证规则也可能是可配置的。例如,我可以用 XML 文档初始化验证服务,在 XML 文档里描述针对具体方法或对象需要执行的规则。我也可以把 API 公开到这个配置里,这样我就可以在运行时修改验证规则。
虽然我们朝着正确的方向走了一小步,但是这种方法仍有不足。当我开发方法的时候,我不得不确保调用验证服务,确保方法实现声明了 ValidationException 异常。这些都是验证服务的工作,和方法的核心逻辑实际没有任何关系。我实际想要的,是一种编写 User 接口实现的方法,这样它就不需要知道这类事情了。






回页首




动态代理方法

动态代理是这样一种类,它可以实现在运行时指定的一组接口。对代理类的方法调用,被分配给调用处理程序,而调用处理程序是在代理类生成的时候指定的。动态代理类有许多应用程序中使用的接口,其中一个可以用统一的方式有效地处理方法前和方法后的调用操作。因为验证通常是方法前调用操作,所以动态代理为我们提供了针对前面示例所指出的问题的解决方案。动态代理类给了我们一种以统一方式方便地处理任何方法上的验证途径,同时把所有的验证逻辑完全与核心业务逻辑分离开。

因为在许多框架中,都存在针对主要业务对象和服务的接口,所以您对于交换进和交换出这类接口的不同实现应该有所体验。使用动态代理类与其非常类似,区别在于,客户并不直接处理接口的实现,而是与代理类打交道,代理类负责实现接口、执行验证、把方法调用委托给实现类。使用动态代理方法,所有的验证逻辑对于代理的客户,都应当是透明的。因此,实现新的验证方案应当会非常简单:对于使用 User 接口的代码,我一行也不用修改。

关于接口的说明

生成动态代理类时带有一组需要实现的接口。出于本文的需要,我要使用 User 接口,虽然一般来讲,您需要确保已经为那些想要用这种方式进行验证的方法定义了接口。


总体来说,我要建立一个执行验证规则的客户调用处理程序。调用处理程序中会包含一个实际的实现类的实例,把它作为实例变量。它首先验证方法调用的方法参数,然后把方法调用委托给实现类。当应用程序需要业务对象实例时,它实际会接收到一个动态代理类的实例。正如您稍后会看到的,这允许业务对象实现类完全独立于那些只与验证过程有关的代码。






回页首




调用处理程序

调用处理程序类是处理所有数据验证逻辑的地方。调用处理程序类还会把方法调用委托到真正的实现类,以便处理核心业务逻辑。清单 4 显示了一个调用处理程序,它没有绑定到任何具体的业务对象,这样就能把它用于任何需要被验证的业务对象。请注意,在下面的 invoke() 方法中的验证代码,几乎与 清单 3中的代码完全一样。实际上,在这里可以使用与前面完全一样的验证器服务。


清单 4. 调用处理程序
/**
* This is the object to which methods are delegated if they are not
* handled directly by this invocation handler.  Typically, this is the
* real implementation of the business object interface.
*/
private Object delegate = null;
/**
* Create a new invocation handler for the given delegate.
* @param delegate the object to which method calls are delegated if
* they are not handled directly by this invocation handler.
*/
public BusinessObjectInvocationHandler(Object delegate) {
    this.delegate = delegate;
}
/**
* Processes a method call.
* @param proxy the proxy instance upon which the method was called.
* @param method the method that was invoked.
* @param args the arguments to the method call.
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
    // call the validator:
    BusinessObjectValidationService.validate(proxy, method.getName(), args);
    // could perform any other method pre-processing routines here...
    /* validation succeeded, so invoke the method on the delegate.  I
       only catch the InvocationTargetException here so that I can
       unwrap it and throw the contained target exception.  If a checked
       exception is thrown by this method that is not assignable to any of
       the exception types declared in the throws clause of the interface
       method, then an UndeclaredThrowableException containing the
       exception that was thrown by this method will be thrown by the
       method invocation on the proxy instance.
    */
    Object retVal = null;
    try {
        retVal = method.invoke(delegate, args);
    } catch (InvocationTargetException ite) {
        /* the method invocation threw an exception, so "unwrap" it and
           throw it.
        */
        throw ite.getTargetException();
    }
    // could do method post-processing routines here if necessary...
    return retVal;
}



您可以看到,调用处理程序的这种实现,利用了通用的验证器服务,与 清单 3 里相同。作为替代解决方案,我建立一个看起来很像 清单 2 的调用处理程序,而验证代码直接在调用处理程序里运行。在这种情况下,我让调用处理程序自己检查调用它的方法是不是 setPassword() 方法,而长度和 null 检查也直接在处理程序中进行。虽然这种方法可以把接口的验证逻辑从它的核心业务代码去耦,但是它的可重用性和可配置性不是很强。在下一节中,我会继续采用通用验证器的实现,在那里您会真正开始发现可重用和可配置代码的价值。






回页首




业务对象实现

下一步是把业务对象实现指定给调用处理程序的构造函数。出于本示例的需要,我会采用不受验证限制的方式实现 User 接口,因为验证逻辑是在调用处理程序中处理的。实现类中的相关代码如清单 5 所示。


清单 5. 不受验证限制的 User 实现
/**
* The username of this User.
*/
private String username = null;
/**
* The password of this User.
*/
private String password = null;
/**
* Gets the username of the User.
*/
public String getUsername() {
    return username;
}
/**
* Sets the username of the User.
*/
public void setUsername(String username) {
    this.username = username;
}
/**
* Gets the password of the User.
*/
public String getPassword() {
    return password;
}
/**
* Sets the password of the User.
*/
public void setPassword(String password) {
    this.password = password;
}



如果您将 setPassword() 方法体中的代码与前面 清单 2中的方法进行对比,您会看到它没有包含专门用于验证的代码。验证过程的细节现在完全由调用处理程序来处理。






回页首




业务对象工厂

我可以把所有这些捆绑在一起,形成最后的代码片断,这段代码会实际建立动态代理类,为它加上正确的调用处理程序。最简单的方法就是在工厂模式中把代理类的建立封装起来。

许多业务对象框架采用工厂模式来建立业务对象接口的具体实现,例如 User 接口。这样,建立一个新业务对象实例就仅仅是调用工厂方法的问题了:对象建立背后的全部细节,都留给工厂,客户端对于实际上如何构建实现是毫不知情的。清单 6 显示了如何为 User 接口建立动态代理(用 UserImpl 实现类),同时通过调用处理程序传递所有的方法调用。


清单 6.UserFactory
/**
* Creates a new User instance.
*/
public static User create() {
    return(User)Proxy.newProxyInstance(User.class.getClassLoader(),
                                       new Class[] {User.class},
                                       new BusinessObjectInvocationHandler(new UserImpl()));
}



请注意: Proxy.newProxyInstance() 方法有三个参数:动态代理定义的类加载器 classloader; Class 数组,里面包含动态代理要实现的所有接口(虽然我可以指定任何要实现的接口,但是在工厂中只实现了 User 接口);以及处理方法调用的调用处理程序。我还建立了 UserImpl 实现类的新实例,并把实例传递给调用处理程序。调用处理程序会使用 UserImpl 类来委托所有的业务方法调用。






回页首




动态代理的缺点

不幸的是,使用动态代理类确实有一个重要不足:性能。对动态代理类的方法调用,不会像直接调用对象的方法那样好。所以,在应用程序框架中对动态代理的使用,取决于什么对您更重要:更整洁的架构还是更好的性能。在应用程序的许多方面,性能损失可能是值得的,但是在其他方面,性能则有可能是至关重要的。所以,有一种解决方案,就是在有些地方用动态代理,在其他地方则不用。如果您决定走这条路,一定要记住,除了验证之外,调用处理程序还能做其他的事,这允许您在运行时或在代码部署之后改变您的业务对象行为。






回页首




动态代理的其他用途

动态代理类在业务对象框架中有许多用途,不仅仅是用一致的方式对方法进行验证。我前面建立的简单调用处理程序 可以用于任何方法前和方法后的调用处理。例如,我可以很容易对业务方法计时,只要在业务对象的实现方法调用之前和之后插入代码,就可计算方法经历的时间。我还可以插入方法后调用逻辑,把状态的变化通知给有兴趣的侦听者。

用 bean 进行验证

除了本文中讨论的方法之外,您还可以用 JavaBean 架构的受限属性功能进行验证。简单点说,在受限属性发生变化之前,可以通知任意数量的有兴趣的侦听者。如果有任何一个侦听者不同意变化,它就可以抛出 java.beans.PropertyVetoException 异常,这意味着属性的变化是不可接受的。

这个模型实际上和动态代理相处得非常好,因为整个消息传递机制都可以放在调用处理程序中。与使用本文前面描述的方法一样,真实的业务对象实现不需要了解或者关心正在进行什么类型的验证。实际上,这类验证可以迟一些引入,不会改变受限属性所包含的任何方法实现。


在示例中,我只为一个接口建立了动态代理类,这个接口是: User 。我可以很容易地指定动态代理类在运行时要实现的多个接口。用这种方式,静态工厂方法返回的对象可以实现建立代理时所定义的任意数量的接口。调用处理程序类必须知道如何处理所有接口类型的方法调用。

您还应当注意到,在示例里我一直调用实际的实现类来执行业务逻辑。这不是必需的。例如,代理类可以把方法调用委托其他任何对象,甚至是处理程序本身。一个简单的例子就是有这样一个接口,它通过 set/get 方法公开了大量 JavaBean 属性。我还建立了一个专门的调用处理程序,它维持了一个 Map ,在映射里,键是属性名称,值是属性的值。在 get 调用上,我替换 Map 中保存的值,这就消除了为这些简单的 JavaBean 类实现编写代码的需要。






回页首




结束语

使用动态代理类进行验证是从应用程序的核心逻辑去耦验证程序的简单而有效的方法。与紧密耦合方法不同,使用动态代理给您带来了可以重用、可以配置的验证代码。

在这篇文章里,您看到了用调用处理使用动态代理的好处。因为动态代理类上的方法调用都可以通过公共的调用处理程序进行传递,您可以非常容易地修改处理程序执行的逻辑,甚至在已经部署的代码中修改或者在运行时动态修改。还可以重构调用处理程序,让它处理其他横跨不同对象类型的方法调用的操作。

Java 平台的动态代理功能不是从核心代码的业务逻辑中去耦验证程序的唯一选项。在某些情况下,例如在性能是应用程序的主要考虑因素的地方,就可能不是最佳选项。虽然本文侧重在动态代理上,我还是讨论了其他一些选项,包括 JavaBean 的受限属性功能,以及代码生成工具的使用。使用任何一种技术,您都应当仔细评估替代品,只有当动态代理是应用程序的最佳解决方案时才使用它。







回页首




下载

名字 大小 下载方法
j-dynproxies-source.zip  HTTP

关于下载方法的信息 



参考资料

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


请单击本文顶部或底部的 代码图标,可以下载本文中使用的源代码。


John Zukowski 在他的文章 “ Merlin 的魔力: 动态事件监听器代理”( developerWorks,2003 年 10 月)中,向您展示了动态代理的另外一种用法。


Brett McLaughlin 的 “ EJB 最佳实践:数据验证出现在什么地方最合适”( developerWorks,2002 年 12 月)解释了如何让验证代码取得最佳性能。


虽然这篇文章侧重于特定业务的验证,Brett 的专栏 “ EJB 最佳实践:验证助手类”( developerWorks,2003 年 1 月)向您展示了如何在数据格式验证方面避免代码冗余。


要想学习用 JavaBean 的受限属性功能进行验证,请参阅 Victor Okunev 的 “ Validation with pure Java”( JavaWorld,2000 年 12 月)。


凡事有两面性, Brett McLaughlin 的 “ Validation with Java and XML Schema”( JavaWorld,2000 年 12 月)解释了为什么 Java 代码不是数据验证的完整解决方案。


Dennis Sosnoski 的“ Data binding, Part 1: Code generation approaches -- JAXB and more”( developerWorks,2003 年 1 月)是支持 Java 语言代码生成的 XML 数据绑定框架的综述。


请访问 Sun Microsystems 的 Dynamic Proxy API 主页,了解有关动态代理的更多内容。


在 developerWorksJava 技术专区 可以找到有关 Java 编程各个方面的文章。


还可以访问 Java 技术专区教程页面,获得 developerWorks提供的免费 Java 教程的完整列表。


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




关于作者



  Eric Olson 是 Lakeview Technologies 的软件工程师,该公司专门研究高可用性、灾难恢复、群集,以及数据复制基础设施软件和服务。Eric 用 Java 编程语言进行开发已经 6 年多了,他使用过许多不同的业务对象框架,包括 EJB 技术和 IBM WebSphere 业务组件。


分享到:
评论

相关推荐

    JAVA_API1.6文档(中文)

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供...

    java开源包4

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    Java 1.6 API 中文 New

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供了...

    JAVA上百实例源码以及开源项目

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

    JAVA上百实例源码以及开源项目源代码

     Java波浪文字,一个利用Java处理字符的实例,可以设置运动方向参数,显示文本的字符数组,高速文本颜色,显示字体的 FontMetrics对象,得到Graphics实例,得到Image实例,填充颜色数组数据,初始化颜色数组。...

    java开源包11

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包6

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包9

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包101

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包5

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java api最新7.0

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供了...

    java开源包8

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包10

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    JavaAPI1.6中文chm文档 part1

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供...

    Java案例开发锦集

    案例4 利用Java API发送E-mail 案例5 从Mail Server删除一条消息 案例6 在Java程序中实现FTP的功能 案例7 一个最简单的聊天程序 案例8 代理服务器的实现 第十章 Java综合实例 案例1 用Java...

    java开源包3

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包1

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    JavaAPI中文chm文档 part2

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供...

    java jdk-api-1.6 中文 chmd

    java.lang.instrument 提供允许 Java 编程语言代理检测运行在 JVM 上的程序的服务。 java.lang.management 提供管理接口,用于监视和管理 Java 虚拟机以及 Java 虚拟机在其上运行的操作系统。 java.lang.ref 提供...

Global site tag (gtag.js) - Google Analytics