前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Spring 中一个少见的引介增强 IntroductionAdvisor

Spring 中一个少见的引介增强 IntroductionAdvisor

作者头像
江南一点雨
发布于 2023-09-09 08:33:58
发布于 2023-09-09 08:33:58
30500
代码可运行
举报
文章被收录于专栏:玩转JavaEE玩转JavaEE
运行总次数:0
代码可运行

我们平时做 AOP 开发的时候,基本上都是增强某一个方法,在某一个方法执行之前或者执行之后做一些事情,这种叫做 PointcutAdvisor,实际上,Spring 中的 Advisor 大致可以分为两种类型,除了 PointcutAdvisor 之外,还有另外一种 Advisor 叫做 IntroductionAdvisor,因为最近想和小伙伴们聊一聊 Spring AOP 的源码,看源码有一个前提就是得先掌握 Spring 的各种用法,这样看源码的时候往往就有一种醍醐灌顶的感觉,否则看源码的时候就容易懵!

1. 实践

不同于 PointcutAdvisor,IntroductionAdvisor 这种增强主要是针对一个类来增强。

接下来松哥写一个简单的案例,小伙伴们来看下 IntroductionAdvisor 到底做了什么工作。

假设我有一个 Animal 接口,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Animal {
    void eat();
}

这个动物具备吃的能力。

现在我还有一个 Dog,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Dog {
    void run();
}
public class DogImpl implements Dog{
    @Override
    public void run() {
        System.out.println("Dog run");
    }
}

Dog 具备跑的能力,注意,Dog 和 Animal 之间并无继承/实现的关系。

现在,我们通过 Spring 中的 IntroductionAdvisor,就能让 Dog 具备 Animal 的能力,我们来看下具体怎么做。

首先,我们先来开发一个 Advice,这个 Advice 同时也是 Animal 接口的实现类,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class AnimalIntroductionInterceptor implements IntroductionInterceptor, Animal {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
            return invocation.getMethod().invoke(this, invocation.getArguments());
        }
        return invocation.proceed();
    }

    @Override
    public void eat() {
        System.out.println("Animal eat");
    }

    @Override
    public boolean implementsInterface(Class<?> intf) {
        return intf.isAssignableFrom(this.getClass());
    }
}

跟普通 AOP 一样,当目标方法被拦截下来的时候,这里的 invoke 方法会被触发,在 invoke 方法中我们需要先调用 implementsInterface 方法进行判断,如果被拦截下来的方法所属的类是 Animal 的话,即 implementsInterface 方法返回 true 的情况(this 其实就是 Animal),那么就直接获取到 method 对象然后通过反射去调用就行了,这样会就会导致这里的 eat 方法被触发;否则,说明是被拦截下来的方法本身,那么就调用 invocation.proceed(); 让拦截器链继续往下执行即可。

接下来我们来定义 Advisor:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class DogIntroductionAdvisor implements IntroductionAdvisor {
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return Dog.class.isAssignableFrom(clazz);
            }
        };
    }

    @Override
    public void validateInterfaces() throws IllegalArgumentException {

    }

    @Override
    public Advice getAdvice() {
        return new AnimalIntroductionInterceptor();
    }

    @Override
    public boolean isPerInstance() {
        return true;
    }

    @Override
    public Class<?>[] getInterfaces() {
        return new Class[]{Animal.class};
    }
}

这里有几个方法需要实现:

  1. getClassFilter:哪些类需要拦截在这里配置,ClassFilter 松哥在上篇文章中已经讲过了,这里只需要返回被拦截的类就行了,不需要具体到哪个方法被拦截。
  2. getAdvice:这个就是返回拦截下来后执行的通知,我们就返回前面定义的通知即可,这里有一个要求,就是 这个 Advice 需要实现 Animal 接口。
  3. getInterfaces:这个方法还比较重要,生成代理对象的时候,代理对象需要实现哪些接口,就是从这个地方定义的,这里返回 Animal,所以将来我拿到手的代理对象就实现了 Animal 接口,就能调用 Animal 中的方法了。
  4. isPerInstance:这个方法暂时没有实现,返回 true 就行了。
  5. validateInterfaces:这个方法是做接口校验的,我这里就不校验了。

好啦,我的代码现在就写好了,我们来测试看下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("introduction.xml");
Dog dog = ctx.getBean(Dog.class);
dog.run();
System.out.println("Animal.class.isAssignableFrom(dog.getClass()) = " + Animal.class.isAssignableFrom(dog.getClass()));
Animal animal = (Animal) dog;
animal.eat();

执行结果如下:

我们拿到手的 dog 对象其实也是一个 Animal。

这就是 Spring AOP 中的 IntroductionAdvisor,当一个类需要具备另一个类的能力的时候,可以使用 IntroductionAdvisor。

2. 源码分析

那么这一切是怎么实现的呢?

因为这篇文章我主要是想和小伙伴们分享 IntroductionAdvisor 的知识点,所以关于 AOP 完整的创建流程我先不说,在后续的文章中我会和大家做一个详细介绍,我今天就来和大家聊一聊在 Spring AOP 执行的过程中,究竟是如何处理 IntroductionAdvisor 的。

Spring AOP 中创建代理对象,一般是通过后置处理器来完成,从 AbstractAutoProxyCreator#postProcessAfterInitialization 方法开始,大致时序图如下:

我们就从 buildProxy 方法开始看起吧,这个方法看名字就知道是用来构建代理对象的。

AbstractAutoProxyCreator#buildProxy:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
  @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
 //...
 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
 proxyFactory.addAdvisors(advisors);
 //...
 return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

这里有一个 buildAdvisors 方法,这个方法是用来处理 Advisor 的,我们自定义的 DogIntroductionAdvisor 将在这里被读取进来,然后将之添加到 proxyFactory 对象中,在添加的过程中,会进行一些额外的处理,proxyFactory#addAdvisors 最终会来到 AdvisedSupport#addAdvisors 方法中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void addAdvisors(Collection<Advisor> advisors) {
 if (!CollectionUtils.isEmpty(advisors)) {
  for (Advisor advisor : advisors) {
   if (advisor instanceof IntroductionAdvisor introductionAdvisor) {
    validateIntroductionAdvisor(introductionAdvisor);
   }
   this.advisors.add(advisor);
  }
  adviceChanged();
 }
}

在这里会遍历所有的 Advisor,判断类型是不是 IntroductionAdvisor 类型的,我们自定义的 DogIntroductionAdvisor 恰好就是 IntroductionAdvisor 类型的,所以会进一步调用 validateIntroductionAdvisor 方法,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private void validateIntroductionAdvisor(IntroductionAdvisor advisor) {
 advisor.validateInterfaces();
 Class<?>[] ifcs = advisor.getInterfaces();
 for (Class<?> ifc : ifcs) {
  addInterface(ifc);
 }
}
public void addInterface(Class<?> intf) {
 if (!this.interfaces.contains(intf)) {
  this.interfaces.add(intf);
  adviceChanged();
 }
}

小伙伴们看一下,advisor.getInterfaces(); 实际上就调用到我们自定义的 DogIntroductionAdvisor 中的 getInterfaces 方法了,所以这里会返回 Animal 接口,然后这里会把 Animal 接口存入到 interfaces 这个变量中,将来在生成 AOP 对象的时候会用到。

好啦,现在回到 buildProxy 方法中,该方法最终会执行到 proxyFactory.getProxy 方法,该方法最终执行的时候,要么是 JDK 动态代理,要么是 CGLIB 动态代理,我们分别来说一下。

2.1 JDK 动态代理

先说如果是 JDK 动态代理,那么 proxyFactory.getProxy 方法就需要构建一个 JdkDynamicAopProxy 出来,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
 this.advised = config;
 this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
 findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}

参数 config 中就包含了我们前面说的要实现的接口,所以这里 proxiedInterfaces 变量中保存的就是代理对象将来要实现的接口,以我们前面的代码为例,这里 proxiedInterfaces 的值如下:

可以看到,就包含了 Animal 接口。

最后,调用 JdkDynamicAopProxy#getProxy 方法生成代理对象,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
 return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

这就是大家比较熟悉的 JDK 动态代理了,可以看到,生成的代理对象有五个接口,生成的代理对象不仅仅是 Dog、Animal 的实例,也是 SpringProxy 等的实例。现在大家就明白了为什么我们拿到手的 dog 对象还能强转成 Animal 了。

2.2 CGLIB 动态代理

再来看 CGLIB 动态代理的实现逻辑,其实也差不多:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
 this.advised = config;
 this.advisedDispatcher = new AdvisedDispatcher(this.advised);
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
 return buildProxy(classLoader, false);
}
private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
 //...
 enhancer.setSuperclass(proxySuperClass);
 enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
 //...
 // Generate the proxy class and create a proxy instance.
 return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
}

可以看到,其实跟 JDK 里边的思路差不多,也是从 advised 中提取出来接口设置进去,advised 也是在 CglibAopProxy 对象构建的时候传入进来的。

3. 小结

好了,现在小伙伴们应该明白了什么是 IntroductionAdvisor 了吧?说白了,就是在生成代理对象的时候,把我们在 Advisor 中设置好的接口也考虑进去,这样生成的代理对象同时也是该接口的实现类,当然,在我们提供的 Advice 中,必须也要实现该接口,否则代理对象执行接口中的方法,找不到具体实现的时候就会报错了。

感兴趣的小伙伴赶紧体验一把吧~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江南一点雨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
机器学习的5种“兵法"
在研究机器学习中,理论在其整个自上而下方法中试用于哪里呢? 在传统的机器学习教学中,丰富的数学理论知识对于理解机器学习是至关重要的,我的机器学习教学方法通常是教你如何端对端解决问题以及传输结构。 所以此方法哪里要用到理论知识了呢? 在此帖中你将了解我们是如何理解机器学习中的“理论”一词。提示:全部与算法有关。 你会发现一旦你可以熟练解决问题以及传输结果后,你就欲罢不能地想要对机器学习有更深入的理解以及获得更好的结果,没人能拉得住你。 最后,你会发现可以通过应用标准数据集练习机器学习的5个技巧,从而进一步加深
CDA数据分析师
2018/02/05
5800
机器学习的5种“兵法"
警惕!机器学习入门阶段易犯的5个错误
怎样进入机器学习领域没有定式。我们的学习方式都有些许不同,学习的目标也因人而异。 但一个共同的目标就是要能尽快上手。如果这也是你的目标,那么这篇文章为你列举了程序员们在通往机器学习高手道路上常见的五种错误。 1.将机器学习看得高不可攀 机器学习不过是另一堆技术的集合,你可以用它来解决复杂问题。这是一个飞速发展的领域,因此,机器学习的学术交流一般出现在学术期刊及研究生的课本里,让它看起来高不可攀又难于理解。 要想高效掌握机器学习,我们需要转变观念,从技术转到方法,由精确变为“足够好”,这也同样适用于程序员感兴
CDA数据分析师
2018/02/05
5710
谷歌机器学习白皮书全解析 43条黄金法则(一)
编者按:此文由AI科技评论独家编译,未经许可拒绝转载。此白皮书为谷歌总结的机器学习(ML)最优实践方法,浓缩了其多年技术积累与经验,尤其是 Youtube、Google Play 和 Google+ 等平台背后的 ML 算法开发、维护经历。谷歌于白皮书中总结了四十三条 ML 黄金法则,旨在帮助已经掌握了基础知识的开发者少走弯路。鉴于其珍贵程度与技术性,AI科技评论逐条做了严格尊重原文的翻译。若你已学习过机器学习课程,抑或有开发 ML 模型的经验,那么应当具备足够的背景知识理解这篇文章。 术语 以下是对文
AI科技评论
2018/03/09
1.1K0
谷歌机器学习白皮书全解析 43条黄金法则(一)
5种无需数学背景也可理解机器学习算法的技巧
在一种自顶向下的研究机器学习的方法中,理论应立足于何处?
Bon
2018/02/06
1.2K1
5种无需数学背景也可理解机器学习算法的技巧
理解任何机器学习算法的6个问题
有很多机器学习算法,每个算法都是一个独立的研究。
BAStriver
2018/02/06
7800
理解任何机器学习算法的6个问题
停止从头开始编写机器学习算法
从头开始实现算法是我看到初学者犯的最大的错误之一。
anthlu
2018/02/08
7420
停止从头开始编写机器学习算法
机器学习法则:(谷歌)机器学习工程最佳实践
机器学习(ML)最优实践方法,浓缩了其多年技术积累与经验,尤其是YouTube、Google Play和Google+ 等平台背后的ML算法开发、维护经历。谷歌于白皮书中总结了四十三条ML黄金法则,旨在帮助已经掌握了基础知识的开发者少走弯路。
机器学习AI算法工程
2019/10/28
5920
机器学习法则:(谷歌)机器学习工程最佳实践
动态 | 谷歌发布机器学习规则: 关于机器学习工程的最佳实践(上)
本文档旨在帮助已掌握机器学习基础知识的人员从 Google 机器学习的最佳实践中受益。以下为上篇,包含术语、概览以及在进行机器学习之前的第 1-20 条规则。
AI科技评论
2018/07/26
5020
《量子机器学习》作者 Peter Wittek 最新研究:高级量子资源可用于机器学习
【新智元导读】《量子机器学习》作者 Peter Wittek 的最新研究发现,与近似概率推理的经典启发式算法相比,量子协议的最新结果直接应用于 Gibbs 抽样,在速度上产生了指数级的提升,这一发现从
新智元
2018/03/26
7630
《量子机器学习》作者 Peter Wittek 最新研究:高级量子资源可用于机器学习
ML入门阶段易犯的5个错误
怎样进入机器学习领域没有定式。我们的学习方式都有些许不同,学习的目标也因人而异。但一个共同的目标就是要能尽快上手。如果这也是你的目标,那么这篇文章为你列举了程序员们在通往机器学习高手道路上常见的五种错误。 1. 将机器学习看得高不可攀   机器学习不过是另一堆技术的集合,你可以用它来解决复杂问题。这是一个飞速发展的领域,因此,机器学习的学术交流一般出现在学术期刊及研究生的课本里,让它看起来高不可攀又难于理解。   要想高效掌握机器学习,我们需要转变观念,从技术转到方法,由精确变为“足够好”,这也同样适用于
计算机视觉研究院
2018/04/17
1K0
机器学习近年来之怪现状
总体来说,机器学习(ML)的研究人员正在致力于数据驱动算法知识的创建与传播。在一篇论文中提到,研究人员迫切地想实现下列目标的任一个:理论性阐述可学习内容、深入理解经验严谨的实验或者构建一个有高预测精度的工作系统。虽然确定哪些知识值得探究是很主观的,但一旦主题确定,当论文为读者服务时,它对社区最有价值,它能创造基础知识并尽可能清楚地进行阐述。
机器之心
2018/07/26
2770
机器学习近年来之怪现状
计算机视觉研究入门全指南
后台有很多人问如何入门CV,这篇是旧文重发,文章很长,翻译自某外文博客,时间有点久,但道理是相通的,非常值得一读! 这篇文章从一个刚刚开始计算机视觉研究的初学者的角度,详细探讨了这个领域的文献、专家学者、研究组、博客,并重点说明了如何开始研究,如何选择方向,如何看论文、实现代码、调试代码等,并详细说明了研究计算机视觉应该如何学习机器学习等。是初入该领域的博士、学者、欲深入研究的开发者的非常值得详细考察和收藏的参考。 顶级会议和期刊 第一梯队顶级会议: CVPR, ECCV, ICCV, NIPS, IJCA
CV君
2019/12/27
7710
【机器学习】从零实现来理解机器学习算法
从零开始实现机器学习算法的好处 我推广了从零开始实现机器学习算法的观念。 我认为你可以学到很多关于算法是如何工作的。我也认为,作为一名开发者,它提供了一个学习用于机器学习的数学符号、描述以及直觉的桥梁。 在“从零开始实现机器学习算法的好处”这篇文章里,我已经讨论了从零实现机器学习算法的好处。 在那篇文章,我列出的好处如下: 你获取了知识; 它提供了一个起点; 拥有算法和代码的所属权。 在这篇文章中,我对如何利用现有的教程和书籍来缩短这个学习过程表达了一些个人看法。有一些用于初学的丰富资源,但也要堤防一些绊脚
陆勤_数据人网
2018/02/26
9250
【机器学习】从零实现来理解机器学习算法
机器学习问题框架的循序渐进指南
在过去的四年里(在谷歌,以及之前的Comet Labs),我有机会与世界各地的数百家初创公司和公司合作,帮助他们定义他们的ML战略,从问题框架到结束 - 到 -最终实施在生产中运行的ML模型。我们共同致力于部署模型以提高运营效率(例如内部工具,DevOps等),摆脱瓶颈(例如,为客户服务团队提供“神奇的力量”),开发基于ML的产品功能,并构建新产品一起。
iOSDevLog
2019/04/09
4670
机器学习问题框架的循序渐进指南
机器学习为何重要|通过计算一个句子的音节总数证明给你看
大数据文摘编译作品,转载具体要求见文末 翻译校对| 黄文畅 姜范波 Dr. Guo 对信息时代的大多数人来说,如果想通过电脑编程解决一些难题,你只有两种方法:查找式和启发式。 现在,便利的机器学习算
大数据文摘
2018/05/22
5290
[机器学习|理论&实践] 机器学习与生物启发式算法的融合
在现代科技的发展中,机器学习和生物启发式算法的结合为问题解决提供了一种创新的方式。本文将深入研究机器学习与生物启发式算法的融合,通过一个实例项目展示其部署过程,并探讨这一技术在未来的发展方向。
数字扫地僧
2024/01/20
3611
华为 AI 战略第一步:发布基于机器学习的智能网络控制系统 Network Mind
【新智元导读】华为诺亚方舟实验室昨天首次对外公布了 Network Mind——全球网络通信业界首个基于机器学习的网络大脑,实现智能化的网络控制与管理。Network Mind 原型的研发成功将促进机器学习、人工智能技术在通信网络的应用,推动下一代通信网络自动化、智能化的进程。 2016 年 10 月 26 日,华为诺亚方舟实验室对外公布了他们的最新研究成果——全球网络通信业界首个基于机器学习的网络大脑(Network Mind),可以自动检测、准确预测网络流量的变化,智能地实现网络流量的自动控制。 华为利
新智元
2018/03/26
1.3K0
华为 AI 战略第一步:发布基于机器学习的智能网络控制系统 Network Mind
机器学习自学指南
有很多途径来学习机器学习。有丰富的资源:有书籍,有课程可以参与,可以参加比赛,有大量供你使用的工具。在这篇文章中,我想围绕这些活动提出一些你机器学习之旅大致会有的阶段并在你程序员通往到机器学习大师之路上给出一个大概的顺序,以及在各个层次上有哪些可供你利用的资源
xixigiggling
2018/02/07
5790
如何实现机器学习算法
在代码中实现一个机器学习的算法能够使你更加了解该算法以及其工作机理。
CJ
2018/02/02
8820
AI+组合优化 |机器学习顶会ICLR/ICML/NeurIPS'23最新进展-MIP求解篇(附原文源码)
----ICLR、NIPS和ICML是人工智能领域的三个顶级学术会议,以下是它们的介绍:
决小策
2023/12/17
1.4K0
推荐阅读
相关推荐
机器学习的5种“兵法"
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档