Spring AOP调用本类方法为什么没有生效
首先请思考一下以下代码执行的结果:
LogAop.java
//声明一个AOP拦截service包下的所有方法
@Aspect
public class LogAop {
@Around("execution(* com.demo.service.*.*(..))")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
try {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object ret = joinPoint.proceed();
//执行完目标方法之后打印
System.out.println("after execute method:"+method.getName());
return ret;
} catch (Throwable throwable) {
throw throwable;
}
}
}
UserService.java
@Service
public class UserService{
public User save(User user){
//省略代码
}
public void sendEmail(User user){
//省略代码
}
//注册
public void register(User user){
//保存用户
this.save(user);
//发送邮件给用户
this.sendEmail(user);
}
}
UserServiceTest.java
@SpringBootTest
public class UserServiceTest{
@Autowired
private UserService userService;
@Test
public void save(){
userService.save(new User());
}
@Test
public void sendEmail(){
userService.sendEmail(new User());
}
@Test
public void register(){
UserService.register(new User());
}
}
在执行save方法后,控制台输出为:
after execute method:save
在执行sendEmail方法后,控制台输出为:
after execute method:sendEmail
请问在执行register()方法后会打印出什么内容?
反直觉
这个时候可能很多人都会和我之前想的一样,在register方法里调用了save和sendEmail,那 AOP 会处理save和sendEmail,输出:
after execute method:save
after execute method:sendEmail
after execute method:register
然而事实并不是这样的,而是输出:
after execute method:register
在这种认知的情况下,很可能就会写出有bug的代码,例如:
@Service
public class UserService{
//用户下单一个商品
public void order(User user,String orderId){
Order order = findOrder(orderId);
pay(user,order);
}
@Transactional
public void pay(User user,Order order){
//扣款
user.setMoney(user.getMoney()-order.getPrice());
save(user);
//...其它处理
}
}
当用户下单时调用的order方法,在该方法里面调用了@Transactional注解修饰的pay方法,这个时候pay方法的事务管理已经不生效了,在发生异常时就会出现问题。
理解 AOP
我们知道 Spring AOP默认是基于动态代理来实现的,那么先化繁为简,只要搞懂最基本的动态代理自然就明白之前的原因了,这里直接以 JDK 动态代理为例来演示一下上面的情况。
关注公众号程序员小乐回复关键字“Java”获取Java面试题和答案。
由于 JDK 动态代理一定需要接口类,所以首先声明一个IUserService接口
IUserService.java
public interface IUserService{
User save(User user);
void sendEmail(User user);
User register(User user);
}
编写实现类
UserService.java
public class UserService implements IUserService{
@Override
public User save(User user){
//省略代码
}
@Override
public void sendEmail(User user){
//省略代码
}
//注册
@Override
public void register(User user){
//保存用户
this.save(user);
//发送邮件给用户
this.sendEmail(user);
}
}
编写日志处理动态代理实现
ServiceLogProxy.java
public static class ServiceLogProxy {
public static Object getProxy(Class<?> clazz, Object target) {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(target, args);
System.out.println("after execute method:" + method.getName());
return ret;
}
});
}
}
运行代码
Main.java
public class Main{
public static void main(String[] args) {
//获取代理类
IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
userService.save(new User());
userService.sendEmail(new User());
userService.register(new User());
}
}
结果如下:
after execute method:save
after execute method:sendEmail
after execute method:register
可以发现和之前 Spring AOP 的情况一样,register方法中调用的save和sendEmail方法同样的没有被动态代理拦截到,这是为什么呢,接下来就看看下动态代理的底层实现。
关注公众号程序员小乐回复关键字“offer”获取算法面试题和答案。
动态代理原理
其实动态代理就是在运行期间动态的生成了一个class在 jvm 中,然后通过这个class的实例调用真正的实现类的方法,伪代码如下:
public class $Proxy0 implements IUserService{
//这个类就是之前动态代理里的new InvocationHandler(){}对象
private InvocationHandler h;
//从接口中拿到的register Method
private Method registerMethod;
@Override
public void register(User user){
//执行前面ServiceLogProxy编写好的invoke方法,实现代理功能
h.invoke(this,registerMethod,new Object[]{user})
}
}
回到刚刚的main方法,那个userService变量的实例类型其实就是动态生成的类,可以把它的 class 打印出来看看:
IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService());
System.out.println(userService.getClass());
输出结果为:
xxx.xxx.$Proxy0
在了解这个原理之后,再接着解答之前的疑问,可以看到通过代理类的实例执行的方法才会进入到拦截处理中,而在InvocationHandler#invoke()方法中,是这样执行目标方法的:
//注意这个target是new UserService()实例对象
Object ret = method.invoke(target, args);
System.out.println("after execute method:" + method.getName());
在register方法中调用this.save和this.sendEmail方法时,this是指向本身new UserService()实例,所以本质上就是:
User user = new User();
UserService userService = new UserService();
userService.save(user);
userService.sendEmail(user);
不是动态代理生成的类去执行目标方法,那必然不会进行动态代理的拦截处理中,明白这个之后原理之后,就可以改造下之前的方法,让方法内调用本类方法也能使动态代理生效,就是用动态代理生成的类去调用方法就好了,改造如下:
UserService.java
public class UserService implements IUserService{
//注册
@Override
public void register(User user){
//获取到代理类
IUserService self = (IUserService) ServiceLogProxy.getProxy(IUserService.class, this);
//通过代理类保存用户
self.save(user);
//通过代理类发送邮件给用户
self.sendEmail(user);
}
}
运行main方法,结果如下:
after execute method:save
after execute method:sendEmail
after execute method:save
after execute method:sendEmail
after execute method:register
可以看到已经达到预期效果了。
Spring AOP 中方法调用本类方法的解决方案
同样的,只要使用代理类来执行目标方法就行,而不是用this引用,修改后如下:
@Service
public class UserService{
//拿到代理类
@Autowired
private UserService self;
//注册
public void register(User user){
//通过代理类保存用户
self.save(user);
//通过代理类发送邮件给用户
self.sendEmail(user);
}
}
好了,问题到此就解决了,但是需要注意的是Spring官方是不提倡这样的做法的,官方提倡的是使用一个新的类来解决此类问题,例如创建一个UserRegisterService类:
@Service
public class UserRegisterService{
@Autowired
private UserService userService;
//注册
public void register(User user){
//通过代理类保存用户
userService.save(user);
//通过代理类发送邮件给用户
userService.sendEmail(user);
}
}
Spring AOP调用本类方法为什么没有生效的更多相关文章
- spring aop 动态代理批量调用方法实例
今天项目经理发下任务,需要测试 20 个接口,看看推送和接收数据是否正常.因为对接传输的数据是 xml 格式的字符串,所以我拿现成的数据,先生成推送过去的数据并存储到文本,以便验证数据是否正确,这时候 ...
- 源码解析Spring AOP的加载与生效
本次博主主要进行Spring AOP这里的解析,因为在工作中使用后,却不知道背后的实现原理并在使用的过程中发现了一些认知缺陷,所以决定写这么一篇文章以供大家参考参考,进入正题. 本次博主使用了@Asp ...
- Spring AOP在函数接口调用性能分析及其日志处理方面的应用
面向切面编程可以实现在不修改原来代码的情况下,增加我们所需的业务处理逻辑,比如:添加日志.本文AOP实例是基于Aspect Around注解实现的,我们需要在调用API函数的时候,统计函数调用的具体信 ...
- Spring AOP深入理解之拦截器调用
Spring AOP深入理解之拦截器调用 Spring AOP代理对象生成回想 上一篇博客中:深入理解Spring AOP之二代理对象生成介绍了Spring代理对象是怎样生成的,当中重点介绍了JDK动 ...
- spring aop 内部调用问题解决
方法1: 基于 proxy 的 spring aop 带来的内部调用问题可以使用 AopContext.currentProxy() 强转为当前的再调用就可以解决了 例如: 错误用法:public A ...
- spring aop 之链式调用
关关雎鸠,在河之洲.窈窕淑女,君子好逑. 概述 AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横 ...
- Spring AOP无法拦截内部方法调用
当在同一个类中,A方法调用B方法时,AOP无法工作的问题 假设一个接口里面有两个方法: package demo.long; public interface CustomerService { pu ...
- java:struts框架2(方法的动态和静态调用,获取Servlet API三种方式(推荐IOC(控制反转)),拦截器,静态代理和动态代理(Spring AOP))
1.方法的静态和动态调用: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCT ...
- Spring AOP开发时如何得到某个方法内调用的方法的代理对象?
Spring AOP开发时如何得到某个方法内调用的方法的代理对象? 问题阅读起来拗口,看代码 在方法中调用其他方法很常见,也经常使用,如果在一个方法内部调用其他方法,比如 public class U ...
随机推荐
- .NET6: 开发基于WPF的摩登三维工业软件 (2)
在<.NET6: 开发基于WPF的摩登三维工业软件 (1)>我们创建了一个"毛坯"界面,距离摩登还差一段距离.本文将对上一阶段的成果进行深化,实现当下流行的暗黑风格UI ...
- python基础语法_闭包详解
https://www.cnblogs.com/Lin-Yi/p/7305364.html 闭包有啥用??!! 很多伙伴很糊涂,闭包有啥用啊??还这么难懂! 3.1装饰器!!!装饰器是做什么的??其 ...
- 大地坐标BLH转平面坐标xyh(高斯投影坐标正算) Java版
技术背景 做过位置数据处理的小伙伴基本上都会遇到坐标转换,而基于高斯投影原理的大地坐标转平面坐标就是其中一种坐标转换,坐标转换的目的就是方便后面数据的处理工作,大地坐标转高斯平面坐标常用的有两种,即3 ...
- visual studio自动向量化
//////////////////////////////////////////////////*SSE 和 AVX 每个都有16个寄存器SSE 有 XMM0 ~ XMM15,是128bitAVX ...
- [LeetCode]2.Add Two Numbers 两数相加(Java)
原题地址: add-two-numbers 题目描述: 给你两个非空的链表,表示两个非负的整数.它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字. 请你将两个数相加,并以相同形式返回 ...
- c++ StrVec等效vector(string)的类
c++ StrVec等效vector<string>的类 知识点 静态成员变量要在类外定义和初始化 allocator类是使用和uninitialized_copy的配合使用,实现stri ...
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- jenkins针对不同用户显示不同项目
网上看了别人写的博客有点头晕 比如:https://www.cnblogs.com/kazihuo/p/9022899.html 典型的权限混乱,te用户可以读re用户的项目,re用户可以读te用户 ...
- pytest--配置用例执行顺序(pytest_ordering插件介绍)
前言 设置测试用例执行顺序: 默认情况下,pytest测试用例的执行顺序是按先外层后内层(目录下的文 件),再根据名称按ascii码值的顺序升序执行. 如果想自定义pytest测试用例的执行顺序,可以 ...
- 2、CPU是怎么实现运算的 ?
我先来了解一下芯片构造,芯片由晶体管组成的.晶体管组成逻辑运算与或非电路. P型半导体的"P"表示正电的意思,取自英文Positive的第一个字母. N型半导体的"N&q ...