SpringMVC控制层private方法中出现注入的service对象空指针异常

简介: 一、现象SpringMVC中controller里的private接口中注入的service层的bean为null,而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。

一、现象

SpringMVC中controller里的private接口中注入的service层的bean为null,而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。


controller中的方法被AOP进行了代理,普通Controller如果没有AOP,private方法中bean也是正常的。


二、原因分析

因为没有AOP增强的private方法是正常的,所以我们可以联想到可能是因为创建了代理对象的原因导致的属性为空。


首先SpringAOP有两种实现方式,一种是Jdk动态代理,一种是Cglib动态代理。


这两种方式一种是通过对接口的实现,一种是通过创建子类重写,那么显然这两种方式都是无法代理私有方法的。


创建代理对象时会经过这么一段逻辑Enhancer#generateClass -> Enhancer#getMethods -> CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true)) -> VisibilityPredicate#evaluate

public boolean evaluate(Object arg) {
   Member member = (Member)arg;
   int mod = member.getModifiers();
   if (Modifier.isPrivate(mod)) {
       return false;
   } else if (Modifier.isPublic(mod)) {
       return true;
   } else if (Modifier.isProtected(mod) && this.protectedOk) {
       return true;
   } else {
       return this.samePackageOk && this.pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass())));
   }
}

可以看到其中将私有方法进行了过滤,即创建的代理对象中并不会增强private方法


Spring中使用@Aspect注解会注册一个后置处理器,在Bean初始化时判断是否需要创建代理(主要逻辑在wrapIfNecessary方法中)。而我们都知道Bean在属性赋值时便将属性的依赖都注入了,所以此时的Bean中service层的bean是完成填充了的。


那为什么会出现调用private方法空指针异常呢?


这是因为为该类创建的代理并没有完成bean的生命周期,所以其中的属性是null。private方法并没有被真正的代理类拦截(如前面所说被过滤了),因此private方法无法获取被代理的对象,所以使用的是代理对象去调用的方法,而代理对象是由Cglib创建的并没有注入bean对象,所以出现了空指针异常。而当调用被增强了的方法(即在代理类中重写了的方法)时,其实传入的并非代理的实例对象,而是target,即被代理的Bean的实例对象,所以才能取得service层的bean。

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
   @Override
   @Nullable
   public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
       // 省略...
       target = targetSource.getTarget();
       Class<?> targetClass = (target != null ? target.getClass() : null);
       List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
       Object retVal;
       // Check whether we only have one InvokerInterceptor: that is,
       // no real advice, but just reflective invocation of the target.
       if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
          // We can skip creating a MethodInvocation: just invoke the target directly.
          // Note that the final invoker must be an InvokerInterceptor, so we know
          // it does nothing but a reflective operation on the target, and no hot
          // swapping or fancy proxying.
          Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
          retVal = invokeMethod(target, method, argsToUse, methodProxy);
       }
       else {
          // We need to create a method invocation...
          retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
       }
       retVal = processReturnType(proxy, target, method, retVal);
       return retVal;
       // 省略...
   }
}
static boolean isMethodProxyCompatible(Method method) {
   return (Modifier.isPublic(method.getModifiers()) &&
           method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
           !AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method));
}

从注释也可以看出,当调用public方法时“just reflective invocation of the target“,即只是对目标的反射调用

相关文章
|
11月前
|
前端开发 Java API
SpringBoot整合Flowable【06】- 查询历史数据
本文介绍了Flowable工作流引擎中历史数据的查询与管理。首先回顾了流程变量的应用场景及其局限性,引出表单在灵活定制流程中的重要性。接着详细讲解了如何通过Flowable的历史服务API查询用户的历史绩效数据,包括启动流程、执行任务和查询历史记录的具体步骤,并展示了如何将查询结果封装为更易理解的对象返回。最后总结了Flowable提供的丰富API及其灵活性,为后续学习驳回功能做了铺垫。
818 0
SpringBoot整合Flowable【06】- 查询历史数据
|
关系型数据库 MySQL 索引
MySQL in 太多的解决方案
MySQL in 太多的解决方案
1311 0
|
消息中间件 JSON Java
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
Spring Boot、Spring Cloud与Spring Cloud Alibaba版本对应关系
28152 0
|
缓存 监控 安全
Spring AOP 详细深入讲解+代码示例
Spring AOP(Aspect-Oriented Programming)是Spring框架提供的一种面向切面编程的技术。它通过将横切关注点(例如日志记录、事务管理、安全性检查等)从主业务逻辑代码中分离出来,以模块化的方式实现对这些关注点的管理和重用。 在Spring AOP中,切面(Aspect)是一个模块化的关注点,它可以跨越多个对象,例如日志记录、事务管理等。切面通过定义切点(Pointcut)和增强(Advice)来介入目标对象的方法执行过程。 切点是一个表达式,用于匹配目标对象的一组方法,在这些方法执行时切面会被触发。增强则定义了切面在目标对象方法执行前、执行后或抛出异常时所
17192 4
|
开发工具 git 开发者
如何让现有的 Git 分支跟踪远程分支?
【8月更文挑战第15天】
1481 1
如何让现有的 Git 分支跟踪远程分支?
|
Java Spring
SpringMVC控制层private方法中出现注入的service对象空指针异常
一、现象 SpringMVC中controller里的private接口中注入的service层的bean为null,而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。 controller中的方法被AOP进行了代理,普通Controller如果没有AOP,private方法中bean也是正常的。
|
缓存 监控 负载均衡
将近2万字的Dubbo原理解析,彻底搞懂dubbo
市面上有很多基于RPC思想实现的框架,比如有Dubbo。今天就从Dubbo的SPI机制、服务注册与发现源码及网络通信过程去深入剖析下Dubbo。
28773 9
|
缓存 监控 NoSQL
7min到40s:SpringBoot 启动优化实践!
7min到40s:SpringBoot 启动优化实践!
2267 3
|
Java 关系型数据库 MySQL
全网最实用的 IDEA Debug 调试技巧(超详细案例)
Debug 是程序员的开发神器,使用好了可以帮助我们非常高效的工作、学习、排查问题等。毫不客气的说,是决定我们进阶到更高层级的一个重要技能。 今天跟大家分享一下 IDEA 中 Debug 调试的各种奇技淫巧。
3748 0
全网最实用的 IDEA Debug 调试技巧(超详细案例)
|
监控 Dubbo Java
超详细的Sentinel入门
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
超详细的Sentinel入门