Spring中有五种增强:BeforeAdvide(前置增强)、AfterAdvice(后置增强)、ThrowsAdvice(异常增强)、RoundAdvice(环绕增强)、IntroductionAdvice(引入增强)
RoundAdvice(环绕增强):就是BeforeAdvide(前置增强)、AfterAdvice(后置增强)的组合使用叫环绕增强。
前四种增强都比较简单,我们今天要介绍的是IntroductionAdvice(引入增强)的概念及原理。
引入增强(Introduction Advice)的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。
1.Cglib实现引入增强
记住,我的目的不是告诉你怎么在Spring中使用引入增强功能(这不是我的风格),而是探究引入增强功能的底层实现原理。
public interface IHello { public void sayHello(); }
上面是接口功能,CeremonyService是需要增强的类,在不改变CeremonyService类的情况下,使其具备IHello接口功能。
public class CeremenyService { public void sayBye() { System.out.println("Say bye from Ceremeny."); } }
看起来要像下面这样:
CeremenyService cs; IHello ih = (IHello) cs; ih.sayHello();
即,CeremenyService居然变成了IHello类型。
我们编写一个重要的拦截器,来实现此功能。
import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import x.y.IHello; public class IntroInterceptor implements MethodInterceptor, IHello { // 实现了IHello增强接口的对象 private Object delegate; public IntroInterceptor() { this.delegate = this; } public IntroInterceptor(Object delegate) { this.delegate = delegate; } @Override public void sayHello() { System.out.println("Say hello from delegate."); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Class<?> clz = method.getDeclaringClass(); if (clz.isAssignableFrom(IHello.class)) { // 如果实现了IHello增强接口,则调用实现类delegate的方法 return method.invoke(delegate, args); } return methodProxy.invokeSuper(obj, args); } }
我们来编写一个测试类。
public static void main(String[] args) { Enhancer en = new Enhancer(); en.setSuperclass(CeremenyService.class); en.setInterfaces(new Class[] { IHello.class }); en.setCallback(new IntroInterceptor()); CeremenyService cs = (CeremenyService) en.create(); cs.sayBye(); IHello ih = (IHello) cs; ih.sayHello(); }
en.setInterfaces(new Class[] { IHello.class });非常重要,表示Cglib生成代理类,将要实现的接口集合。
于是生成的代理类Class,类似于:public class CeremenyService$$EnhancerByCGLIB$$86859be5 extends CeremenyService implements IHello
输出结果:
Say bye from Ceremeny. Say hello from delegate.
这就是大名鼎鼎的引入增强(Introduction Advice)的底层实现原理。
2. Spring framework引入增强源码解读
Spring的xml文件配置。
<bean id="ceremonyService" class="x.y.service.CeremonyService" /> <bean id="ceremonyIntroAdvice" class="x.y.advice.CeremonyIntroAdvice" /> <bean id="ceremonyProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="interfaces" value="x.y.IHello"/> <!-- 需要动态实现的接口 --> <property name="target" ref="ceremonyService"/> <!-- 目标类 --> <property name="interceptorNames" value="ceremonyIntroAdvice"/> <!-- 引入增强 --> <property name="proxyTargetClass" value="true"/> <!-- 代理目标类(默认为 false,代理接口) --> </bean>
我们需要自定义一个拦截器。
import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; import x.y.IHello; @SuppressWarnings("serial") public class CeremonyIntroAdvice extends DelegatingIntroductionInterceptor implements IHello { @Override public Object invoke(MethodInvocation mi) throws Throwable { return super.invoke(mi); } @Override public void sayHello() { System.out.println("Say hello."); } }
在Spring中,要实现引入增强,需要继承自DelegatingIntroductionInterceptor。
下面看看该DelegatingIntroductionInterceptor类的invoke()方法源码。
@Override public Object invoke(MethodInvocation mi) throws Throwable { // 检测是否是引入增强 if (isMethodOnIntroducedInterface(mi)) { // 执行实现了引入增强接口的delegate对象的增强方法 Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments()); // Massage return value if possible: if the delegate returned itself, // we really want to return the proxy. if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) { Object proxy = ((ProxyMethodInvocation) mi).getProxy(); if (mi.getMethod().getReturnType().isInstance(proxy)) { retVal = proxy; } } return retVal; } return doProceed(mi); }
AopUtils.invokeJoinpointUsingReflection()方法内部,其实就是反射方法调用。
try { ReflectionUtils.makeAccessible(method); return method.invoke(target, args); }
最后写一个测试方法,来测试一下。
public static void main(String[] args) { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext( "D:/workspace/Spring4.2.5/bin/applicationContext.xml"); CeremonyService service = context.getBean("ceremonyProxy", CeremonyService.class); service.sayBye(); IHello hello = (IHello) service; hello.sayHello(); context.close(); }
输出:
Say bye. Say hello.