对于之前介绍过的Before Advice、After Advice、Around Advice、Throw Advice,从使用者的角度 来看,它们“影响了目标物件上某些方法的行为”,例如让某些方法看来似乎增加了一些记录的动 作。
Introduction是个特别的Advice,从使用者的角度来看,它“影响了目标物件的行为定义,直接增 加了目标物件的职责(具体来说就是增加了可 操作的方法)”,例如让某个已定义好的物件,在 不修改该物件之类别档案的情况下,却可以增加一些额外的操作方法到物件之上。
就Java程式语言类别设计的观点来说,动态为物件增加可操作的方法显得不可思议,事实上在 Spring AOP中,您可以透过实作org.springframework.aop.IntroductionInterceptor来实现Introduction。
IntroductionInterceptor 继承 了 MethodInterceptor 与 DynamicIntroductionAdvice 介面 , 其中 implementsInterface()方法(继承自DynamicIntroductionAdvice)如果返回true的话,表示目前的 IntroductionInterceptor实作了给定的介面(也就是要额外增加行为的介面),此时您要使用invoke() 呼叫介面上的方法,让目 标物件执行额外的行为,您不可能使用MethodInvocation的proceed() 方法,因为您要执行的是物件上原来没有的行为,呼叫 proceed()方法没有意义。
从文字上来理解Introduction会比较抽象,举个实际的例子来说,假设您的系统中已经有以下的 类别:
package onlyfun.caterpillar; public interface ISome { public void doSome(); }
package onlyfun.caterpillar; public class Some implements ISome { public void doSome() { System.out.println("原来物件的职责。。。"); } }
您希望在不修改原始档案的情况下,为 Some 类别增加一些可操作的方法,也许您甚至连原始码 档案都没有,只有.class 档案,您唯一知道的也许是他们的 API 说明,在不对它们作出修改的情 况下,您希望 Some 类别可以增加 doOther()方法。
在 Spring 中,您可以藉由实作 IntroductionInterceptor 介面来完成上面的任务,首先您为 doOther() 方法建立介面:
package onlyfun.caterpillar; public interface IOther { public void doOther(); }
接着 定义 一个 OtherIntroduction 类别 实作 IntroductionInterceptor 介面 ,并 在实 作 IntroductionInterceptor 介面的同时,也实作 IOther 介面,例如:
package onlyfun.caterpillar; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.IntroductionInterceptor; public class OtherIntroduction implements IntroductionInterceptor, IOther { // 是否实作自 IOther 介面 public boolean implementsInterface(Class clazz) { return clazz.isAssignableFrom(IOther.class); } public Object invoke(MethodInvocation methodInvocation) throws Throwable { // 如果呼叫的方法来自 IOther 介面的定义 if(implementsInterface( methodInvocation.getMethod().getDeclaringClass())) { // 呼叫执行额外加入(mixin)的行为 return methodInvocation.getMethod(). invoke(this, methodInvocation.getArguments()); } else { return methodInvocation.proceed(); } } public void doOther() { System.out.println("增加的职责。。。"); } }
接 着您要在 Bean 定义档中将 Introduction 缝合 至 Some 物件之上,使用 org.springframework.aop.support.DefaultIntroductionAdvisor 就可以了,例如:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="some" class="onlyfun.caterpillar.Some"/> <bean id="otherIntroduction" class="onlyfun.caterpillar.OtherIntroduction"/> <bean id="otherAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor"> <constructor-arg index="0"> <ref bean="otherIntroduction"/> </constructor-arg> <constructor-arg index="1"> <value>onlyfun.caterpillar.IOther</value> </constructor-arg> </bean> <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>onlyfun.caterpillar.ISome</value> </property> <property name="target"> <ref bean="some"/> </property> <property name="interceptorNames"> <list> <value>otherAdvisor</value> </list> </property> </bean> </beans>
DefaultIntroductionAdvisor 在建构时,需要给它 IntroductionInterceptor 的实例,以及所要代理额 外行为的介面,现在,来撰写一个简单的程式测试,从这个程式当中,您可以更进一步了解何谓 为物件额外增加行为:
package onlyfun.caterpillar; import org.springframework.context.ApplicationContext; import org.springframework.context. support.FileSystemXmlApplicationContext; public class SpringAOPDemo { public static void main(String[] args) { ApplicationContext context = new FileSystemXmlApplicationContext( "beans-config.xml"); ISome some = (ISome) context.getBean("proxyFactoryBean"); some.doSome(); // 看来好像 some 物件动态增加了职责 ((IOther) some).doOther(); } }
对于 some 所参考的物件来说,它原先并不会有 doOther()方法可供操作,然而透过 Spring AOP 的 Introduction 机制,现在 some 所参考的物件多了 doOther()方法可以操作。