文章整理来源:Spring编程常见错误50例_spring_spring编程_bean_AOP_SpringCloud_SpringWeb_测试_事务_Data-极客时间
案例11:AOP 调用 this 的当前方法无法被拦截
在 charge() 方法中调用了增强的 this.pay() 方法,发现增强的没有起到效果
@Service
public class ElectricService {
public void charge() throws Exception {
System.out.println("Electric charging ...");
this.pay();
}
public void pay() throws Exception {
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
-------------------------------------------------------------
@Aspect
@Service
public class AopConfig {
@Around("execution(* com.spring.puzzle.class5.example1.ElectricService.pay()) ")
public void recordPayPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("Pay method time cost(ms): " + (end - start));
}
}
解析:Spring AOP 底层创建代理有两种方式,JDK方式(针对实现接口类生成代理) 和 CGLIB方式(针对类生成代理子类)。
创建代理对象,是在 InitializeBean() 的 applyBeanPostProcessorsAfterInitialization() 方法中创建代理对象
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;
}
------------------------------------------------------------------
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;
}
// 省略非关键代码
}
-----------------------------------------------
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
// 省略非关键代码
ProxyFactory proxyFactory = new ProxyFactory();
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
// 省略非关键代码
return proxyFactory.getProxy(getProxyClassLoader());
}
从 Spring 中获取到的对象都是这个代理对象,所以具有 AOP 功能。而之前直接使用 this 引用到的只是一个普通对象,自然也就没办法实现 AOP 的功能了
解决:1.从 AopContext 获取当前的 Proxy ;2. 将自身注入
1. 在 @EnableAspectJAutoProxy 里加一个配置项 exposeProxy = true,表示将代理对象放入到 ThreadLocal,这样才可以直接通过 AopContext.currentProxy() 的方式获取到
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {
// 省略非关键代码
}
-------------------------------
@Service
public class ElectricService {
public void charge() throws Exception {
System.out.println("Electric charging ...");
ElectricService electric = ((ElectricService) AopContext.currentProxy());
electric.pay();
}
public void pay() throws Exception {
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
2. 将自身注入
@Service
public class ElectricService {
@Autowired
ElectricService electricService;
public void charge() throws Exception {
System.out.println("Electric charging ...");
//this.pay();
electricService.pay();
}
public void pay() throws Exception {
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
案例12:直接访问被拦截类的属性抛空指针异常
在上述例子中定义登录服务,并给 login() 方法做增强,注入该服务到 ElectricService 中,并在其中获取登录服务的 adminUser ,而这个流程操作会报 adminUser 为空指针异常
@Service
public class AdminUserService {
public final User adminUser = new User("202101166");
public void login() {
System.out.println("admin user login...");
}
static class User {
private String payNum;
public User(String payNum) {
this.payNum = payNum;
}
public String getPayNum() {
return payNum;
}
}
}
------------------------------------------------------
@Aspect
@Service
public class AopConfig {
@Before("execution(* com.spring.puzzle.class5.example2.AdminUserService.login(..)) ")
public void logAdminLogin(JoinPoint pjp) throws Throwable {
System.out.println("! admin login ...");
}
}
----------------------------------------------------------
@Service
public class ElectricService {
@Autowired
private AdminUserService adminUserService;
public void pay() throws Exception {
adminUserService.login();
// adminUserService.adminUser 报空指针异常
String payNum = adminUserService.adminUser.getPayNum();
System.out.println("User pay num : " + payNum);
System.out.println("Pay with alipay ...");
Thread.sleep(1000);
}
}
解析:正常情况下,AdminUserService 只是一个普通的对象,而 AOP 增强过的则是一个 AdminUserService $$EnhancerBySpringCGLIB$$xxxx。这个类实际上是 AdminUserService 的一个子类。
它会 overwrite 所有 public 和 protected 方法,并在内部将调用委托给原始的 AdminUserService 实例。从具体实现角度看,CGLIB 中 AOP 的实现是基于 org.springframework.cglib.proxy 包中 Enhancer 和 MethodInterceptor 两个接口来实现的。但最终通过 ReflectionFactory.newConstructorForSerialization().newInstance() 实例化代理类则不会初始化类成员变量
解决:在ElectricService 里通过 getUser() 获取 User 对象,即调用代理对象的 getUser() 方法让其委托给实际对象获取到属性变量
@Service
public class AdminUserService {
public final User adminUser = new User("202101166");
public User getAdminUser(){
return adminUser
}
}
----------------------------------------------------------
@Service
public class ElectricService {
......
public void pay() throws Exception {
......
// adminUserService.adminUser 报空指针异常
// 改为 adminUserService.getAdminUser() 获取实际对象的属性
String payNum = adminUserService.getAdminUser().getPayNum();
........
}
}