Spring Boot 依赖注入为 null 问题

news2024/10/25 10:45:03

目录

问题

省流

代码复现

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

@Service
public class TestService {

    public void doSomething() {
        System.out.println("doSomething");
    }
}

TestAspect

@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

@RestController
@RequestMapping ("test")
public class TestController {

    @Autowired
    private 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

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    //...... 省略无关代码

    /**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@Override
	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;
	}

    //...... 省略无关代码

	/**
	 * 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

class CglibAopProxy implements AopProxy, Serializable {
    //......省略无关代码

	@Override
	public 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 above
			enhancer.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() failed
			throw 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));

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注入的对象,所以报空指针。

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.coloradmin.cn/o/2223207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Bert+BiLSTM+CRF训练 NER任务

使用的数据集在这里E-Commercial NER Dataset / 电商NER数据集_数据集-阿里云天池 针对面向电商的命名实体识别研究&#xff0c;我们通过爬取搜集了淘宝商品文本的标题&#xff0c;并标注了4大类&#xff0c;9小类的实体类别。具体类型及实体数量如下 针对面向电商的命名实体…

解决:如何在opencv中得到与matlab立体标定一样的矫正图?(python版opencv)

目的&#xff1a;采用一样的标定参数&#xff0c;matlab中和opencv中的立体矫正图像是一样的吗&#xff1f;不一样的话怎么让它们一样&#xff1f; 结论&#xff1a;不一样。后文为解决方案。 原因&#xff1a;注意matlab的标定结果在matlab中的用法和在opencv中的用法不一样&a…

OpenCv-01

使用opencv对图像进行一些简单的处理 首先知道自己的工作目录 import os cwdos.getcwd() 命名一张图片 my_imagelenna.png 获得图片路径 image_pathos.path.join(cwd,my_image) import cv2 imagecv2.imread(my_image) #imread()函数将图片转换为ndarray数组 image.sh…

大数据新视界 --大数据大厂之 Snowflake 在大数据云存储和处理中的应用探索

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

探索华为云DataArts Insight:数据智能的新引擎

在快速发展的数字化时代&#xff0c;数据已经成为企业最宝贵的资产。如何有效地管理和利用这些数据&#xff0c;以实现商业价值&#xff0c;是每个企业需要面对的重要挑战。华为云DataArts Insight平台应运而生&#xff0c;作为一款强大的数据智能解决方案&#xff0c;它帮助企…

STANFORD SR570 斯坦福 SR570 前置放大器

斯坦福&#xff08;Stanford&#xff09;STANFORD SR570前置放大器&#xff0c;作为音频与电子测量领域的璀璨明珠&#xff0c;以其无与伦比的性能与精湛的工艺&#xff0c;赢得了全球专业人士的广泛赞誉。这款前置放大器不仅是技术的集大成者&#xff0c;更是艺术与科学的完美…

论文学习 | 《电动汽车锂离子电池健康状态估计及寿命预测方法研究》

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本文主要对上述论文《电动汽车锂离子电池健康状态估计及寿命预测方法研究》进行学习与阅读总结&#xff0c;便于后续科研进一步学习&#xff08;纯小白自读汇总版&#xff09;。总体感觉这篇有点偏向数理方面&#xff0c;不过后…

【动手学深度学习】8.2. 文本预处理(个人向笔记)

本节将解析文本的常见预处理步骤包括&#xff1a;将文本作为字符串加载到内存中。将字符串拆分为词元&#xff08;如单词和字符&#xff09;。建立一个词表&#xff0c;将拆分的词元映射到数字索引。将文本转换为数字索引序列&#xff0c;方便模型操作。 1. 读取数据集 我们下…

leetcode-73-矩阵置零

题解&#xff1a; 1、获取矩阵的行数M与列数N&#xff1b; 2、 代码实现&#xff1a;

yolo目标检测和姿态识别和目标追踪

要检测摄像头画面中有多少人&#xff0c;人一排排坐着&#xff0c;像教室那样。由于摄像头高度和角度的原因&#xff0c;有的人会被遮挡。 yolo v5 首先需要下载yolo v5官方代码&#xff0c;可以克隆或下载主分支的代码&#xff0c;或者下载release中发布的。 简单说一下环境…

【python实战】利用代理ip爬取Alibaba海外版数据

引言 在跨境电商的业务场景中&#xff0c;数据采集是分析市场、了解竞争对手以及优化经营策略的重要环节。然而&#xff0c;随着越来越多企业依赖数据驱动决策&#xff0c;许多跨境电商平台为了保护自身数据&#xff0c;采取了更严格的防护措施。这些平台通过屏蔽大陆IP地址或部…

Idea、VS Code 如何安装Fitten Code插件使用

简介 Fitten Code是由非十大模型驱动的AI编程助手&#xff0c;它可以自动生成代码&#xff0c;提升开发效率&#xff0c;帮您调试Bug&#xff0c;节省您的时间。还可以对话聊天&#xff0c;解决您编程碰到的问题。免费且支持80多种语言&#xff1a;Python、C、Javascript、Typ…

python实战(一)——iris鸢尾花数据集分类

一、任务背景 本文是python实战系列专栏的第一篇文章&#xff0c;我们将从分类开始由浅入深逐步学习如何使用python完成常规的机器学习/深度学习任务。iris数据集是经典的机器学习入门数据集&#xff0c;许多分类任务教程都会以这个数据集作为示例&#xff0c;它的数据量是150条…

No.21 笔记 | WEB安全 - 任意文件绕过详解 part 3

&#xff08;一&#xff09;空格绕过 原理 Windows系统将文件名中的空格视为空&#xff0c;但程序检测代码无法自动删除空格&#xff0c;使攻击者可借此绕过黑名单限制。基于黑名单验证的代码分析 代码未对上传文件的文件名进行去空格处理&#xff0c;存在安全隐患。相关代码逻…

【软考高级架构】关于分布式数据库缓存redis的知识要点汇总

一.分布式数据库的含义 分布式数据库缓存指的是在高并发的环境下&#xff0c;为了减轻数据库的压力和提高系统响应时间&#xff0c;在数据库系统和应用系统之间增加一个独立缓存系统。 二.常见的缓存技术 &#xff08;1&#xff09;MemCache: Memcache是一个高性能的分布式的内…

openlayers 封装加载本地geojson数据 - vue3

Geojson数据是矢量数据&#xff0c;主要是点、线、面数据集合 Geojson数据获取&#xff1a;DataV.GeoAtlas地理小工具系列 实现代码如下&#xff1a; import {ref,toRaw} from vue; import { Vector as VectorLayer } from ol/layer.js; import { Vector as VectorSource } fr…

html全局属性、框架标签

常用的全局属性&#xff1a; 属性名含义id 给标签指定唯一标识&#xff0c;注意&#xff1a;id是不能重复的。 作用&#xff1a;可以让label标签与表单控件相关联&#xff1b;也可以与css、JavaScript配合使用。 注意&#xff1a;不能再以下HTML元素中使用&#xff1a;<hea…

Unity3D学习FPS游戏(4)重力模拟和角色跳跃

前言&#xff1a;前面两篇文章&#xff0c;已经实现了角色的移动和视角转动&#xff0c;但是角色并没有办法跳跃&#xff0c;有时候还会随着视角移动跑到天上。这是因为缺少重力系统&#xff0c;本篇将实现重力和角色跳跃功能。觉得有帮助的话可以点赞收藏支持一下&#xff01;…

社区养老实训室解决方案

一、实训室建设理念与目标 1.1 培养高质量养老专业人才 随着人口老龄化的不断加剧&#xff0c;对养老专业人才的需求呈现出日益增长的趋势。社区养老实训室的建设理念&#xff0c;正是基于这一背景&#xff0c;致力于培养一支既具备专业技能又拥有综合服务能力的高质量养老人…

gitlab不同账号间·仓库转移

背景&#xff1a;公司业务调整&#xff0c;原先在海外仓库的代码转移回国内 诉求&#xff1a;完整的保留项目记录 操作&#xff1a; 步骤一: 定位到需要迁移的原项目地址 步骤二&#xff1a;创建新项目 步骤三&#xff1a;打开命令行&#xff0c;创建好文件路径为需要clo…