Spring 50例常见错误(五)

本文介绍了Spring AOP在实际应用中遇到的两个问题及其解决方法。问题一涉及在同一个类中,AOP无法拦截this调用的方法,解决方案包括通过AopContext获取代理或注入自身。问题二展示了直接访问被拦截类属性导致的空指针异常,解决办法是通过代理对象访问属性以触发增强。文章详细解析了Spring AOP的代理创建过程,并提供了相应的代码示例。
摘要由CSDN通过智能技术生成

文章整理来源: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();
        ........
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值