1.简介

在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的。现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 bean 的某些方法前后。与筛选合适的通知器相比,创建代理对象的过程则要简单不少,本文所分析的源码不过100行,相对比较简单。在接下里的章节中,我将会首先向大家介绍一些背景知识,然后再去分析源码。那下面,我们先来了解一下背景知识。

2.背景知识

2.1 proxy-target-class

在 Spring AOP 配置中,proxy-target-class 属性可影响 Spring 生成的代理对象的类型。以 XML 配置为例,可进行如下配置:

1
2
3
4
5
6
7
<aop:aspectj-autoproxy proxy-target-class="true"/><aop:config proxy-target-class="true"><aop:aspect id="xxx" ref="xxxx"><!-- 省略 --></aop:aspect>
</aop:config>

如上,默认情况下 proxy-target-class 属性为 false。当目标 bean 实现了接口时,Spring 会基于 JDK 动态代理为目标 bean 创建代理对象。若未实现任何接口,Spring 则会通过 CGLIB 创建代理。而当 proxy-target-class 属性设为 true 时,则会强制 Spring 通过 CGLIB 的方式创建代理对象,即使目标 bean 实现了接口。

关于 proxy-target-class 属性的用途这里就说完了,下面我们来看看两种不同创建动态代理的方式。

2.2 动态代理

2.2.1 基于 JDK 的动态代理

基于 JDK 的动态代理主要是通过 JDK 提供的代理创建类 Proxy 为目标对象创建代理,下面我们来看一下 Proxy 中创建代理的方法声明。如下:

1
2
3
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

简单说一下上面的参数列表:

  • loader - 类加载器
  • interfaces - 目标类所实现的接口列表
  • h - 用于封装代理逻辑

JDK 动态代理对目标类是有一定要求的,即要求目标类必须实现了接口,JDK 动态代理只能为实现了接口的目标类生成代理对象。至于 InvocationHandler,是一个接口类型,定义了一个 invoke 方法。使用者需要实现该方法,并在其中封装代理逻辑。

关于 JDK 动态代理的介绍,就先说到这。下面我来演示一下 JDK 动态代理的使用方式,如下:

目标类定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface UserService {void save(User user);void update(User user);
}public class UserServiceImpl implements UserService {@Overridepublic void save(User user) {System.out.println("save user info");}@Overridepublic void update(User user) {System.out.println("update user info");}
}

代理创建者定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface ProxyCreator {Object getProxy();
}public class JdkProxyCreator implements ProxyCreator, InvocationHandler {private Object target;public JdkProxyCreator(Object target) {assert target != null;Class<?>[] interfaces = target.getClass().getInterfaces();if (interfaces.length == 0) {throw new IllegalArgumentException("target class don`t implement any interface");}this.target = target;}@Overridepublic Object getProxy() {Class<?> clazz = target.getClass();// 生成代理对象return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method start");// 调用目标方法Object retVal = method.invoke(target, args);System.out.println(System.currentTimeMillis() + " - " + method.getName() + " method over");return retVal;}
}

如上,invoke 方法中的代理逻辑主要用于记录目标方法的调用时间,和结束时间。下面写点测试代码简单验证一下,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class JdkProxyCreatorTest {@Testpublic void getProxy() throws Exception {ProxyCreator proxyCreator = new JdkProxyCreator(new UserServiceImpl());UserService userService = (UserService) proxyCreator.getProxy();System.out.println("proxy type = " + userService.getClass());System.out.println();userService.save(null);System.out.println();userService.update(null);}
}

测试结果如下:

如上,从测试结果中。我们可以看出,我们的代理逻辑正常执行了。另外,注意一下 userService 指向对象的类型,并非是 xyz.coolblog.proxy.UserServiceImpl,而是 com.sun.proxy.$Proxy4。

关于 JDK 动态代理,这里先说这么多。下一节,我来演示一下 CGLIB 动态代理,继续往下看吧。

2.2.2 基于 CGLIB 的动态代理

当我们要为未实现接口的类生成代理时,就无法使用 JDK 动态代理了。那么此类的目标对象生成代理时应该怎么办呢?当然是使用 CGLIB 了。在 CGLIB 中,代理逻辑是封装在 MethodInterceptor 实现类中的,代理对象则是通过 Enhancer 类的 create 方法进行创建。下面我来演示一下 CGLIB 创建代理对象的过程,如下:

本节的演示环节,打算调侃(无贬低之意)一下59式坦克,这是我们国家大量装备过的一款坦克。59式坦克有很多种改款,一般把改款统称为59改,59改这个梗也正是源于此。下面我们先来一览59式坦克的风采:


图片来源:百度图片搜索

下面我们的工作就是为咱们的 59 创建一个代理,即 59改。好了,开始我们的魔改吧。

目标类,59式坦克:

1
2
3
4
5
6
7
8
9
10
public class Tank59 {void run() {System.out.println("极速前行中....");}void shoot() {System.out.println("轰...轰...轰...轰...");}
}

CGLIB 代理创建者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CglibProxyCreator implements ProxyCreator {private Object target;private MethodInterceptor methodInterceptor;public CglibProxyCreator(Object target, MethodInterceptor methodInterceptor) {assert (target != null && methodInterceptor != null);this.target = target;this.methodInterceptor = methodInterceptor;}@Overridepublic Object getProxy() {Enhancer enhancer = new Enhancer();// 设置代理类的父类enhancer.setSuperclass(target.getClass());// 设置代理逻辑enhancer.setCallback(methodInterceptor);// 创建代理对象return enhancer.create();}
}

方法拦截器 - 坦克再制造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TankRemanufacture implements MethodInterceptor {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if (method.getName().equals("run")) {System.out.println("正在重造59坦克...");System.out.println("重造成功,已获取 ✨59改 之 超音速飞行版✨");System.out.print("已起飞,正在突破音障。");methodProxy.invokeSuper(o, objects);System.out.println("已击落黑鸟 SR-71,正在返航...");return null;}return methodProxy.invokeSuper(o, objects);}
}

好了,下面开始演示,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CglibProxyCreatorTest {@Testpublic void getProxy() throws Exception {ProxyCreator proxyCreator = new CglibProxyCreator(new Tank59(), new TankRemanufacture());Tank59 tank59 = (Tank59) proxyCreator.getProxy();System.out.println("proxy class = " + tank59.getClass() + "\n");tank59.run();System.out.println();System.out.print("射击测试:");tank59.shoot();}
}

测试结果如下:

如上,“极速前行中…” 和 “轰…轰…轰…轰…” 这两行字符串是目标对象中的方法打印出来的,其他的则是由代理逻辑打印的。由此可知,我们的代理逻辑生效了。

好了,最后我们来看一下,经过魔改后的 59,也就是超音速59改的效果图:


图片来源:未知

本节用59式坦克举例,仅是调侃,并无恶意。作为年轻的一代,我们应感谢那些为国防事业做出贡献的科技人员们。没有他们贡献,我们怕是不会有像今天这样安全的环境了(尽管不完美)。

到此,背景知识就介绍完了。下一章,我将开始分析源码。源码不是很长,主逻辑比较容易懂,所以一起往下看吧。

3.源码分析

为目标 bean 创建代理对象前,需要先创建 AopProxy 对象,然后再调用该对象的 getProxy 方法创建实际的代理类。我们先来看看 AopProxy 这个接口的定义,如下:

1
2
3
4
5
6
7
public interface AopProxy {/** 创建代理对象 */Object getProxy();Object getProxy(ClassLoader classLoader);
}

在 Spring 中,有两个类实现了 AopProxy,如下:

Spring 在为目标 bean 创建代理的过程中,要根据 bean 是否实现接口,以及一些其他配置来决定使用 AopProxy 何种实现类为目标 bean 创建代理对象。下面我们就来看一下代理创建的过程,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);/** 默认配置下,或用户显式配置 proxy-target-class = "false" 时,* 这里的 proxyFactory.isProxyTargetClass() 也为 false*/if (!proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {/** 检测 beanClass 是否实现了接口,若未实现,则将 * proxyFactory 的成员变量 proxyTargetClass 设为 true*/ evaluateProxyInterfaces(beanClass, proxyFactory);}}// specificInterceptors 中若包含有 Advice,此处将 Advice 转为 AdvisorAdvisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 创建代理return proxyFactory.getProxy(getProxyClassLoader());
}public Object getProxy(ClassLoader classLoader) {// 先创建 AopProxy 实现类对象,然后再调用 getProxy 为目标 bean 创建代理对象return createAopProxy().getProxy(classLoader);
}

getProxy 这里有两个方法调用,一个是调用 createAopProxy 创建 AopProxy 实现类对象,然后再调用 AopProxy 实现类对象中的 getProxy 创建代理对象。这里我们先来看一下创建 AopProxy 实现类对象的过程,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected final synchronized AopProxy createAopProxy() {if (!this.active) {activate();}return getAopProxyFactory().createAopProxy(this);
}public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {/** 下面的三个条件简单分析一下:**   条件1:config.isOptimize() - 是否需要优化,这个属性没怎么用过,*         细节我不是很清楚*   条件2:config.isProxyTargetClass() - 检测 proxyTargetClass 的值,*         前面的代码会设置这个值*   条件3:hasNoUserSuppliedProxyInterfaces(config) *         - 目标 bean 是否实现了接口*/if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}// 创建 CGLIB 代理,ObjenesisCglibAopProxy 继承自 CglibAopProxyreturn new ObjenesisCglibAopProxy(config);}else {// 创建 JDK 动态代理return new JdkDynamicAopProxy(config);}}
}

如上,DefaultAopProxyFactory 根据一些条件决定生成什么类型的 AopProxy 实现类对象。生成好 AopProxy 实现类对象后,下面就要为目标 bean 创建代理对象了。这里以 JdkDynamicAopProxy 为例,我们来看一下,该类的 getProxy 方法的逻辑是怎样的。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Object getProxy() {return getProxy(ClassUtils.getDefaultClassLoader());
}public 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, true);findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);// 调用 newProxyInstance 创建代理对象return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

如上,请把目光移至最后一行有效代码上,会发现 JdkDynamicAopProxy 最终调用 Proxy.newProxyInstance 方法创建代理对象。到此,创建代理对象的整个过程也就分析完了,不知大家看懂了没。好了,关于创建代理的源码分析,就先说到这里吧。

4.总结

本篇文章对 Spring AOP 创建代理对象的过程进行了较为详细的分析,并在分析源码前介绍了相关的背景知识。总的来说,本篇文章涉及的技术点不是很复杂,相信大家都能看懂。限于个人能力,若文中有错误的地方,欢迎大家指出来。好了,本篇文章到此结束,谢谢阅读。

参考

  • 《Spring 源码深度解析》- 郝佳

附录:Spring 源码分析文章列表

Ⅰ. IOC

更新时间 标题
2018-05-30 Spring IOC 容器源码分析系列文章导读
2018-06-01 Spring IOC 容器源码分析 - 获取单例 bean
2018-06-04 Spring IOC 容器源码分析 - 创建单例 bean 的过程
2018-06-06 Spring IOC 容器源码分析 - 创建原始 bean 对象
2018-06-08 Spring IOC 容器源码分析 - 循环依赖的解决办法
2018-06-11 Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
2018-06-11 Spring IOC 容器源码分析 - 余下的初始化工作

Ⅱ. AOP

更新时间 标题
2018-06-17 Spring AOP 源码分析系列文章导读
2018-06-20 Spring AOP 源码分析 - 筛选合适的通知器
2018-06-20 Spring AOP 源码分析 - 创建代理对象
2018-06-22 Spring AOP 源码分析 - 拦截器链的执行过程

Ⅲ. MVC

更新时间 标题
2018-06-29 Spring MVC 原理探秘 - 一个请求的旅行过程
2018-06-30 Spring MVC 原理探秘 - 容器的创建过程
  • 本文链接: https://www.tianxiaobo.com/2018/06/20/Spring-AOP-源码分析-创建代理对象/

http://www.tianxiaobo.com/2018/06/20/Spring-AOP-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E5%88%9B%E5%BB%BA%E4%BB%A3%E7%90%86%E5%AF%B9%E8%B1%A1/

Spring AOP 源码分析 - 创建代理对象相关推荐

  1. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  2. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  3. spring AOP源码分析(一)

    spring AOP源码分析(一) 对于springAOP的源码分析,我打算分三部分来讲解:1.配置文件的解析,解析为BeanDefination和其他信息然后注册到BeanFactory中:2.为目 ...

  4. 一步一步手绘Spring AOP运行时序图(Spring AOP 源码分析)

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 一步一步手绘Spring IOC运行时序图一(Spring 核心容器 IOC初始化过程) 一步一步手绘Spring IOC运行时序图二(基于XM ...

  5. 【Spring】Spring AOP源码分析-导读(一)

    文章目录 1.简介 2.AOP 原理 3.AOP 术语及相应的实现 3.1 连接点 - Joinpoint 3.2 切点 - Pointcut 3.3 通知 - Advice 3.4 切面 - Asp ...

  6. Spring AOP源码分析(四)Spring AOP的JDK动态代理

    2019独角兽企业重金招聘Python工程师标准>>> 本篇文章将会介绍上一个例子中的源码执行情况,从中熟悉整个SpringAOP的一些概念和接口设计. 首先整个SpringAOP的 ...

  7. Spring AOP源码分析(七)ProxyFactoryBean介绍

    2019独角兽企业重金招聘Python工程师标准>>> 这篇文章里面就要说说Spring自己的AOP,搞清楚哪种方式是Spring自己实现的AOP,哪种方式是Spring引入aspe ...

  8. Spring AOP源码分析(八)SpringAOP要注意的地方

    2019独角兽企业重金招聘Python工程师标准>>> SpringAOP要注意的地方有很多,下面就举一个,之后想到了再列出来: (1)SpringAOP对于最外层的函数只拦截pub ...

  9. Spring AOP源码分析(六)Spring AOP配置的背后

    本篇文章主要对Spring AOP配置背后进行了哪些事情做下说明.还是如上类似的工程,在xml中AOP拦截配置如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < ...

最新文章

  1. tcpdump移植和使用
  2. x_html语言名词解释,第2章++XHTML标记语言(97页)-原创力文档
  3. 下一代对话系统中的关键技术(上篇)
  4. 学习笔记:ORACLE 性能优化求生指南
  5. 如何在论文中自动生成标准的参考文献格式
  6. java圆形泳池问题_Java实现 LeetCode 778 水位上升的泳池中游泳(二分+DFS)
  7. Oracle 初始化参数文件pfile和spfile
  8. mac 安装mysql 找不到_mac安装mysql遇到的坑
  9. 基于 Java 机器学习自学笔记 (第60天:过去十日的总结)
  10. 技术经济与企业管理复习知识点总结
  11. typescript工程_使用TypeScript构建游戏。 工程图网格2/5
  12. STM32F407使用串口3获取攀腾G7的PM2.5数据
  13. Thunk,thunk,thunk
  14. javascript高级编程学习
  15. YAML 语法快速入门(一)
  16. 1758 生成交替二进制字符串的最少操作数
  17. linux 锁屏 代码,Android锁屏与解屏相关代码分析
  18. IDEA中使用JUnit5(单元测试框架)
  19. 网易云音乐不能加载音乐 解决办法
  20. PMM2:迟到的礼物-2.8.0版本发布

热门文章

  1. mysql 字符串的处理
  2. The evolution of cluster scheduler architectures--转
  3. spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件)
  4. Active Object 并发模式在 Java 中的应用--转载
  5. 使用navicat 11 出现不能返回存储过程结果的问题
  6. Java SE 6 新特性: 编译器 API
  7. 清除WebLogic8.1缓存
  8. Visual Studio 2013开发 mini-filter driver step by step (11) driver 签名
  9. Shell遍历hadoop目录的批量操作
  10. linux shell mysql备份_linux shell 备份mysql 数据库