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“,即只是对目标的反射调用

相关文章
Auto.js 各版本之间的区别
Auto.js 各版本之间的区别
1511 0
|
3月前
|
Web App开发 数据采集 前端开发
如何在VSCode中查看、转换markdown文件:使用MPE(Markdown Previe Enhanced)插件
本文介绍了在VSCode中使用MPE插件处理Markdown文件的方法。主要内容包括:1)MPE插件的安装;2)查看Markdown渲染效果的两种方式(内置快捷键和MPE右键预览);3)Markdown文件转换功能,详细讲解了导出HTML(离线/在线模式)、PDF(Chrome/Prince/Pandoc三种方式)和Word的操作步骤及注意事项。文章还提供了相关软件的安装指南和常见问题解决方案,帮助用户更好地使用MPE插件进行Markdown文档处理。
如何在VSCode中查看、转换markdown文件:使用MPE(Markdown Previe Enhanced)插件
|
8月前
|
人工智能 运维 安全
技术深析快手直播安全事件:为什么大量违规直播“关不掉”?
快手直播安全事件暴露了高并发下账号权限、风控与审核系统的系统性失效。对测试开发而言,需从功能验证转向系统性防控,强化极端场景测试、高负载审核链路验证及熔断机制演练,提升对复杂风险的预判与拦截能力。
|
Java
【异常解决】Java运行时发生 java.lang.NoClassDefFoundError: Could not initialize class com.iot.alarm.ProcAlar
【异常解决】Java运行时发生 java.lang.NoClassDefFoundError: Could not initialize class com.iot.alarm.ProcAlar
3017 0
|
前端开发 Java API
SpringBoot整合Flowable【07】- 驳回节点任务
本文通过绩效流程的业务场景,详细介绍了如何在Flowable工作流引擎中实现任务驳回功能。具体步骤包括:获取目标任务节点和当前任务节点信息,进行必要的判空和逻辑校验,调用API完成节点回退,并清理相关脏数据(如历史任务和变量)。最后通过测试验证了驳回功能的正确性,确保流程能够成功回退到指定节点并清除中间产生的冗余数据。此功能在实际业务中非常有用,能够满足上级驳回自评等需求。
3192 0
SpringBoot整合Flowable【07】- 驳回节点任务
|
9月前
|
人工智能 安全 测试技术
让 Obsidian 会自动工作:用 n8n 打造智能知识库
通过 n8n 与 Obsidian 的 Local REST API 插件结合,实现完全自定义的自动化知识管理:自动抓取小报童文章、生成每日模板、同步邮件等,让知识库自动生长。
|
安全 Linux 网络安全
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
151832 0
|
存储 文字识别 JavaScript
Uppy:告别传统上传!这款开源工具如何让文件传输效率提升300%?🐶
**Uppy** 是由 Transloadit 团队开发的模块化、高扩展性的 JavaScript 文件上传库,支持断点续传、云存储直传、图片编辑等高级功能。它无缝集成 React、Vue 等框架,兼容移动端,被 Instagram、知乎等企业采用。Uppy 采用“核心+插件”架构,代码轻量且功能强大,适合电商、在线教育等多种场景。项目开源免费,GitHub 获得数万星标,提供丰富的插件生态和跨平台支持。
1553 4
|
安全 Java Linux
Could not connect to SMTP host: smtp.***.com, port: 465, response: -1
Could not connect to SMTP host: smtp.***.com, port: 465, response: -1
1322 0
|
Java
错误:has been compiled by a more recent version of the Java Runtime (class file version 56.0)
错误:has been compiled by a more recent version of the Java Runtime (class file version 56.0)
7391 0