Spring Boot 依赖注入为 null 问题

devtools/2024/10/25 16:27:26/

目录

问题

省流

代码复现

TestService

TestAspect

TestController

源码分析

AbstractAutoProxyCreator

CglibAopProxy

Enhancer


问题

工作中,在负责的模块里使用 @DubboService 注解注册了一个 dubbo 接口,给定时任务模块去调用。在自我调试阶段,需要在本地自己验证一下接口的功能实现,所以就在本地写了一个测试接口来调用 dubbo 接口,在 Controller 层使用 @DubboReference 注解注入依赖后,在 debug 时却发现注入的依赖为 null。第一时间怀疑是自己写法不对,时间比较赶,所以就想着先不研究这个问题,可以先通过其他方式来验证,可以使用 Spring 的 @Service 注解将 dubbo 接口实现注册成 Spring 容器的bean,然后通过 @Autowired 来注入依赖,同样可以验证 dubbo 接口的功能。但是最终在 debug 时,注入的依赖同样为 null。

省流

当接口使用了 AOP 时,Controller 层接口方法不能为 private !!!应该为 public!!!

正常来说都不应该遇到这种问题,写接口时一般也是直接复制现有的接口方法,然后修修改改,Controller 层的方法一般都是 public,所以当时出现问题了也没能快速反应到是方法修饰符写错了。项目中存在private修饰的接口方法,估计就是写代码时通过代码提示手滑才选中了private。

代码复现

这里使用简单的代码进行复现错误,以及排查过程。

TestService

使用 Spring 的 @Service 注解注册为 Spring 容器的 bean

java">@Service
public class TestService {public void doSomething() {System.out.println("doSomething");}
}

TestAspect

java">@Aspect
@Component
@Slf4j
public class TestAspect {@Pointcut("execution(* test*(..))")public void testPointCut() { }@Around("testPointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {return point.proceed();}
}

TestController

java">@RestController
@RequestMapping ("test")
public class TestController {@Autowiredprivate TestService testService;@RequestMapping("/test1")private ResponseEntity<String> test1(){testService.doSomething();return ResponseEntity.ok("success");}@RequestMapping("/test2")public ResponseEntity<String> test2(){testService.doSomething();return ResponseEntity.ok("success");}
}

当时项目是能够正常启动的,所以就排除了依赖不存在的问题,如果启动时发现找不到依赖,就会直接抛出异常,导致 Spring 容器启动了。

自己写的接口依赖注入为 null,但是试了下其他接口,却发现一切正常。

最终,在 debug 中发现了问题

依赖注入失败的接口依赖注入成功的接口,拿到的 Controller 实例不一样!

依赖注入失败的接口:

依赖注入成功的接口:

 可以看到两个接口最终拿到的是不一样的对象实例,public 方法获取到的就是有依赖注入的TestController实例,而private 方法获取到的是 TestController 的代理对象。代理对象显然是使用 AOP 动态生成的。

源码分析

直接从源码上看 AOP 创建切面代理对象的逻辑

AbstractAutoProxyCreator

在项目依赖中找到 AbstractAutoProxyCreator,可以看到它实现了 SmartInstantiationAwareBeanPostProcessor 接口,BeanPostProcessor 接口,就是用于 Spring 创建实例后做后置处理的,这里就是用来创建 AOP 代理对象。Spring 容器在实例化bean后,就会通过 postProcessAfterInitialization 来做 bean 的后置处理。

调用方法链如下:postProcessAfterInitialization -> wrapIfNecessary -> createProxy

java">public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {//...... 省略无关代码/*** Create a proxy with the configured interceptors if the bean is* identified as one to proxy by the subclass.* @see #getAdvicesAndAdvisorsForBean*/@Overridepublic 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;}//...... 省略无关代码/*** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is*/protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.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;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}//...... 省略无关代码/*** Create an AOP proxy for the given bean.* @param beanClass the class of the bean* @param beanName the name of the bean* @param specificInterceptors the set of interceptors that is* specific to this bean (may be empty, but not null)* @param targetSource the TargetSource for the proxy,* already pre-configured to access the bean* @return the AOP proxy for the bean* @see #buildAdvisors*/protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);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);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}return proxyFactory.getProxy(getProxyClassLoader());}//...... 省略无关代码}

creatProxy 方法最后委托给了 proxyFactory 来创建代理对象

proxyFactory.getProxy(getProxyClassLoader());

从上面debug的截图中可以看出来,Spring 最后使用的是 cglib 动态代理方式。

所以这里直接找到对应 cglib 动态代理相关的类 CglibAopProxy

CglibAopProxy

java">class CglibAopProxy implements AopProxy, Serializable {//......省略无关代码@Overridepublic Object getProxy(@Nullable ClassLoader classLoader) {if (logger.isTraceEnabled()) {logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());}try {Class<?> rootClass = this.advised.getTargetClass();Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");Class<?> proxySuperClass = rootClass;if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {proxySuperClass = rootClass.getSuperclass();Class<?>[] additionalInterfaces = rootClass.getInterfaces();for (Class<?> additionalInterface : additionalInterfaces) {this.advised.addInterface(additionalInterface);}}// Validate the class, writing log messages as necessary.validateClassIfNecessary(proxySuperClass, classLoader);// Configure CGLIB Enhancer...Enhancer enhancer = createEnhancer();if (classLoader != null) {enhancer.setClassLoader(classLoader);if (classLoader instanceof SmartClassLoader &&((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {enhancer.setUseCache(false);}}enhancer.setSuperclass(proxySuperClass);enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));Callback[] callbacks = getCallbacks(rootClass);Class<?>[] types = new Class<?>[callbacks.length];for (int x = 0; x < types.length; x++) {types[x] = callbacks[x].getClass();}// fixedInterceptorMap only populated at this point, after getCallbacks call aboveenhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));enhancer.setCallbackTypes(types);// Generate the proxy class and create a proxy instance.return createProxyClassAndInstance(enhancer, callbacks);}catch (CodeGenerationException | IllegalArgumentException ex) {throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +": Common causes of this problem include using a final class or a non-visible class",ex);}catch (Throwable ex) {// TargetSource.getTarget() failedthrow new AopConfigException("Unexpected AOP exception", ex);}}protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {enhancer.setInterceptDuringConstruction(false);enhancer.setCallbacks(callbacks);return (this.constructorArgs != null && this.constructorArgTypes != null ?enhancer.create(this.constructorArgTypes, this.constructorArgs) :enhancer.create());}//......省略无关代码}

可以看到,CglibAopProxy 最后将创建代理对象委托给了 Enhancer

Enhancer

在 Enhancer 中,可以看到其中的 getMethods 方法,其中的 CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));

java">public class Enhancer extends AbstractClassGenerator {private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) {ReflectUtils.addAllMethods(superclass, methods);List target = (interfaceMethods != null) ? interfaceMethods : methods;if (interfaces != null) {for (int i = 0; i < interfaces.length; i++) {if (interfaces[i] != Factory.class) {ReflectUtils.addAllMethods(interfaces[i], target);}}}if (interfaceMethods != null) {if (forcePublic != null) {forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));}methods.addAll(interfaceMethods);}CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC));CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));CollectionUtils.filter(methods, new DuplicatesPredicate());CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL));}}

VisibilityPredicate 中,可以看到会取方法的修饰符做判断,Modifier.isPrivate(mode) 为true,则返回false。即将被代理对象的private方法过滤掉了,被代理对象中没有对应的方法,所以就只能执行代理对象中的方法了,而代理对象是由cglib实例化的,里面没有spring注入的对象,所以报空指针。

java">public class VisibilityPredicate implements Predicate {private boolean protectedOk;private String pkg;private boolean samePackageOk;public VisibilityPredicate(Class source, boolean protectedOk) {this.protectedOk = protectedOk;this.samePackageOk = source.getClassLoader() != null;this.pkg = TypeUtils.getPackageName(Type.getType(source));}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())));}}
}


http://www.ppmy.cn/devtools/128721.html

相关文章

从传统到智能,从被动监控到主动预警,解锁视频安防平台EasyCVR视频监控智能化升级的关键密钥

视频监控技术从传统监控到智能化升级的过程是一个技术革新和应用场景拓展的过程。智能视频监控系统通过集成AI和机器学习算法&#xff0c;能够实现行为分析、人脸识别和异常事件检测等功能&#xff0c;提升了监控的准确性和响应速度。这些系统不仅用于传统的安全防护&#xff0…

uni-app 开发微信小程序,实现图片预览和保存

1.使用 uni.previewImage() 预览图片 1.1 图片列表 1.2 预览 1.2.1 样式无法调整 1.2.2 微信小程序不支持预览本地文件路径图片&#xff08;图片上传到小程序的临时文件存储或云服务存储&#xff09; 1.3 无法绑定 longpress"saveImage(item)" 长按保存图片事件 …

微信消息语音播报秒实现

1. 监听系统消息通知 注册一个监听系统消息的服务 <serviceandroid:name".MyNotificationListenerService"android:exported"true"android:permission"android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" ><intent-filter>&…

FFmpeg 库的简要说明

FFmpeg 库的简要说明&#xff1a; libavutil 功能&#xff1a;提供一系列通用工具函数&#xff0c;旨在简化开发流程。 主要用途&#xff1a; 随机数生成器&#xff1a;用于生成随机数&#xff0c;适用于各种应用。 数据结构&#xff1a;提供常用的数据结构&#xff08;如链表…

[Linux网络编程]04-多进程/多线程并发服务器思路分析及实现(进程,信号,socket,线程...)

一.思路 实现一个服务器可以连接多个客户端&#xff0c;每当accept函数等待到客户端进行连接时 就创建一个子进程; 核心思路&#xff1a;让accept循环阻塞等待客户端&#xff0c;每当有客户端连接时就fork子进程&#xff0c;让子进程去和客户端进行通信&#xff0c;父进程用于…

骨传导耳机哪个品牌好用质量好?2024年五款高性价比骨传导耳机推荐

骨传导耳机是我们日常生活中不可或缺的电子产品之一&#xff0c;无论是为了享受音乐还是运动健身使用都非常的合适&#xff0c;而选择一款好用且质量优秀的骨传导耳机却并不简单。因为市面上的骨传导耳机品牌琳琅满目&#xff0c;各种功能和设计让人眼花缭乱&#xff0c;经常让…

基于RK3588/算能BM1684 AI盒子:综合视频智能AI分析系统建设方案(五)边缘盒子与AI服务器

方案硬件介绍 智能AI分析服务器 机型:2U机架式服务器 处理器&#xff1a;CPU_Xeon_5318Y_2.1GHz_24C_48T_165W*2 内存&#xff1a;32GB DDR4 3200 RDIMM*4 240GB 2.5in&#xff08;SATA SSD&#xff09;*1 硬盘&#xff1a;6 TB, SATA 6Gb/s, 7.2K&am…

《近似线性可分支持向量机的原理推导》 目标函数 公式解析

本文是将文章《近似线性可分支持向量机的原理推导》中的公式单独拿出来做一个详细的解析&#xff0c;便于初学者更好的理解。 公式 9-38 解释&#xff1a; min ⁡ w , b , ξ 1 2 ∥ w ∥ 2 C ∑ i 1 N ξ i \min_{w, b, \xi} \quad \frac{1}{2} \|w\|^2 C \sum_{i1}^{N} \x…