AopContext.currentProxy() 获取代理对象

2024-02-01 20:44

本文主要是介绍AopContext.currentProxy() 获取代理对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

引言

Spring AOP中,切面Aspect是一个强大的概念,允许我们将横切关注点模块化,提高代码的可维护性。然而,在同一个类中,当非切面方法A调用切面方法B时,有时我们会面临切面失效的问题。本文将介绍一种解决方案,即使用AopContext.currentProxy()方法,以确保在这种场景下切面能够正常生效。

背景

在某些情况下,我们可能会在同一个类中定义既包含切面方法,又包含非切面方法的业务逻辑。当非切面方法直接调用切面方法时,切面有可能失效,导致切面中定义的通知未被触发。这时,我们需要一种方法来确保切面能够在同一类的内部正确工作。

起源

闲着无聊看到RuoYi-Cloud-Plus项目里面封装的一个SpringUtils,也就是封装的一个spring工具类,它继承了Hutool的SpringUtil工具类,其中里面有一个getAopProxy获取aop代理对象的方法引起了我的注意。

    /*** 获取aop代理对象** @param invoker* @return*/@SuppressWarnings("unchecked")public static <T> T getAopProxy(T invoker) {return (T) AopContext.currentProxy();}

该方法所引用到的地方例如:
全限定路径:com.ruoyi.resource.service.impl.SysOssServiceImpl#download
这是一个文件上传的服务
在这里插入图片描述
从图中我们可以看到download方法里面调用了当前类的一个getById方法,但是仔细看,getById方法上面标注了一个注解 @Cacheable,这个注解主要就是用来做缓存数据的,而这个注解在Spring中主要是通过Aop实现的,因此在会被Spring创建一个代理对象

那么问题来了,为什么要这样写呢?而不是直接去调用getById方法


解决方案:AopContext.currentProxy()

为什么切面会失效?

在同一个类中,非切面方法直接调用切面方法时,Spring AOP 默认情况下可能无法正确拦截调用,从而导致切面失效。这是因为 Spring AOP 是基于代理模式实现的,直接在同一类中调用切面方法可能绕过了代理。

AopContext.currentProxy() 的作用

为了解决这个问题,我们可以使用 AopContext.currentProxy() 方法获取当前代理对象。该方法允许我们在同一类的非切面方法中获取代理对象,从而确保切面可以正常生效

示例代码

日志切面类
package com.hsqyz.aop.test.proxy.aspect;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Component
@Aspect//表明当前类是一个切面类
public class LogUtil {/*** 用于配置当前方法是一个前置通知*/@Before("execution(* com.hsqyz.aop.test.proxy.service.impl.*.saveUser(..))")public void printLog() {System.out.println("执行打印日志的功能");}}
用户实体类
package com.hsqyz.aop.test.proxy.entity;import lombok.Data;import java.io.Serializable;
import java.util.Date;@Data
public class User implements Serializable {private String id;private String username;private String password;private String email;private Date birthday;private String gender;private String mobile;private String nickname;
}
用户业务接口
package com.hsqyz.aop.test.proxy.service;import com.hsqyz.aop.test.proxy.entity.User;import java.util.List;public interface UserService {/*** 模拟保存用户** @param user*/void saveUser(User user);/*** 批量保存用户** @param users*/void saveAllUser(List<User> users);}
用户业务实现类
package com.hsqyz.aop.test.proxy.service.impl;import com.hsqyz.aop.test.proxy.service.UserService;
import com.hsqyz.aop.test.proxy.entity.User;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;import java.util.List;@Service("userService")
public class UserServiceImpl implements UserService {/*** 此方法被AOP切面增强过* @param user 用户对象*/@Overridepublic void saveUser(User user) {System.out.println("执行了保存用户" + user);}/*** 保存所有用户* 对用户列表中的每个用户,通过动态代理调用 UserService 的 saveUser 方法进行保存。** @param users 用户列表,包含需要保存的用户对象*/@Overridepublic void saveAllUser(List<User> users) {for (User user : users) {UserService proxyUserServiceImpl = (UserService) AopContext.currentProxy();proxyUserServiceImpl.saveUser(user);}}}
AOP切面配置类
package com.hsqyz.aop.test.proxy;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan("com.hsqyz.aop.test.proxy")
@EnableAspectJAutoProxy(exposeProxy = true)//开启spring注解aop配置的支持
public class SpringConfiguration {}
测试类
package com.hsqyz.aop.test.proxy;import com.hsqyz.aop.test.proxy.entity.User;
import com.hsqyz.aop.test.proxy.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;import java.util.ArrayList;
import java.util.List;public class SpringEnableAspecctJAutoProxyTest {public static void main(String[] args) {//1.创建容器AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);//2.获取对象UserService userService = ac.getBean("userService", UserService.class);//3.执行方法User user = new User();user.setId("1");user.setUsername("test");List<User> users = new ArrayList<>();users.add(user);// 调用userService.saveAllUser(users);}}

总览

运行效果

这里展示2种不同调用运行效果

通过this直接调用

通过this直接调用当前对象的方法。

运行效果:没有走切面逻辑!

通过代理调用

通过AopContext获取代理对象 UserService proxyUserServiceImpl = (UserService) AopContext.currentProxy();

运行效果:走了切面逻辑!

总结

Spring AOP 中,切面的生效取决于方法的调用关系:

  • 在不同类中,非切面方法A调用切面方法B,切面生效。
  • 在不同类中,切面方法A调用非切面方法B,切面生效
  • 在同一个类中,切面方法A调用非切面方法B,切面具有传播性,切面生效
  • 在同一个类中,非切面方法A调用同一类中的另一个切面方法C时,切面失效

其实这个问题和有些场景比如:加上@Transactional注解事物却失效,都是一个道理, @Transactional也是基于AOP实现的,那么既然基于AOP肯定就会被Spring创建代理对象,事务失效失效有很多种,其一种场景就和本文说的是一样的,直接在当前类去调用一个事物方法,也就会导致事物失效。

参考资料

AopContext.currentProxy();为什么能获取到代理对象
SpringBoot 中 AOP 的内部调用失效

这篇关于AopContext.currentProxy() 获取代理对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/668417

相关文章

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

Python获取中国节假日数据记录入JSON文件

《Python获取中国节假日数据记录入JSON文件》项目系统内置的日历应用为了提升用户体验,特别设置了在调休日期显示“休”的UI图标功能,那么问题是这些调休数据从哪里来呢?我尝试一种更为智能的方法:P... 目录节假日数据获取存入jsON文件节假日数据读取封装完整代码项目系统内置的日历应用为了提升用户体验,

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

使用Python实现获取网页指定内容

《使用Python实现获取网页指定内容》在当今互联网时代,网页数据抓取是一项非常重要的技能,本文将带你从零开始学习如何使用Python获取网页中的指定内容,希望对大家有所帮助... 目录引言1. 网页抓取的基本概念2. python中的网页抓取库3. 安装必要的库4. 发送HTTP请求并获取网页内容5. 解

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

使用Python高效获取网络数据的操作指南

《使用Python高效获取网络数据的操作指南》网络爬虫是一种自动化程序,用于访问和提取网站上的数据,Python是进行网络爬虫开发的理想语言,拥有丰富的库和工具,使得编写和维护爬虫变得简单高效,本文将... 目录网络爬虫的基本概念常用库介绍安装库Requests和BeautifulSoup爬虫开发发送请求解