Spring Aop原理之Advised接口

通过之前我们介绍的ProxyFactory我们知道,Spring Aop是通过ProxyFactory来创建代理对象的。ProxyFactory在创建代理对象时会委托给DefaultAopProxyFactory.createAopProxy(AdvisedSupport config)DefaultAopProxyFactory内部会分情况返回基于JDK的JdkDynamicAopProxy或基于CGLIB的ObjenesisCglibAopProxy,它俩都实现了Spring的AopProxy接口。AopProxy接口中只定义了一个方法,getProxy()方法,Spring Aop创建的代理对象也就是该接口方法的返回结果。

我们先来看一下基于JDK代理的JdkDynamicAopProxy的getProxy()的逻辑。

    @Overridepublic Object getProxy() {return getProxy(ClassUtils.getDefaultClassLoader());}@Overridepublic Object getProxy(ClassLoader classLoader) {if (logger.isDebugEnabled()) {logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());}Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}

我们可以看到它最终是通过JDK的Proxy来创建的代理,使用的InvocationHandler实现类是它本身,而使用的接口是AopProxyUtils.completeProxiedInterfaces(this.advised)的返回结果。而这个this.advised对象是AdvisedSupport类型,它是ProxyFactory的父类(间接通过ProxyCreatorSupport继承,ProxyFactory的直接父类是ProxyCreatorSupportProxyCreatorSupport的父类是AdvisedSupport),AdvisedSupport的父类是ProxyConfigProxyConfig中包含创建代理对象时的一些配置项信息。以下是AopProxyUtils.completeProxiedInterfaces(this.advised)的内部逻辑。

    public static Class<?>[] completeProxiedInterfaces(AdvisedSupport advised) {Class<?>[] specifiedInterfaces = advised.getProxiedInterfaces();if (specifiedInterfaces.length == 0) {// No user-specified interfaces: check whether target class is an interface.Class<?> targetClass = advised.getTargetClass();if (targetClass != null && targetClass.isInterface()) {specifiedInterfaces = new Class<?>[] {targetClass};}}boolean addSpringProxy = !advised.isInterfaceProxied(SpringProxy.class);boolean addAdvised = !advised.isOpaque() && !advised.isInterfaceProxied(Advised.class);int nonUserIfcCount = 0;if (addSpringProxy) {nonUserIfcCount++;}if (addAdvised) {nonUserIfcCount++;}Class<?>[] proxiedInterfaces = new Class<?>[specifiedInterfaces.length + nonUserIfcCount];System.arraycopy(specifiedInterfaces, 0, proxiedInterfaces, 0, specifiedInterfaces.length);if (addSpringProxy) {proxiedInterfaces[specifiedInterfaces.length] = SpringProxy.class;}if (addAdvised) {proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class;}return proxiedInterfaces;}

我们可以看到其会在!advised.isOpaque() && !advised.isInterfaceProxied(Advised.class)返回true的情况下加上本文的主角Advised接口。isOpaque()ProxyConfig中的一个方法,对应的是opaque属性,表示是否禁止将代理对象转换为Advised对象,默认是false!advised.isInterfaceProxied(Advised.class)表示将要代理的目标对象类没有实现Advised接口,对于我们自己应用的Class来说,一般都不会自己去实现Advised接口的,所以这个通常也是返回true,所以通常创建Aop代理对象时是会创建包含Advised接口的代理对象的,即上述的proxiedInterfaces[proxiedInterfaces.length - 1] = Advised.class会被执行。
前面我们已经提到,JdkDynamicAopProxy创建代理对象应用的InvocationHandler是其自身,所以我们在调用JdkDynamicAopProxy创建的代理对象的任何方法时都将调用JdkDynamicAopProxy实现的InvocationHandler接口的invoke(Object proxy, Method method, Object[] args)方法。该方法实现如下:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MethodInvocation invocation;Object oldProxy = null;boolean setProxyContext = false;TargetSource targetSource = this.advised.targetSource;Class<?> targetClass = null;Object target = null;try {if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {// The target does not implement the equals(Object) method itself.return equals(args[0]);}if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {// The target does not implement the hashCode() method itself.return hashCode();}if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}Object retVal;if (this.advised.exposeProxy) {// Make invocation available if necessary.oldProxy = AopContext.setCurrentProxy(proxy);setProxyContext = true;}// May be null. Get as late as possible to minimize the time we "own" the target,// in case it comes from a pool.target = targetSource.getTarget();if (target != null) {targetClass = target.getClass();}// Get the interception chain for this method.List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// Check whether we have any advice. If we don't, we can fallback on direct// reflective invocation of the target, and avoid creating a MethodInvocation.if (chain.isEmpty()) {// We can skip creating a MethodInvocation: just invoke the target directly// Note that the final invoker must be an InvokerInterceptor so we know it does// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);} else {// We need to create a method invocation...invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed();}// Massage return value if necessary.Class<?> returnType = method.getReturnType();if (retVal != null && retVal == target && returnType.isInstance(proxy) &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {// Special case: it returned "this" and the return type of the method// is type-compatible. Note that we can't help if the target sets// a reference to itself in another returned object.retVal = proxy;} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);}return retVal;}finally {if (target != null && !targetSource.isStatic()) {// Must have come from TargetSource.targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}

其中关于Advised接口方法调用最核心的一句是如下这句。我们可以看到,当我们调用的目标方法是定义自Advised接口时,对应方法的调用将委托给AopUtils.invokeJoinpointUsingReflection(this.advised, method, args)invokeJoinpointUsingReflection方法的逻辑比较简单,是通过Java反射来调用目标方法。在这里invokeJoinpointUsingReflection传递的目标对象正是AdvisedSupport类型的this.advised对象。

    if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {// Service invocations on ProxyConfig with the proxy config...return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);}

AdvisedSupport类是实现了Advised接口的,所以Spring Aop创建了基于Advised接口的代理对象后在调用Advised接口方法时可以把它委托给AdvisedSupport。而我们知道Spring Aop代理对象的创建正是基于AdvisedSupport的配置进行的(配置项主要都定义在AdvisedSupport的父类ProxyConfig类中)。创建代理对象时应用AdvisedSupport,调用Advised接口方法也用同一个实现了Advised接口的AdvisedSupport对象,所以这个过程在Spring Aop内部就可以很好的衔接。接着我们来看一下Advised接口的定义。

public interface Advised extends TargetClassAware {boolean isFrozen();boolean isProxyTargetClass();Class<?>[] getProxiedInterfaces();boolean isInterfaceProxied(Class<?> intf);void setTargetSource(TargetSource targetSource);TargetSource getTargetSource();void setExposeProxy(boolean exposeProxy);boolean isExposeProxy();void setPreFiltered(boolean preFiltered);boolean isPreFiltered();Advisor[] getAdvisors();void addAdvisor(Advisor advisor) throws AopConfigException;void addAdvisor(int pos, Advisor advisor) throws AopConfigException;boolean removeAdvisor(Advisor advisor);void removeAdvisor(int index) throws AopConfigException;int indexOf(Advisor advisor);boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;void addAdvice(Advice advice) throws AopConfigException;void addAdvice(int pos, Advice advice) throws AopConfigException;boolean removeAdvice(Advice advice);int indexOf(Advice advice);String toProxyConfigString();}

Advised接口中定义的方法还是非常多的,通过它我们可以在运行时了解我们的代理对象是基于CGLIB的还是基于JDK代理的;可以了解我们的代理对应应用了哪些Advisor;也可以在运行时给我们的代理对象添加和删除Advisor/Advise。本文旨在描述Spring Aop在创建代理对象时是如何基于Advised接口创建代理的,以及我们能够应用Advised接口做哪些事。文中应用的是Spring创建基于JDK代理对象的过程为示例讲解的,其实基于CGLIB的代理也是一样的。关于CGLIB的代理过程、本文中描述的一些核心类以及本文的核心——Advised接口的接口方法说明等请有兴趣的朋友参考Spring的API文档和相关的源代码。
(注:本文是基于Spring4.1.0所写,Elim写于2017年5月15日)

Spring Aop(十五)——Aop原理之Advised接口相关推荐

  1. Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    <p>这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例.</p> 先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭 ...

  2. 让大学生校招逆袭的Spring三十五问,四万字详解分析(堪称2022年大学生毕业面试指南)

    有人说,"Java程序员都是Spring程序员",我不太赞成这个观点,但是这也可以看出Spring在Java世界里举足轻重的作用. 基础 1.Spring是什么?特性?有哪些模块? ...

  3. 面渣逆袭:Spring三十五问,四万字+五十图详解

    基础 1.Spring是什么?特性?有哪些模块? 一句话概括:Spring 是一个轻量级.非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架. 2003年,一个音乐家Rod Johnson ...

  4. 面渣逆袭:Spring三十五问,四万字+五十图详解 。不要错过

    基础 1.Spring是什么?特性?有哪些模块? Spring Logo 一句话概括:Spring 是一个轻量级.非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的框架. 2003年,一个音乐 ...

  5. 面渣逆袭:Spring三十五问,四万字+五十图详解,建议收藏。

    这节我们来搞定另一个面试必问知识点--Spring. 有人说,"Java程序员都是Spring程序员",老三不太赞成这个观点,但是这也可以看出Spring在Java世界里举足轻重的 ...

  6. java 分布式事务_Java核心知识 Spring原理十五 JPA 原理

    1. 事务 事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity ).一致性 ( Consistency ).隔离性 ( Isolation ) 和持久性 ( Dur ...

  7. Spring三十五问,四万字+五十图详解 建议收藏

    这节我们来搞定另一个面试必问知识点--Spring. 有人说,"Java程序员都是Spring程序员",老三不太赞成这个观点,但是这也可以看出Spring在Java世界里举足轻重的 ...

  8. (转)Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    http://www.ityouknow.com/springboot/2017/09/23/spring-boot-jpa-thymeleaf-curd.html 这篇文章介绍如何使用 Jpa 和 ...

  9. spring boot(十五)spring boot+thymeleaf+jpa增删改查示例

    快速上手 配置文件 pom包配置 pom包里面添加jpa和thymeleaf的相关包引用 <dependency><groupId>org.springframework.bo ...

  10. 【零基础学Java】—继承父类并实现多个接口(二十五)

    [零基础学Java]-继承父类并实现多个接口(二十五) 一.使用接口的注意事项 1️⃣接口是没有静态代码块或者构造方法的 2️⃣一个类的直接父类是唯一的,但是一个类可以同时实现多个接口 3️⃣如果实现 ...

最新文章

  1. 更改centos 5 yum源
  2. MATLAB应用实战系列(七十七)-【图像处理】COVID-19 防疫应用口罩检测
  3. 04-CoreData Stack技术栈堆手动实现
  4. Java集合篇:集合细节:为集合指定初始容量、asList的缺陷、subList的缺陷
  5. Plupload+easyui+springmvc实现批量上传
  6. 字符串匹配的Boyer-Moore算法
  7. MySQL Hardware--FIO压测
  8. Jquery Datatable的使用样例(ssm+bootstrsp框架下)服务器端分页
  9. iisS7 配置SSL 绑定主机头实现多站点访问
  10. centos 程序 mysql_Centos 源码安装 MySQL
  11. q语言 科学计数_3岁宝宝说话结巴,被诊断语言障碍,我用1招让孩子口齿清晰,打脸众人!...
  12. c语言如何调用外部文件的函数调用,keil 中如何调用其他文件的函数
  13. 组内连续三个或三个以上Repeated Measures ANOVA
  14. vue json对象转数组_年薪百万之路--第六十七天 Vue入门
  15. 软件测试工具常用的都有哪些?
  16. 请教一个能在WinPE环境下获取系统相关信息的代码
  17. matlab神经网络应用设计 张德丰,MATLAB神经网络应用设计
  18. 五一劳动节,微信公众号图文应该怎样排版?
  19. 开发 HTML5 小游戏
  20. 银联商务技术开发笔试题目

热门文章

  1. [原创]windows server 2012 AD架构 试验 系列 – 17管理用户AD帐号
  2. linux没有cpufreq目录,Linux内核的cpufreq(变频)机制
  3. windows 截图软件——sharex 截图软件的天花板 并且是免费开源的。
  4. 华为智慧屏鸿蒙20,华为智慧屏SE系列首销 搭载鸿蒙分布式跨屏技术
  5. 《文明的冲突与世界秩序的重建》摘
  6. 内存屏障(Memory Barrier)(一)什么是写屏障?
  7. macos可以升级到指定版本吗_错过Mac OS升级,找不到旧OS版本怎么办?
  8. Tesseract-OCR样本训练方法
  9. Android查电池循环软件,这款软件,让安卓也能查看电池寿命
  10. 解决iText 5.0.1,加入iTextAsian.jar 出现异常 Font 'STSong-Light' with 'UniGB-UCS2-H'