引言
在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 用户对象
*/
@Override
public void saveUser(User user) {
System.out.println("执行了保存用户" + user);
}
/**
* 保存所有用户
* 对用户列表中的每个用户,通过动态代理调用 UserService 的 saveUser 方法进行保存。
*
* @param users 用户列表,包含需要保存的用户对象
*/
@Override
public 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 的内部调用失效