我计划完成50到100篇有关Spring的文章,这是第十三篇。本文对应Spring in Action(Spring实战)第四版第四章中(4.3.4 Annotating introductions)的内容,将讲解如何使用标签为类动态添加方法。
一些像Ruby和Groovy这样的语言,有开放类(open classes)的概念,可以在不改变类和对象的定义的情况下,增加新的方法。不幸的是,Java没有那么动态,一旦一个类编译好之后,很难再为这个类增加功能了。
但是仔细想想,使用AOP的时候,难道不是在动态增加功能吗?虽然没有为类增加方法,但是为这些类执行时增加了一些功能性。进而,使用AOP的introduction的概念,可以给Spring的bean添加方法。
在Spring中,aspect对目标对象进行代理,并且和目标对象有相同接口。试想一下,如果不仅具有目标对象的接口,还添加了一些目标对象没有的接口,那么通过调用这个代理,就可以实现目标对象没有实现的接口了。这样,尽管没有改变目标对象的代码,却在功能上添加了方法。
工作原理如下图:
举个例子,假设Performance是一个接口,现在想为这个接口的所有实现,动态添加一个performEncore()方法。首先我们要定义一个包含performEncore()方法的接口,再定义一个aspect将这个接口与Performance的实例关联。
定义包含要引入的方法的接口
定义包含要引入的performEncore()方法的接口:
1 | public interface Encoreable { |
定义aspect,将方法引入到实例
1 |
|
在这个aspect中,没有使用常用的@Before,@After,@Around等标签,而是使用了@DeclareParents标签。它的value属性指定了哪些实例要引入方法,defaultImpl属性则指定了引入的接口(这个例子中是指Encoreable接口)的默认实现类,而在这个类(DefaultEncoreable)中有引入的方法的具体实现。此外,被引入的接口,使用static修饰。上面的语句中,concert.Performance+表示Performance的任何具体实现类,不包含Performance接口本身。在这个例子中,Performance接口的所有实现类都默认实现了Encoreable接口。所以,在使用Encoreable接口时,可以做到:既使用Performance的实现类的方法,又使用新添加的performEncore方法。至于Encoreable接口代表Performance的哪个具体实现类的实例,则可以在注入bean时决定,而注入的bean则自动有了新增加的performEncore方法,当然,从语法上看,是在使用Encoreable接口的方法。
下面的例子中,注入的bean(Performance接口的某个具体实现类的实例)执行了DefaultEncoreable的performEncore方法。
1 |
|
最后,别忘了aspect自身需要生成实例,才能起作用:
装配aspect的bean
1 | <bean class="concert.EncoreableIntroducer" /> |
装配bean的方法有很多种,这里是使用xml配置的方式。也可以使用java配置,装配bean,详见《Spring in Acton》第四版第二章《装配bean》读书笔记。