Spring中引入增强(IntroductionAdvice)的底层实现原理

淩亂°似流年 2022-01-14 18:45 122阅读 0赞

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

作为一名程序员,舆论一直在告诫我,要有自己的博客,要记录并懂得和别人分享你的技术心得。然而,遗憾的是,每当我有所收获,并为之兴奋,准备下笔要和别人分享之时,我蓦然发现,已经有N位程序员同仁,在N年前,就已经记录过并分享过无数多次该技术要点了。此时,我又何必多此一举的弄一个N+1呢?多年过去,依旧是白纸一张。

不过,在等待多年后,我已经按耐不住了,我的机会终于来了,这一次,无论外面如何春暖花开,如何艳阳高照,如何美女如云,我都视之如粪土。现在,我要做的是,与君分享技术心得。

Let’s Go(来,死狗).

Spring中有五种增强:BeforeAdvide(前置增强)、AfterAdvice(后置增强)、ThrowsAdvice(异常增强)、RoundAdvice(环绕增强)、IntroductionAdvice(引入增强)

RoundAdvice(环绕增强):就是BeforeAdvide(前置增强)、AfterAdvice(后置增强)的组合使用叫环绕增强。

前四种增强都比较简单,我们今天要介绍的是IntroductionAdvice(引入增强)的概念及原理。

引入增强(Introduction Advice)的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。

1.Cglib实现引入增强

记住,我的目的不是告诉你怎么在Spring中使用引入增强功能(这不是我的风格),而是探究引入增强功能的底层实现原理。

  1. public interface IHello {
  2. public void sayHello();
  3. }

上面是接口功能,CeremonyService是需要增强的类,在不改变CeremonyService类的情况下,使其具备IHello接口功能。

  1. public class CeremenyService {
  2. public void sayBye() {
  3. System.out.println("Say bye from Ceremeny.");
  4. }
  5. }

看起来要像下面这样:

  1. CeremenyService cs;
  2. IHello ih = (IHello) cs;
  3. ih.sayHello();

即,CeremenyService居然变成了IHello类型。

我们编写一个重要的拦截器,来实现此功能。

  1. import net.sf.cglib.proxy.MethodInterceptor;
  2. import net.sf.cglib.proxy.MethodProxy;
  3. import x.y.IHello;
  4. public class IntroInterceptor implements MethodInterceptor, IHello {
  5. // 实现了IHello增强接口的对象
  6. private Object delegate;
  7. public IntroInterceptor() {
  8. this.delegate = this;
  9. }
  10. public IntroInterceptor(Object delegate) {
  11. this.delegate = delegate;
  12. }
  13. @Override
  14. public void sayHello() {
  15. System.out.println("Say hello from delegate.");
  16. }
  17. @Override
  18. public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  19. Class<?> clz = method.getDeclaringClass();
  20. if (clz.isAssignableFrom(IHello.class)) {
  21. // 如果实现了IHello增强接口,则调用实现类delegate的方法
  22. return method.invoke(delegate, args);
  23. }
  24. return methodProxy.invokeSuper(obj, args);
  25. }
  26. }

我们来编写一个测试类。

  1. public static void main(String[] args) {
  2. Enhancer en = new Enhancer();
  3. en.setSuperclass(CeremenyService.class);
  4. en.setInterfaces(new Class[] { IHello.class });
  5. en.setCallback(new IntroInterceptor());
  6. CeremenyService cs = (CeremenyService) en.create();
  7. cs.sayBye();
  8. IHello ih = (IHello) cs;
  9. ih.sayHello();
  10. }

en.setInterfaces(new Class[] { IHello.class });非常重要,表示Cglib生成代理类,将要实现的接口集合。

于是生成的代理类Class,类似于:public class CeremenyServiceEnhancerByCGLIB86859be5 extends CeremenyService implements IHello

输出结果:

  1. Say bye from Ceremeny.
  2. Say hello from delegate.

这就是大名鼎鼎的引入增强(Introduction Advice)的底层实现原理。

2. Spring framework引入增强源码解读

Spring的xml文件配置。

  1. <bean id="ceremonyService" class="x.y.service.CeremonyService" />
  2. <bean id="ceremonyIntroAdvice" class="x.y.advice.CeremonyIntroAdvice" />
  3. <bean id="ceremonyProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  4. <property name="interfaces" value="x.y.IHello"/> <!-- 需要动态实现的接口 -->
  5. <property name="target" ref="ceremonyService"/> <!-- 目标类 -->
  6. <property name="interceptorNames" value="ceremonyIntroAdvice"/> <!-- 引入增强 -->
  7. <property name="proxyTargetClass" value="true"/> <!-- 代理目标类(默认为 false,代理接口) -->
  8. </bean>

我们需要自定义一个拦截器。

  1. import org.aopalliance.intercept.MethodInvocation;
  2. import org.springframework.aop.support.DelegatingIntroductionInterceptor;
  3. import x.y.IHello;
  4. @SuppressWarnings("serial")
  5. public class CeremonyIntroAdvice extends DelegatingIntroductionInterceptor implements IHello {
  6. @Override
  7. public Object invoke(MethodInvocation mi) throws Throwable {
  8. return super.invoke(mi);
  9. }
  10. @Override
  11. public void sayHello() {
  12. System.out.println("Say hello.");
  13. }
  14. }

在Spring中,要实现引入增强,需要继承自DelegatingIntroductionInterceptor。

下面看看该DelegatingIntroductionInterceptor类的invoke()方法源码。

  1. @Override
  2. public Object invoke(MethodInvocation mi) throws Throwable {
  3. // 检测是否是引入增强
  4. if (isMethodOnIntroducedInterface(mi)) {
  5. // 执行实现了引入增强接口的delegate对象的增强方法
  6. Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
  7. // Massage return value if possible: if the delegate returned itself,
  8. // we really want to return the proxy.
  9. if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
  10. Object proxy = ((ProxyMethodInvocation) mi).getProxy();
  11. if (mi.getMethod().getReturnType().isInstance(proxy)) {
  12. retVal = proxy;
  13. }
  14. }
  15. return retVal;
  16. }
  17. return doProceed(mi);
  18. }

AopUtils.invokeJoinpointUsingReflection()方法内部,其实就是反射方法调用。

  1. try {
  2. ReflectionUtils.makeAccessible(method);
  3. return method.invoke(target, args);
  4. }

最后写一个测试方法,来测试一下。

  1. public static void main(String[] args) {
  2. FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
  3. "D:/workspace/Spring4.2.5/bin/applicationContext.xml");
  4. CeremonyService service = context.getBean("ceremonyProxy", CeremonyService.class);
  5. service.sayBye();
  6. IHello hello = (IHello) service;
  7. hello.sayHello();
  8. context.close();
  9. }

输出:

  1. Say bye.
  2. Say hello.

总结:介绍如何使用的文章比较多,而介绍原理性的文章少一些,我比较喜欢介绍原理性的文章。希望本篇博文,对您有用。

版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

转载于:https://my.oschina.net/zudajun/blog/663962

发表评论

表情:
评论列表 (有 0 条评论,122人围观)

还没有评论,来说两句吧...

相关阅读