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 ...
随机推荐
- NoSQL 之 Redis配置与优化
NoSQL 之 Redis配置与优化 1.关系数据库与非关系型数据库概述 2.关系数据库与非关系型数据库区别 3.非关系型数据库产生背景 4.Redis简介 5.Redis安装部署 6.Redis 命 ...
- MySQL数据库授权与索引
MySQL数据库授权与索引 目录 MySQL数据库授权与索引 一.数据库用户授权 1. 授予权限 2. 查看权限 3. 删除权限 4. 全部权限(all privileges) 二.MySQL索引 1 ...
- python 使用pip安装包的总结
multiprocessing.logging模块安装 如果使用在cmd中使用 pip install multiprocessing 会报错, 将命令改为 pip3 install multipro ...
- 阅读mybatis的源码的思路
■ 阅读源码mybatis操作数据库的过程: /* 测试查询 */ @Test public void testGet() throws IOException { // 1.从classpath路径 ...
- Kubernetes家族容器小管家Pod在线答疑?
Kubernetes家族容器小管家Pod在线答疑 不知道学习k8s的小伙伴们有没有跟我一样的疑问? k8s为什么不是直接运行容器,而是让Pod介入? Pod又是什么?为什么在应用容器化如此普遍的情况下 ...
- 从零开始学mitmproxy抓包工具
Man In The Middle mitm是Man In The Middle的首字母缩写,意思是位于中间的人,表明mitmproxy是一个代理,可以拦截请求,实现网络抓包.知名的网络抓包工具有Fi ...
- 基于XC7A100T的PCIe千兆电口以太网收发卡
一.板卡概述 本板卡采用Xilinx公司的Artix7系列的XC7A100T-2FGG484 芯片作为主处理器.包含双路千兆电口网络,双组DDR,PCIeX1 V1.1接口,板卡设计满足工业级要求. ...
- Solution -「ABC 215H」Cabbage Master
\(\mathcal{Description}\) Link. 有 \(n\) 种颜色的,第 \(i\) 种有 \(a_i\) 个,任意两球互不相同.还有 \(m\) 个盒子,每个盒子可以被放 ...
- Solution -「LOCAL」ZB 平衡树
\(\mathcal{Description}\) OurOJ. 维护一列二元组 \((a,b)\),给定初始 \(n\) 个元素,接下来 \(m\) 次操作: 在某个位置插入一个二元组: 翻 ...
- Eclipse插件开发demo
一.安装 1. 下载安装JDk 2. 下载安装PDE(Plugin-in Development Environment) 访问eclipse官网,点击Download,选择Download Pack ...