Spring 切面失败和目标类空指针问题

记一次Spring AOP 遇到的坑

背景

由于想记录 Controller 前后的处理情况,为什么不用 filter 处理是因为项目中有作业等其他请求,并不想做太多记录。

问题描述

  1. 加了 @Aspect 注解在切面类上,
 /**
     * 记录controller方法前所有的日志
     *
     * @param joinPoint 不能为空
     */
    @Before("execution(* com.xxx.controller..*.*(..))")
    public void pointBeforeMethodInvoke(JoinPoint joinPoint) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        StringBuilder sb = new StringBuilder("---------- Starting : ");
        sb.append(joinPoint.getSignature().getDeclaringTypeName());
        sb.append(".");
        sb.append(joinPoint.getSignature().getName());
        sb.append(logAllParamsInfo(joinPoint));
        sb.append("TAG_UUID:");
        sb.append(RequestContext.getContext().getId());
        sb.append(", Token:");
        sb.append(request.getParameter("token"));
        sb.append(" --------");
        info(sb::toString);

        RequestContext.getContext().setBeginTime(System.currentTimeMillis());
    }

然后我们的包结构如下:(xx 是把关键字隐去了)

  • com.xx
    • controller
    • controller.api
    • common
    • entity
    • 等等

可以看到我的切面是切的 controller 以及它下面的子包 api 和相关的类。

  1. 问题爆发,测试的时候发现,controller 和它下面子包的 api 除了一个类,其他的类都能被切面处理,唯独某个 Controller 进不去,而且诡异的是 在该类中注入的 service 类全为 null。 然后爆发了空指针异常。

解决异常

  1. 在爆发了空指针后,首先第一反应就是切面是否正常,查看 execution 表达式,以及测试 Controller 下的其他类,正常,所以排除 切面类的问题
  2. 在某个空指针的地方 debug,然后手动获取 注入的 Service 的 bean,发现正常,证明相关的配置注解正常。
  3. 在想不到其他办法的时候,选择使用 @PostConstruct 。在出问题的 Controller 中加入 init 手动初始化,加入 bean
public class StatisticsController {

    private static final Logger logger = LoggerFactory.getLogger(StatisticsController.class);

    @Autowired
    private IStatisticsService statisticsService;
    @Autowired
    private IKnowledgeService knowledgeService;

    @PostConstruct
    private void init() {
        StatisticsController statisticsController = this;
        statisticsController.knowledgeService = this.knowledgeService;
    }

然后在 init 方法中 debug, 发现在 Spring 启动的时候,statisticsService 和 knowledgeService 注入正常,但是在请求 Controller 的时候,2 个 Service 还是 null。

  1. 在尝试完上诉方法,想不到办法的情况下,只能上大招了(原谅作者菜)。一行一行的和正常的类比较代码, 当然,结果出来了,发现出问题的 Controller 的方法都是 private 的,正常的都是 public 的,然后查找相关的 文档,找到了原因

与AspectJ相比,Spring AOP是一种基于代理的“AOP lite”方法。它仅适用于Spring组件,仅适用于公共,非静态方法。这在Spring AOP文档中也有解释,如下所示:

由于Spring的AOP框架基于代理的特性,受保护的方法根据定义不会被拦截,既不用于JDK代理(这不适用),也不用于CGLIB代理(这在技术上可行,但不建议用于AOP)。因此,任何给定的切入点都只能与公共方法匹配!如果您的拦截需要包括受保护/私有方法甚至构造函数,请考虑使用Spring驱动的本机AspectJ编织而不是Spring的基于代理的AOP框架。这构成了具有不同特征的不同AOP使用模式,因此在做出决定之前一定要先熟悉编织。

说白了 就是 aop底层是代理, jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到; cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。

不是类内部直接调用方法,而是通过维护一个自身实例的代理 所以 execution(public * com.xxx.controller...(..))) 只会切 public 的方法,不写也一样,public 是默认切的方法,如果写了protected,他就什么事情都不做,连protected的方法也不拦截。 如果想要实现拦截private方法的 可以使用 原生 AspectJ 编译期/运行期织入。

而我们上面的空指针问题,其实就是 切面已经代理了 Controller 下的类,但是又没有被切到,造成了注入的失败。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券