Spring 源码阅读 60:通过 JDK 动态代理或者 CGLIB 创建 AOP 代理对象

简介: 本文分别分析了 Spring 通过 JDK 动态代理和 CGLIB 两种方式创建 AOP 代理对象的过程。至此,Spring AOP 特性中,代理对象创建的全部过程就分析完了。

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 59:确定创建 AOP 代理的方式是 JDK 动态代理还是 CGLIB

概述

上一篇分析了创建代理对象的getProxy方法,以及 Spring 如何选择使用 JDK 动态代理还是 CGLIB 来创建代理对象。本文来分析这两种方式创建代理对象的过程。

通过 JDK 动态代理创建代理对象

首先来看 Spring 通过 JDK 动态代理的方式创建 AOP 代理对象的过程,也就是 JdkDynamicAopProxy 的getProxy方法。

// org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader)@OverridepublicObjectgetProxy(@NullableClassLoaderclassLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: "+this.advised.getTargetSource());
   }
Class<?>[] proxiedInterfaces=AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
returnProxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

首先,会通过 AopProxyUtils 的completeProxiedInterfaces方法,获取要代理的接口数组。调用方法时,传入的参数this.advised成员变量,是在 JdkDynamicAopProxy 的构造方法中初始化的。

// org.springframework.aop.framework.JdkDynamicAopProxy#JdkDynamicAopProxypublicJdkDynamicAopProxy(AdvisedSupportconfig) throwsAopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length==0&&config.getTargetSource() ==AdvisedSupport.EMPTY_TARGET_SOURCE) {
thrownewAopConfigException("No advisors and no TargetSource specified");
   }
this.advised=config;
}

这里初始化了advised成员变量的值,是参数中传入的config,而这个config就是创建 JdkDynamicAopProxy 时候的 ProxyFactory 对象。completeProxiedInterfaces方法的作用是生成完整的代理接口列表,其中包括 ProxyFactory 中配置的 Bean 类型实现的所有接口,还会增加 SpringProxy 和 Advised 接口,第二个参数传入了true,还会增加 DecoratingProxy 接口。

接下来调用了findDefinedEqualsAndHashCodeMethods方法。我们看一下这个方法的源码。

// org.springframework.aop.framework.JdkDynamicAopProxy#findDefinedEqualsAndHashCodeMethodsprivatevoidfindDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
for (Class<?>proxiedInterface : proxiedInterfaces) {
Method[] methods=proxiedInterface.getDeclaredMethods();
for (Methodmethod : methods) {
if (AopUtils.isEqualsMethod(method)) {
this.equalsDefined=true;
         }
if (AopUtils.isHashCodeMethod(method)) {
this.hashCodeDefined=true;
         }
if (this.equalsDefined&&this.hashCodeDefined) {
return;
         }
      }
   }
}

从代码中可以看出,这里会遍历所有的代理接口中声明的所有方法,只要这些方法中有equalshashCode方法,则将对应的成员变量equalsDefinedhashCodeDefined的值设置为true。这一步骤的作用尚不知道,不过既然这里这两个成员变量的值可能会在此处被修改,那么之后肯定还会遇到,到时候我们再分析它的作用。

最后一步就是代理对象的创建,调用了 JDK 动态代理的方法,创建代理对象并返回。

Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

这个方法的内容,已经不属于 Spring 框架的范畴,就不深入分析了,我们找到这个方法的定义,大概了解一下这几个参数。

@CallerSensitivepublicstaticObjectnewProxyInstance(ClassLoaderloader,
Class<?>[] interfaces,
InvocationHandlerh) {
// 省略方法体中的代码}

第一个参数loader是创建代理的类加载器,第二个参数interfaces是代理对象要实现的所有接口,也就是上一步我们得到的代理接口列表。这两个参数比较好理解。

第三个参数的类型是 InvocationHandler,这个参数方法调用的处理器,也就是对代理对象的方法调用回分派到这个 InvocationHandler 对象中,在上一步中通过newProxyInstance创建代理对象的时候,这个参数传入的是this,也就是在 DefaultAopProxyFactory 中创建的 JdkDynamicAopProxy 对象,也就是说,对代理对象的方法调用,都是由它来完成的。

image.png

从上面的接口中,可以看到 JdkDynamicAopProxy 是实现了 InvocationHandler 接口的,主要的处理逻辑在 InvocationHandler 接口定义的invoke方法中。

通过 CGLIB 创建代理对象

下面接着分析 CGLIB 代理对象的创建,也就是 ObjenesisCglibAopProxy 的getProxy方法,在此之前,我们先看一下它的构造方法,看看代理对象被创建之前,创建 ObjenesisCglibAopProxy 的时候执行了哪些工作。

// org.springframework.aop.framework.ObjenesisCglibAopProxy#ObjenesisCglibAopProxypublicObjenesisCglibAopProxy(AdvisedSupportconfig) {
super(config);
}

只是调用了父类的构造方法,我们再跟着找到父类的构造方法。

// org.springframework.aop.framework.CglibAopProxy#CglibAopProxypublicCglibAopProxy(AdvisedSupportconfig) throwsAopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length==0&&config.getTargetSource() ==AdvisedSupport.EMPTY_TARGET_SOURCE) {
thrownewAopConfigException("No advisors and no TargetSource specified");
   }
this.advised=config;
this.advisedDispatcher=newAdvisedDispatcher(this.advised);
}

这里初始化了两个成员变量,初始化advised的方式跟 JdkDynamicAopProxy 一样,而advisedDispatcher成员变量的初始化,是通过其构造方法创建了一个 AdvisedDispatcher 的对象。

接下来再分析getProxy方法,这个方法的实现也是在父类 CglibAopProxy 中,我们找到方法的代码。

image.png

方法的代码比较多,我们分步来分析,方法的逻辑都在try语句块中。

Class<?>rootClass=this.advised.getTargetClass();

首先要做的就是获取到要代理的目标类型。

Class<?>proxySuperClass=rootClass;
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
proxySuperClass=rootClass.getSuperclass();
Class<?>[] additionalInterfaces=rootClass.getInterfaces();
for (Class<?>additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
   }
}

然后获取目标类型实现的接口,并将它们添加到advised成员变量配置的接口列表中。

// Configure CGLIB Enhancer...Enhancerenhancer=createEnhancer();
if (classLoader!=null) {
enhancer.setClassLoader(classLoader);
if (classLoaderinstanceofSmartClassLoader&&         ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
   }
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(newClassLoaderAwareGeneratorStrategy(classLoader));
Callback[] callbacks=getCallbacks(rootClass);
Class<?>[] types=newClass<?>[callbacks.length];
for (intx=0; x<types.length; x++) {
types[x] =callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks call aboveenhancer.setCallbackFilter(newProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);

接下来就是创建 Enhancer,并设置它的一些属性。Enhancer 字节码增强器是 CGLIB 创建代理对象的一个重要的类。这里设置了类加载器、父类(也就是代理的目标类型)、回调过滤器、回调类型数组等。

returncreateProxyClassAndInstance(enhancer, callbacks);

最后,通过createProxyClassAndInstance方法创建了代理对象,我们再进入createProxyClassAndInstance方法看一下。

image.png

这里需要注意,因为之前创建的 AopProxy 对象是 ObjenesisCglibAopProxy 类型,因此,ObjenesisCglibAopProxy 类型中实现了这个方法,我们应该看 ObjenesisCglibAopProxy 中的实现逻辑。

其中比较关键的逻辑,是通过enhancer.createClass()来创建代理类型,以及通过objenesis.newInstance来创建代理对象实例,不过,这些都属于 CGLIB 的内容,不在 Spring 框架的范畴之内,这里不做介绍了。

总结

本文分别分析了 Spring 通过 JDK 动态代理和 CGLIB 两种方式创建 AOP 代理对象的过程。至此,Spring AOP 特性中,代理对象创建的全部过程就分析完了。


目录
相关文章
|
12天前
|
JavaScript Java 数据安全/隐私保护
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
22 0
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(2)
|
19天前
|
Java Spring 容器
解读spring5源码中实例化单例bean的调用链
解读spring5源码中实例化单例bean的调用链
|
12天前
|
JavaScript Java 关系型数据库
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码(1)
基于SpringBoot+Vue毕业生信息招聘平台系统【源码+论文+演示视频+包运行成功】_基于spring vue的校园招聘系统源码
15 0
|
11天前
|
IDE Oracle Java
day4:JDK、IntelliJ IDEA的安装和环境变量配置
【7月更文挑战第4天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 0
|
7天前
|
存储 Ubuntu Java
【Linux】已解决:Ubuntu虚拟机安装Java/JDK
【Linux】已解决:Ubuntu虚拟机安装Java/JDK
13 1
|
19天前
|
Linux 测试技术 开发工具
CentOS Linux 8使用阿里源(安装jdk11、git测试)
CentOS Linux 8使用阿里源(安装jdk11、git测试)
97 1
|
25天前
|
Java 关系型数据库 MySQL
杨校老师课堂之Java项目部署到云端服务器之安装MySQL、Jdk、Tomcat
杨校老师课堂之Java项目部署到云端服务器之安装MySQL、Jdk、Tomcat
28 0
杨校老师课堂之Java项目部署到云端服务器之安装MySQL、Jdk、Tomcat
|
1月前
|
Oracle Java 关系型数据库
玩客云安装Armbian和部署jdk环境
该文介绍了在玩客云设备上安装Armbian系统和Java SDK的步骤。首先,需要准备玩客云设备、Armbian镜像文件和USB工具。然后,通过短接点刷入Armbian系统,并通过SSH访问。接着,从可信源下载Java SDK,将其解压并移动到合适目录,编辑环境变量使其生效。最后验证Java安装成功。注意选择兼容版本并备份数据。内容涵盖了ROM开发相关技术。
|
1月前
|
Oracle Java 关系型数据库
Java入门——开发环境、入门程序(搭建Java开发环境、安装JDK 验证、JDK、编写代码、编译代码、运行代码)
Java入门——开发环境、入门程序(搭建Java开发环境、安装JDK 验证、JDK、编写代码、编译代码、运行代码)
35 3
|
1月前
|
Java
树莓派安装java jdk8
树莓派安装java jdk8
57 5