4.3.4 通过注解引入新功能

一些编程语言,例如 Ruby 和 Groovy,有开放类的理念。它们可以不用直接修改对象或类的定义就能够为对象或类增加新的方法。不过,Java 并不是动态语言。一旦类编译完成了,我们就很难再为该类添加新的功能了。

但是如果仔细想想,我们在本章中不是一直在使用切面这样做吗?当然,我们还没有为对象增加任何新的方法,但是已经为对象拥有的方法添加了新功能。如果切面能够为现有的方法增加额外的功能,为什么不能为一个对象增加新的方法呢?实际上,利用被称为引入的 AOP 概念,切面可以为 Spring bean 添加新方法。

回顾一下,在 Spring 中,切面只是实现了它们所包装 bean 相同接口的代理。如果除了实现这些接口,代理也能暴露新接口的话,会怎么样呢?那样的话,切面所通知的 bean 看起来像是实现了新的接口,即便底层实现类并没有实现这些接口也无所谓。图 4.7 展示了它们是如何工作的。

我们需要注意的是,当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上,一个 bean 的实现被拆分到了多个类中。

为了验证该主意能行得通,我们为示例中的所有的 Performance 实现引入下面的 Encoreable 接口:

package concert;

public interface Encoreable {
  void performEncore();
}

暂且先不管 Encoreable 是不是一个真正存在的单词,我们需要有一种方式将这个接口应用到 Performance 实现中。我们现在假设你能够访问 Performance 的所有实现,并对其进行修改,让它们都实现 Encoreable 接口。但是,从设计的角度来看,这并不是最好的做法,并不是所有的 Performance 都是具有 Encoreable 特性的。另外一方面,有可能无法修改所有的 Performance 实现,当使用第三方实现并且没有源码的时候更是如此。

值得庆幸的是,借助于 AOP 的引入功能,我们可以不必在设计上妥协或者侵入性地改变现有的实现。为了实现该功能,我们要创建一个新的切面:

package concert;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EncodeableIntroducer {

  @DeclareParents(value="concert.Performce+",
                  defaultImpl=DefaultEncoreable.class)
  public static Encoreable encoreable;
}

可以看到,EncoreableIntroducer 是一个切面。但是,它与我们之前所创建的切面不同,它并没有提供前置、后置或环绕通知,而是通过 @DeclareParents 注解,将 Encoreable 接口引入到 Performance bean 中。

@DeclareParents 注解由三部分组成:

  • value 属性指定了哪种类型的 bean 要引入该接口。在本例中,也就是所有实现 Performance 的类型。(标记符后面的加号表示是 Performance 的所有子类型,而不是 Performance 本身。)

  • defaultImpl 属性指定了为引入功能提供实现的类。在这里,我们指定的是 DefaultEncoreable 提供实现。

  • @DeclareParents 注解所标注的静态属性指明了要引入了接口。在这里,我们所引入的是 Encoreable 接口。

和其他的切面一样,我们需要在 Spring 应用中将 EncoreableIntroducer 声明为一个 bean:

<bean class="concert.EncoreableIntroducer" />

Spring 的自动代理机制将会获取到它的声明,当 Spring 发现一个 bean 使用了 @Aspect 注解时,Spring 就会创建一个代理,然后将调用委托给被代理的 bean 或被引入的实现,这取决于调用的方法属于被代理的 bean 还是属于被引入的接口。

在 Spring 中,注解和自动代理提供了一种很便利的方式来创建切面。它非常简单,并且只涉及到最少的 Spring 配置。但是,面向注解的切面声明有一个明显的劣势:你必须能够为通知类添加注解。为了做到这一点,必须要有源码。

如果你没有源码的话,或者不想将 AspectJ 注解放到你的代码之中,Spring 为切面提供了另外一种可选方案。让我们看一下如何在 Spring XML 配置文件中声明切面。

Last updated