1.简介

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

2.背景知识

2.1 proxy-target-class

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

<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 中创建代理的方法声明。如下:

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

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

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

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

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

目标类定义:

public interface UserService {

    void save(User user);

    void update(User user);
} public class UserServiceImpl implements UserService { @Override
public void save(User user) {
System.out.println("save user info");
} @Override
public void update(User user) {
System.out.println("update user info");
}
}

代理创建者定义:

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;
} @Override
public Object getProxy() {
Class<?> clazz = target.getClass();
// 生成代理对象
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
} @Override
public 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 方法中的代理逻辑主要用于记录目标方法的调用时间,和结束时间。下面写点测试代码简单验证一下,如下:

public class JdkProxyCreatorTest {

    @Test
public 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式坦克:

public class Tank59 {

    void run() {
System.out.println("极速前行中....");
} void shoot() {
System.out.println("轰...轰...轰...轰...");
}
}

CGLIB 代理创建者

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;
} @Override
public Object getProxy() {
Enhancer enhancer = new Enhancer();
// 设置代理类的父类
enhancer.setSuperclass(target.getClass());
// 设置代理逻辑
enhancer.setCallback(methodInterceptor);
// 创建代理对象
return enhancer.create();
}
}

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

public class TankRemanufacture implements MethodInterceptor {

    @Override
public 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);
}
}

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

public class CglibProxyCreatorTest {

    @Test
public 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 这个接口的定义,如下:

public interface AopProxy {

    /** 创建代理对象 */
Object getProxy(); Object getProxy(ClassLoader classLoader);
}

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

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

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 转为 Advisor
Advisor[] 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 实现类对象的过程,如下:

protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
} public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override
public 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 继承自 CglibAopProxy
return new ObjenesisCglibAopProxy(config);
}
else {
// 创建 JDK 动态代理
return new JdkDynamicAopProxy(config);
}
}
}

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

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 源码分析文章列表

Ⅰ. 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 原理探秘 - 容器的创建过程

本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处

作者:田小波

本文同步发布在我的个人博客:http://www.tianxiaobo.com


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

Spring AOP 源码分析 - 创建代理对象的更多相关文章

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

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

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

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

  3. Spring AOP 源码分析系列文章导读

    1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...

  4. Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理

    AOP代理对象的创建 AOP相关的代理对象的创建主要在applyBeanPostProcessorsBeforeInstantiation方法实现: protected Object applyBea ...

  5. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  6. 5.2 spring5源码--spring AOP源码分析二--切面的配置方式

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

  7. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

  8. spring aop 源码分析(三) @Scope注解创建代理对象

    一.源码环境的搭建: @Component @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON,proxyMode = ScopedP ...

  9. spring aop 源码分析(二) 代理方法的执行过程分析

    在上一篇aop源码分析时,我们已经分析了一个bean被代理的详细过程,参考:https://www.cnblogs.com/yangxiaohui227/p/13266014.html 本次主要是分析 ...

随机推荐

  1. ueditor 上传图片

    ueditor在配置图片,附件上传  首先,是以web项目为基础的,需要安装好eclipse以及tomcat 其次,需要下载ueditor(可去百度官网下载 http://ueditor.baidu. ...

  2. NOIP2016原题终结测试(2017081801)

    NOIP2016还有几道原题没有写掉,今天就一并布置掉. 答案的问题,有部分会先放到NOIP题解中,是单独发布的. 最后会汇总放在答案中,各位不要急. 还有,后期会有原创题测试,这个不急,反正11月才 ...

  3. LD_LIBRARY_PATH

    LD_LIBRARY_PATH是Linux环境变量名,该环境变量主要用于指定查找共享库(动态链接库)时除了默认路径之外的其他路径. 在linux下可以用export命令来设置这个值,比如 在linux ...

  4. centos6 搭建nginx实现负载均衡

    一.安装nginx 1)准备2台服务器,环境一样,同时执行 rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-6.noarch.r ...

  5. php获取响应状态码

    $ch = curl_init('http://www.jb51.net'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_exec($ch); ...

  6. __getitem__()、__setitem__()与__delitem__()

    # 如果想要运用[]取值,可以实现__getitem__() # 想要运用[]设值,可以实现__setitem__() # 若想通过del与[]来删除,可以实现__delitem__() class ...

  7. idea关于tab的设置

    新手使用,一不小心tab显示在右面了,这不学习下给搞正常点. settings===>Editor=====>Editor Tabs; Palacement设置的是tab显示的部位: Ta ...

  8. 以太坊虚拟机(EVM)

    转载链接:https://ethfans.org/posts/solidity-chapter1-introduciton-to-smart-contracts 概括总览: 以太坊虚拟机(EVM)是以 ...

  9. 链家web前端面试

    共有三轮面试,每个面试官的第一个问题都是:介绍一个你觉着比较出彩的项目 第一轮面试: 因为公司项目没什么亮点,很传统的pc端,美女面试官就说让讲一下我用react的私人项目; 问了很多都是关于reac ...

  10. 5.Vue临时上传文件夹

    1.在项目目录中,通过npm install multiparty进行安装必要组件npm install multiparty --save-dev 2.app.js中添加app.use(bodyPa ...