Spring在程序运行期,就能帮助我们把切面中的代码织入Bean的方法内,让开发者能无感知地在容器对象方法前后随心添加相应处理逻辑,所以AOP其实就是个代理模式。

但凡是代理,由于代码不可直接阅读,也是初级程序员们 bug 的重灾区。

1 案例

某游戏系统,含负责点券充值的类CouponService,它含有一个充值方法deposit():

deposit()会使用微信支付充值。因此在这个方法中,加入pay()。

由于微信支付是第三方接口,需记录接口调用时间。

引入 @Around 增强 ,分别记录在pay()方法执行前后的时间,并计算pay()执行耗时。

Controller:

访问接口,会发现这段计算时间的切面并没有执行到,输出日志如下:

切面类明明定义了切面对应方法,但却没执行到。说明在类的内部,通过this调用的方法,不会被AOP增强。

2 源码解析

this对应的对象就是一个普通CouponService对象:

而在Controller层中自动装配的CouponService对象:

是个被Spring增强过的Bean,所以执行deposit()时,会执行记录接口调用时间的增强操作。而this对应的对象只是一个普通的对象,并无任何额外增强。

为什么this引用的对象只是一个普通对象?

要从Spring AOP增强对象的过程来看。

实现

AOP的底层是动态代理,创建代理的方式有两种:

JDK方式

只能对实现了接口的类生成代理,不能针对普通类

CGLIB方式

可以针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,来实现代理对象。

针对非Spring Boot程序,除了添加相关AOP依赖项外,还会使用 @EnableAspectJAutoProxy 开启AOP功能。

这个注解类引入AspectJAutoProxyRegistrar,它通过实现ImportBeanDefinitionRegistrar接口完成AOP相关Bean准备工作。

现在来看下创建代理对象的过程。先来看下调用栈:

创建代理对象的时机

创建一个Bean时

创建的的关键工作由AnnotationAwareAspectJAutoProxyCreator完成

AnnotationAwareAspectJAutoProxyCreator

一种BeanPostProcessor。所以它的执行是在完成原始Bean构建后的初始化Bean(initializeBean)过程中

AbstractAutoProxyCreator#postProcessAfterInitialization

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {

if (bean != null) {

Object cacheKey = getCacheKey(bean.getClass(), beanName);

if (this.earlyProxyReferences.remove(cacheKey) != bean) {

return wrapIfNecessary(bean, beanName, cacheKey);

}

}

return bean;

}

关键方法wrapIfNecessary:在需要使用AOP时,它会把创建的原始Bean对象wrap成代理对象,作为Bean返回。

AbstractAutoProxyCreator#wrapIfNecessary

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略非关键代码
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
// 省略非关键代码
}

createProxy

创建代理对象的关键:

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
// ...
// 1. 创建一个代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
// 2. 将通知器(advisors)、被代理对象等信息加入到代理工厂
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
// ...
// 3. 通过代理工厂获取代理对象
return proxyFactory.getProxy(getProxyClassLoader());
}

经过这样一个过程,一个代理对象就被创建出来了。我们从Spring中获取到的对象都是这个代理对象,所以具有AOP功能。而之前直接使用this引用到的只是一个普通对象,自然也就没办法实现AOP的功能了。

3 修正

经过前面分析可知,只有引用的是被 动态代理 所创对象,才能被Spring增强,实现期望的AOP功能。

那得怎么处理对象,才具备这样的条件?

被@Autowired注解

通过 @Autowired,在类的内部,自己引用自己:

直接从AopContext获取当前Proxy

AopContext,就是通过一个ThreadLocal来将Proxy和线程绑定起来,这样就可以随时拿出当前线程绑定的Proxy。

使用该方案有个前提,需要在 @EnableAspectJAutoProxy 加配置项 exposeProxy = true ,表示将代理对象放入到ThreadLocal,这才可以直接通过

AopContext.currentProxy()

获取到,否则报错:

于是修改代码:

勿忘修改EnableAspectJAutoProxy 的 exposeProxy属性:

原文链接:https://blog.csdn.net/qq_33589510/article/details/120387044

阿里四面:你知道Spring AOP创建Proxy的过程吗?的更多相关文章

  1. 面试问烂的 Spring AOP 原理、SpringMVC 过程(求求你别问了)

    Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以.但今天笔者带大家一起深入浅出源码,看看他的原理.以期让印象更加深刻,面试的时候游刃有余. Sp ...

  2. Spring AOP 创建增强类

    AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5种类型的增强:     1)前置增强:org.springframework.aop.BeforeAd ...

  3. Spring AOP 创建切面

        增强被织入到目标类的所有方法中,但是如果需要有选择性的织入到目标类某些特定的方法中时,就需要使用切点进行目标连接点的定位.增强提供了连接点方位信息:如织入到方法前面.后面等,而切点进一步描述织 ...

  4. Spring AOP 的proxy详解

    spring 提供了多种不同的方案实现对 bean 的 aop proxy, 包括 ProxyFactoryBean, 便利的 TransactionProxyFactoryBean 以及 AutoP ...

  5. Spring AOP创建Throwdvice实例

    1.异常发生的时候,通知某个服务对象做处理 2.实现throwsAdvice接口 接口实现: public interface IHello { public void sayHello(String ...

  6. Spring AOP创建AroundAdvice实例

    AroundAdvice 1.在方法之前和之后来执行相应的操作 2.实现MethodInterceptor接口 接口文件: public interface IHello { public void ...

  7. Spring AOP创建BeforeAdvice和AfterAdvice实例

    BeforeAdvice 1.会在目标对象的方法执行之前被调用. 2.通过实现MethodBeforeAdvice接口来实现. 3.该接口中定义了一个方法即before方法,before方法会在目标对 ...

  8. Spring AOP 创建Advice 定义pointcut、advisor

    前面定义的advice都是直接植入到代理接口的执行之前和之后,或者在异常发生时,事实上,还可以对植入的时机定义的更细. Pointcut定义了advice的应用时机,在Spring中pointcutA ...

  9. Spring AOP 创建Advice 基于Annotation

    public interface IHello { public void sayHello(String str); } public class Hello implements IHello { ...

随机推荐

  1. Linux进程管理之基本指令

    目录 基本介绍 显示系统执行的进程 指令 ps - aux 常用选项 每行栏目的含义 查看父进程 终止进程 相关指令 实用案例 踢掉某个非法登录用户 终止远程登录服务sshd,在适当的时候再次重启ss ...

  2. 在本地开启了代理,postman可以正常发起外部请求,但Java代码却请求失败,已解决

    在本地开启了代理,postman可以正常发起外部请求,但Java代码却请求失败,已解决 现象:开了vpn访问公司服务器,postman可以正常发起服务器请求,但是java代码请求失败,连接超时.包括在 ...

  3. 【】JSON介绍

    0.说明 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 易于人阅读和编写. 同时也易于机器解析和生成. 它是基于JavaScript Programm ...

  4. CSS基础 transform属性的基本使用 移动 旋转 缩放

    1.实现元素位移效果 语法:transform:translate(x轴水平移动距离,Y轴垂直移动距离) 取值:正负都可以 取值方式:数字+px 百分比 :参照自己本的盒子的百分比 比如:本身自己的宽 ...

  5. 初识python: 文件操作

    1.文件的打开模式: r:读模式:w:写模式:a:追加模式,在文件最后写入内容;r+:读写模式,读取文件内容,并在末尾添加记录:w+:写读模式,新建文件并添加记录:a+:追加写读:rb:以二进制格式读 ...

  6. Go语言系列之并发编程

    Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(宏观上并行,微观上并发). 并行:同一时刻执行多个任务(宏观和微观都是并行). Go语言的并发通过goroutine实现.gorout ...

  7. Nginx虚拟主机、日志排错、模块配置

    目录 Nginx虚拟主机 1. 基于多IP的方式 2. 基于多端口的方式 3. 基于多域名的方式 Nginx日志 Nginx配置文件配置项 Nginx模块 Nginx访问控制模块 Nginx状态监控模 ...

  8. 一文看懂B端产品和C端产品

    大纲 什么是B端产品 什么是C端产品 为什么会产生B端产品和C端产品 怎么判断一个产品是B端还是C端 B端产品和C端产品存在哪些差异 C端产品经理如何向B端产品经理转型 写在最后   什么是B, Bu ...

  9. 对极验geetest滑块验证码图片还原算法的研究

    免责声明 本文章所提到的技术仅用于学习用途,禁止使用本文章的任何技术进行发起网络攻击.非法利用等网络犯罪行为,一切信息禁止用于任何非法用途.若读者利用文章所提到的技术实施违法犯罪行为,其责任一概由读者 ...

  10. Solon 开发,八、注入依赖与初始化

    Solon 开发 一.注入或手动获取配置 二.注入或手动获取Bean 三.构建一个Bean的三种方式 四.Bean 扫描的三种方式 五.切面与环绕拦截 六.提取Bean的函数进行定制开发 七.自定义注 ...