最近在学习Spring in action英文版,突然就想把它翻译出来以便自己复习。边学边翻,学到哪翻到哪。中间有些自己加的内容,用[ ]加斜体表示。首先就从3.4节开始,之前的等全部完了以后有时间再翻吧。
3.4 创建Introduction(介绍器)
我们之前曾提到introduction和其他的Spring advice有些不同。其他的都是在环绕着某个方法接入点(join point)处被织入(weave in)的,introduction则能对整个类起作用,它可以向目标类(的对象)添加新的行为和属性。这意味着你可以让现有的类实现新的接口,增加新的状态,这也叫作混入(mix-in)。换言之,introduction可以让你动态地组合对象,获得类似于多重继承的效果。
3.4.1 实现IntroductionInterceptor(介绍型拦截器)
Spring里的介绍器(introduction)是指IntroductionMethodInterceptor接口,它继承自MethodInterceptor接口,并增加了一个方法:
boolean implementsInterface (Class theInterface);
这个方法决定了介绍器该怎样运作。其参数"theInterface"应该是一个接口类型,如果介绍器类实现了接口“theInterface”,本方法就返回true。这也意味着“theInterface”中的所有方法调用都会被委托给IntroductionMethodInterceptor的invoke方法,由invoke()应该负责这些方法的真正行为——因此不该直接调用MethodInvocation.proceed()。因为现在的目的是要把新的接口介绍(introduce)给目标,若再proceed()就没意义了。
为了更好地理解,让我们回到“Spring Training application”案例。现在有了一个新需求:追踪domain域里的所有对象的最近的更改的时间。现有的对象(Course、Student等等)都没有这个功能。怎么办呢?常规的做法是修改每一个现有对象(的类)以把新功能加进去,不过我们现在有了更简便的方法:把新功能“介绍给”(introduce)现有的对象,这也称作“混入”(mix-in)。
[有点像我们中国人日常生活里所说的“介绍对象”:媒人给某人介绍一个对象,他们二人喜结连理了,然后互相扶持共渡人生…;在Spring里,媒人(ProxyFactoryBean)把A接口(实际上是介绍器,介绍器类得实现A接口),介绍给B对象(目标对象),二者合为一体,得到一个新的对象(proxy),这个proxy兼具二者的功能。ProxyFactoryBean、proxy、目标对象后面还会讲到。]
我们先来看看这个被“介绍给”人家的接口:Auditable(意:可审计的),见listing 3.11
Listing 3.11 Auditable.java
package com.springinaction.training.advice;
import java.util.Date;
public interface Auditable {
void setLastModifiedDate(Date date);
Date getLastModifiedDate();
}
简单明了,嗯?接下来编写介绍器类AuditableMixin,她应该实现Auditale接口和IntroductionMethodInterceptor接口:
[这个类才是真正要被“介绍给”别人的:]
Listing 3.12 AuditableMixin.java subclassing IntroductionInterceptor
package com.springinaction.training.advice;
import java.util.Date;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;
public class AuditableMixin
implements IntroductionInterceptor, Auditable {
public boolean implementsInterface(Class intf) {
return intf.isAssignableFrom(Auditable.class);
// 既然本类实现了Auditable接口,那么当参数是Auditable的
// 实现时就应该返回true
}
public Object invoke(MethodInvocation m) throws Throwable {
if (implementsInterface(m.getMethod().getDeclaringClass())){
return m.getMethod().invoke(this, m.getArguments());
}
else {
return m.proceed();
}
}
private Date lastModifiedDate;
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
首先,我们的类不仅实现了Spring中的接口IntroductionInterceptor,还实现了我们的业务接口Auditable,因为这个类既是介绍器又得负责业务功能的具体实现。因此这个类有来自Auditable接口的两个方法和用来保持跟踪状态的lastModifiedDate属性。
其次,如果被调用的方法是Auditable接口下的,那么implementsInterace()会返回true。这意味着我们必须在本类的invoke方法里为Auditable接口的那两个方法提供具体实现;对于外界任何一个对Auditable接口中的方法的调用,我们都会调用本类里的对应方法,而其余的则让它们proceed。
这就是一个典型的introduction用例——如此的典型以至于Spring为此提供了一个方便的类:DelegatingIntroductionInterceptor.。Listing 3.13演示了用法,我们的上个例子变得容易多了。
Listing 3.13
package com.springinaction.training.advice.AuditableMixin;
import java.util.Date;
import org.springframework.aop.support.
DelegatingIntroductionInterceptor;
public class AuditableMixin
extends DelegatingIntroductionInterceptor implements Auditable {
private Date lastModifiedDate;
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
注意我们的类AuditableMixin里为什么没有invoke()——这由它的父类DelegatingIntroductionInterceptor实现:对于某个方法调用,检查它所属的接口,如果所属接口被AuditableMixin实现了,那么该方法将被委托给invoke()。我们的类实现了Auditable,因此对Auditable接口中的任何一个方法的调用都会被拦截,而其它的方法则委托给目标对象。如果你的拦截器类得实现某个你不希望进行mix-in的接口,只要把该接口传给DelegatingIntroductionInterceptor的suppresInterface()方法就可以啦。
我们刚才说了你不必实现invoke(),但如果你的mix-in类要对目标类的某些行为进行调节的话也可以实现。例如,假设你有一个接口Immutable(意:不可变的),它只有一个方法,你想要把它介绍到目标对象。这个接口的功能是让对象不可变动——对象的内部状态不能改变。见Listing 3.14。
Listing 3.14 ImmutableMixin.java
package com.springinaction.chapter03.aop;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.
DelegatingIntroductionInterceptor;
public class ImmutableMixin
extends DelegatingIntroductionInterceptor implements Immutable {
private boolean immutable;
public void setImmutable(boolean immutable) {
this.immutable = immutable;
}
public Object invoke(MethodInvocation mi)
throws Throwable {
String name = mi.getMethod().getName();
if (immutable && name.indexOf("set") == 0) {
throw new IllegalModificationException();
}
return super.invoke(mi);
}
}
这里invoke()进行了override,invoke会拦截所有的方法调用,当immutable设置成true时所有以“set”开头的方法调用都会抛出异常。对其他的方法则调用super.invoke()而不是mi.proceed(),这是因为只有父类DelegatingIntroductionInterceptor才知道哪个方法由哪个类(的对象)来负责(目标对象还是拦截器自己负责?)。为确保能正确地处理方法调用,重写的invoke()里一定要调用super.invoke()。
3.4.2 创建IntroductionAdvisor(介绍型通知器)
刚才我们创建的IntroductionInterceptor(介绍型拦截器)就是一种介绍型通知(introduction advice),接下来我们要创建“介绍型通知器”。
“介绍型通知”(introduction advice)只能作用于类级别,要进行方法级的介绍就要靠“介绍型通知器”了。用于方法级介绍的通知器都实现了接口IntroductionAdvisor。Spring提供了一个该接口的默认实现类,名字如你所料就是DefaultIntroductionAdvisor,它的构造器需要一个IntroductionInterceptor型参数。这个类能满足大多数应用的需要了。Listing3.15演示了如何把刚才的AuditableMixin组合到DefaultIntroductionAdvisor里。
Listing 3.15 Configuring an introduction
<beans>
<bean id="courseTarget"
Listing 3.15 Configuring an introduction
class="com.springinaction.training.model.Course"
singleton="false"/>
<bean id="auditableMixin"
class="com.springinaction.training.advice.AuditableMixin"
singleton="false"/>
<bean id="auditableAdvisor" class="org.springframework.
aop.support.DefaultIntroductionAdvisor"
singleton="false">
<constructor-arg>
<ref bean="auditableMixin"/>
</constructor-arg>
</bean>
<bean id="course"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyTargetClass">
<value>true</value>
</property>
<property name="singleton">
<value>false</value>
</property>
<property name="proxyInterfaces">
<value>com.springinaction.training.advice.Auditable</value>
</property>
<property name="auditableAdvisor">
<list>
<value>servletAdvisor</value>
</list>
</property>
<property name="target">
<value ref="courseTarget">
</property>
</bean>
</beans>
请注意,所有的3个AOP相关bean(auditableMixin,autditableAdvisor和course)的singleton属性都被设置成false。这是因为我们要介绍的是一个有状态的(stateful)mixin。因此每次我们向BeanFactory请求的bean时都应该获得一个新的实例。如果不把singleton设为false的话,所有被通知的对象都得靠同一个 introduction对象来维持状态,这显然不是我们所希望的。
3.4.3 慎用introduction advice
大多数其他的advice,比如before和after advice,一般都只介绍新的行为。而introduction advice则可以为目标添加新的接口和状态。这一招颇具威力,应该谨慎使用。
在之前那个把Auditable接口介绍给Course类的例子里,只有当你从Spring的某个BeanFactory里请求一个Course的对象时,advice才会被织入(weave into)到那个Course对象[当然不是真的往Course的字节码里混入什么东西,而是由于BeanFactory返回给你的其实并不是一个“真正的”Course,而是“真正的”Course的代理对象(用ProxyBeanFactory产生的),这个代理对象的行为看起来好像是“原版”Course被织入了新的东西]。记住,有的AOP框架可能是在编译时织入[也就是真的要对目标类的字节码进行改变],而Spring则是在运行时进行织入的,这意味着通过其他非BeanFactory途径创建或获得的Course是没有advice的,比如使用new运算符创建的,通过其他框架(如Hibernate这样的持久层框架)创建的,以及通过反串行化操作(deserialize)创建的实例。
你也可以在自己的代码中生成被织入过的实例,方法是通过工厂来获得对象。例如,用一个CourseFactory接口来生产Course的新实例:
public interface CourseFactory {
Course getCourse();
}
如果你不希望你的代码依赖于Spring特有的类,可以在所有需要Course实例的地方改用CourseFactory接口来获得Course。然后你可以用一个CourseFactory的实现类,把生产Course的工作交给Spring的BeanFactory来做,见listing 3.16
Listing 3.16 BeanFactoryCourseFactory.java
package com.springinaction.training.model;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
public BeanFactoryCourseFactory
implements CourseFactory, BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public Course getCourse() {
return (Course) beanFactory.getBean("course");
}
}
之后,你的代码通过CourseFactory而不是“new”来获得Course的新实例:
…
private CourseFactory courseFactory;
public void setCourseFactory(CourseFactory courseFactory) {
this.courseFactory = courseFactory;
}
public void someMethod() {
Course course = CourseFactory.getCourse();
…
}
这回你得到的Course就是带通知的版本了。
本文深入探讨了Spring AOP中的Introduction Advice概念,介绍了如何利用IntroductionInterceptor和IntroductionAdvisor为现有类添加新接口及行为,实现类似多重继承的效果。
1861

被折叠的 条评论
为什么被折叠?



