【翻译】Spring in action 3.4

本文深入探讨了Spring AOP中的Introduction Advice概念,介绍了如何利用IntroductionInterceptor和IntroductionAdvisor为现有类添加新接口及行为,实现类似多重继承的效果。

 最近在学习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”中的所有方法调用都会被委托给IntroductionMethodInterceptorinvoke方法,由invoke()应该负责这些方法的真正行为——因此不该直接调用MethodInvocation.proceed()。因为现在的目的是要把新的接口介绍(introduce)给目标,若再proceed()就没意义了。

为了更好地理解,让我们回到“Spring Training application”案例。现在有了一个新需求:追踪domain域里的所有对象的最近的更改的时间。现有的对象(CourseStudent等等)都没有这个功能。怎么办呢?常规的做法是修改每一个现有对象(的类)以把新功能加进去,不过我们现在有了更简便的方法:把新功能“介绍给”(introduce)现有的对象,这也称作“混入”(mix-in)

[有点像我们中国人日常生活里所说的“介绍对象”:媒人给某人介绍一个对象,他们二人喜结连理了,然后互相扶持共渡人生…;在Spring里,媒人(ProxyFactoryBean)把A接口(实际上是介绍器,介绍器类得实现A接口),介绍给B对象(目标对象),二者合为一体,得到一个新的对象(proxy),这个proxy兼具二者的功能。ProxyFactoryBeanproxy、目标对象后面还会讲到。]

 

我们先来看看这个被“介绍给”人家的接口: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的接口,只要把该接口传给DelegatingIntroductionInterceptorsuppresInterface()方法就可以啦。

我们刚才说了你不必实现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()进行了overrideinvoke会拦截所有的方法调用,当immutable设置成true时所有以“set”开头的方法调用都会抛出异常。对其他的方法则调用super.invoke()而不是mi.proceed(),这是因为只有父类DelegatingIntroductionInterceptor才知道哪个方法由哪个类(的对象)来负责(目标对象还是拦截器自己负责?)。为确保能正确地处理方法调用,重写的invoke()一定要调用super.invoke()

 

3.4.2  创建IntroductionAdvisor(介绍型通知器)

刚才我们创建的IntroductionInterceptor(介绍型拦截器)就是一种介绍型通知(introduction advice),接下来我们要创建“介绍型通知器”。

“介绍型通知”(introduction advice)只能作用于类级别,要进行方法级的介绍就要靠“介绍型通知器”了。用于方法级介绍的通知器都实现了接口IntroductionAdvisorSpring提供了一个该接口的默认实现类,名字如你所料就是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就是带通知的版本了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值