【springboot源码】深度解析@Value赋值时机及底层原理

news2024/11/24 22:30:29

1.@Value使用

@Value主要是让我们程序动态的将外部值注入到bean中的,使用方式主要如下两种:

1.1@Value("${}"):可以获取对应属性文件中定义的属性值。

1.2@Value("#{}"):表示 SpEl 表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。

下面不做代码演示,只是看看它底层实现的时机

2.获取时机

2.1简单说下spring bean的生命周期

为啥要说下bean的生命周期,主要是@Value注入到我们的bean属性上,那么必然牵扯spring bean的生命周期,看到网上好多帖子直接讲解对应的实现方法,我觉得看完之后,大家会云里来雾里去

点击springboot的启动类,一直往下找到这个方法,点进去找到refresh方法

spring bean的生命周期主要就在refresh方法中

红线的方法其实就是初始化了所有的singleton beans

点击红线的方法,继续往下看:

先找到getBean=>doGetBean=>createBean=>doCreateBean

这里面主要看红线标注的三个方法

createBeanInstance方法,创建对象的

点击看instantiateBean=>instantiate=>BeanUtils.instantiateClass

public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
		Assert.notNull(ctor, "Constructor must not be null");
		try {
			ReflectionUtils.makeAccessible(ctor);
			return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
					KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
		}
		catch (InstantiationException ex) {
			throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
		}
		catch (IllegalAccessException ex) {
			throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
		}
		catch (IllegalArgumentException ex) {
			throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
		}
		catch (InvocationTargetException ex) {
			throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
		}
	}
可以看到创建对象的方法是通过构造器反射创建的

如果对于创建对象的方式比较迷惑,可以看另一篇文章【java基础】Java常见的创建对象方式_风卷残云_迟来大师的博客-CSDN博客

另外两个方法,下面讲

2.1@Value创建时机

2.1.1 看这个applyMergedBeanDefinitionPostProcessors方法

这个是后置处理器,去收集/预解析属性元数据

这个方法会调用AutowiredAnnotationBeanPostProcessor类的这个postProcessMergedBeanDefinition方法,然后调用这个findAutowiringMetadata方法

findAutowiringMetadata 找到注入的元素

下面看红线的方法,这个是核心方法

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
    /**
     * 如果没有 Autowired Value 注解信息就返回 EMPTY
     * this.autowiredAnnotationTypes.add(Autowired.class);
     * this.autowiredAnnotationTypes.add(Value.class);
     */
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;
    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
        // 遍历Class中的所有field,根据注解判断每个field是否需要被注入
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // 看看field是不是有注解@Autowired 或 @Value
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 不支持静态类
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                // 确定带注解的字段是否存在required并且是true 默认是true
                boolean required = determineRequiredStatus(ann);
                // AutowiredFieldElement 对象包装一下
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        // 遍历Class中的所有method,根据注解判断每个method是否需要注入
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 桥接方法 什么是桥接方法 大概查了查跟泛型方法有关系
            // 我猜的哈 比如某一个泛型方法 没有具体的实现的话 不知道注入何种类型 就会略过吧  知道的还请告知哈
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // 看看方法是不是有注解@Autowired 或 @Value
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // 静态方法略过
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                // 参数为空的方法略过
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                method);
                    }
                }
                // 判断是不是有 required
                boolean required = determineRequiredStatus(ann);
                // 获取目标class中某成员拥有读或写方法与桥接方法一致的PropertyDescriptor
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // AutowiredMethodElement 对象包装一下
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
        elements.addAll(0, currElements);
        // 递归调用
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);
    // 包装成 InjectionMetadata 对象  targetClass属性就是当前的类   injectedElements属性就是分析的字段或者方法
    return InjectionMetadata.forElements(elements, clazz);
}

看着这个代码

AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);

找到自动注入的注解,进去看下:

@Nullable
	private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
		if (ao.getAnnotations().length > 0) {  // autowiring annotations have to be local
			for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
				AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
				if (attributes != null) {
					return attributes;
				}
			}
		}
		return null;
	}

这个里面有一个全局变量

this.autowiredAnnotationTypes

看下这个值是咋来的,查看AutowiredAnnotationBeanPostProcessor这个类的构造函数

很清晰的告诉你了,我们要看的注解

从上面可以看到这个buildAutowiringMetadata方法会对类的属性进行遍历以及父亲的递归,对于字段会忽略掉static修饰的,对于方法会也会忽略掉static以及参数为空的。最后解析到的属性会包装成 AutowiredFieldElement ,方法会包装成 AutowiredMethodElement ,最后统一放进集合中,包装成 InjectionMetadata 对象返回,并放进缓存

2.1.2看populateBean这个方法

点击去看调用这个postProcessPropertyValues方法

继续往下看

element.inject(target, beanName, pvs)方法
调用对象对应的方法进行注入 属性就是 AutowiredFieldElement  方法就是 AutowiredMethodElement

点下去主要看属性

第一个红线获取属性值

第二个红线注入属性值

2.1.3看下beanFactory.resolveDependency这个获取属性值方法

doResolveDependency这个是核心
@Nullable
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}

			Class<?> type = descriptor.getDependencyType();
			// 如果是 @Value 注解元素,则获取 value 值
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			if (value != null) {
				if (value instanceof String) {
					// 占位符解析
					String strVal = resolveEmbeddedValue((String) value);
					BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
					// SpEL 解析
					value = evaluateBeanDefinitionString(strVal, bd);
				}
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}

			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
			if (multipleBeans != null) {
				return multipleBeans;
			}

			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			if (matchingBeans.isEmpty()) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				return null;
			}

			String autowiredBeanName;
			Object instanceCandidate;

			if (matchingBeans.size() > 1) {
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				if (autowiredBeanName == null) {
					if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
						return descriptor.resolveNotUnique(type, matchingBeans);
					}
					else {
						// In case of an optional Collection/Map, silently ignore a non-unique case:
						// possibly it was meant to be an empty collection of multiple regular beans
						// (before 4.3 in particular when we didn't even look for collection beans).
						return null;
					}
				}
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			}
			else {
				// We have exactly one match.
				Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
				autowiredBeanName = entry.getKey();
				instanceCandidate = entry.getValue();
			}

			if (autowiredBeanNames != null) {
				autowiredBeanNames.add(autowiredBeanName);
			}
			if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
			Object result = instanceCandidate;
			if (result instanceof NullBean) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				result = null;
			}
			if (!ClassUtils.isAssignableValue(type, result)) {
				throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
			}
			return result;
		}
		finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}

看下resolveEmbeddedValue方法

红线就取到值了

3.结论:

经过源码的层层解析,大家已经看到了,@Value取值实在对象创建完成后,在后置处理器中进行赋值的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1029748.html

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

相关文章

【操作系统】聊聊什么是CPU上下文切换

对于linux来说&#xff0c;本身就是一个多任务运行的操作系统&#xff0c;运行远大于CPU核心数的程序&#xff0c;从用户视角来看是并发执行&#xff0c;而在CPU视角看其实是将不同的CPU时间片进行分割&#xff0c;每个程序执行一下&#xff0c;就切换到别的程序执行。那么这个…

[激光原理与应用-70]:AD8512运算放大器的工作原理与连接电路

目录 概述&#xff1a; 一、AD8512的功能与主要特征 一、AD8510 1.1 管脚说明 1.2 电源电压 1.3 输入信号电压 1.4 偏置电压 1.5 参考连接 1.6 放大倍数 1.7 参考电路 二、AD8512 1.1 管脚说明 1.2 AD8512的电路连接 1.3 如何调整放大倍数 1.4 AD8512半波与全波…

基于 Socket 网络编程

基于 Socket 网络编程 前言一、基于Socket的网络通信传输&#xff08;传输层&#xff09;二、UDP 的数据报套接字编程1、UDP 套接字编程 API2、使用 UDP Socket 实现简单通信 三、TCP 流套接字编程1、TCP 流套接字编程 API2、使用 TCP Socket 实现简单通信3、使用 Tcp 协议进行…

【数据链路层】网络基础 -- MAC帧协议与ARP协议

数据链路层认识以太网以太网帧格式(MAC帧)认识MAC地址对比理解MAC地址和IP地址认识MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对于TCP协议的影响 再谈局域网转发原理&#xff08;基于协议&#xff09;ARP协议ARP协议的作用ARP协议的工作流程ARP数据报的格式 数据链路层 用于两…

网工内推 | 雄岸区块链集团,网安工程师,HCIE-Security、CISP优先

01 雄岸区块链集团 招聘岗位&#xff1a;网络安全测试工程师 职责描述&#xff1a; 1、负责安全测试工作&#xff0c;包括渗透测试、漏洞分析、攻防演练和安全评估等。 2、发掘业务系统安全问题&#xff0c;跟进安全整改。 3、出现网络攻击或安全事件时&#xff0c;进行紧急…

Tensorrt8.6.1安装

环境配置&#xff1a;Tensorrt8.6.1 &#xff0c;cuda11.6, cudnn8.6.0, torch1.13.1cu116, torchvision0.14.1cu116 1.先把cuda11.6, cudnn8.6.0, torch1.13.1cu116, torchvision0.14.1cu116这几个安装完之后 2.下载Tensorrt8.6.1&#xff0c;复制移动以下文件 将include中头…

Nat. Rev. Bioeng. | 中山大学左涛组详述肠道微生态工程化改造

肠道微生态工程化改造 Engineering the gut microbiome Review Article, 2023-6-16, Nature Reviews Bioengineering DOI&#xff1a;10.1038/s44222-023-00072-2 原文链接&#xff1a;https://www.nature.com/articles/s44222-023-00072-2 第一作者&#xff1a;Xiaowu Bai&…

近距离看GPU计算-1

文章目录 前言1.什么是GPU及其分类1.独立GPU(Discrete GPU)2.集成GPU(Integrated GPU)3.移动GPU(Mobile GPU) 2.GPU绘制流水线3.GPU计算的演进之旅1.CUDA的发明2.统一可编程单元3.浮点计算的标准化4.随机存取数据5.存储支持ECC 前言 转自 GPU and Computing 公众号 在前面文章…

Java实现单链表

目录 一.单链表 二.单链表基本操作的实现 1.单链表类、属性的定义 2.求链表长度 3.链表是否包含值为key的节点 4.添加元素 5.删除节点 6.清空链表 三、完整代码 一.单链表 链表是一种在物理存储结构上非连续的存储结构&#xff0c;数据元素的逻辑顺序通过链表中的引用…

【深度学习】卷积神经网络(LeNet)【文章重新修改中】

卷积神经网络 LeNet 前言LeNet 模型代码实现MINST代码分块解析1 构建 LeNet 网络结构2 加载数据集3 初始化模型和优化器4 训练模型5 训练完成 完整代码 Fashion-MINST代码分块解析1 构建 LeNet 网络结构2 初始化模型参数3 加载数据集4 定义损失函数和优化器5 训练模型 完整代码…

AIDAO,将会引领我们走向何方?

人工智能&#xff08;AI&#xff09;和分布式自治组织&#xff08;DAO&#xff09;都是区块链赛道的热门项目之一&#xff0c;他们看似在不同的领域独立发展&#xff0c;然而&#xff0c;它们之间也存在着巨大的协同潜力。 未来&#xff0c;AI有望成为推动DAO发展的重要动力&a…

【Java 基础篇】Java并发包详解

多线程编程是Java开发中一个重要的方面&#xff0c;它能够提高程序的性能和响应能力。然而&#xff0c;多线程编程也伴随着一系列的挑战&#xff0c;如线程安全、死锁、性能问题等。为了解决这些问题&#xff0c;Java提供了一套强大的并发包。本文将详细介绍Java并发包的各个组…

Docker网络问题:容器无法访问外部网络

Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f; Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f;摘要 &#x1f914;引言 &#x1f310;正文 &#x1f913;为什么容器无法访问外部网络&#xff1f; &#x1f615;1. 网络配置错误2. 防火墙设置3…

WebGL 选中物体

目录 前言 如何实现选中物体 示例程序&#xff08;PickObject.js&#xff09; 代码详解 gl.readPixels&#xff08;&#xff09;函数规范 示例效果 前言 有些三维应用程序需要允许用户能够交互地操纵三维物体&#xff0c;要这样做首先就得允许用户选中某个物体。对物体…

三维建模软件Cinema 4D 2024 mac(c4d2024)中文版特点

Cinema 4D 2024 mac是一款专业的三维建模、动画和渲染软件&#xff0c;c4d2024 可以用于电影制作、广告设计、工业设计等领域。 Cinema 4D 2024具有强大的建模工具&#xff0c;可以创建各种复杂的几何体&#xff0c;包括多边形网格、NURBS曲线和体积对象。它还提供了丰富的材质…

Docker快速入门到项目部署,MySQL部署+Nginx部署

《Docker》是微服务在企业落地的最后一块拼图。微服务项目由于拆分粒度细&#xff0c;服务部署环境复杂&#xff0c;部署实例很多&#xff0c;维护困难。而Docker则可以解决项目部署的各种环境问题&#xff0c;让开发、运维一体化&#xff0c;真正实现持续集成、持续部署。大大…

一文详解自动化测试框架知识

前言 自动化测试因其节约成本、提高效率、减少手动干预等优势已经日渐成为测试人员的“潮流”&#xff0c;从业人员日益清楚地明白实现自动化框架是软件自动化项目成功的关键因素之一。 我们将从什么是真正的自动化测试框架、自动化脚本如何工作以及自动化测试框架会如何在测…

three.js——辅助器AxesHelper和轨道控制器OrbitControls的使用

辅助器AxesHelper和轨道控制器OrbitControls的使用 前言效果图1、辅助器AxesHelper:是物体出现辅助的x/y/z轴2、轨道控制器OrbitControls2.1导入OrbitControls文件2.2 使用2.3 如果OrbitControls改变了相机参数&#xff0c;重新调用渲染器渲染三维场景 前言 1、AxesHelper 官网…

搭建GraphQL服务

js版 GraphQL在 NodeJS 服务端中使用最多 安装graphql-yoga: npm install graphql-yoga 新建index.js: const {GraphQLServer} require("graphql-yoga")const server new GraphQLServer({ typeDefs: type Query { hello(name:String):String! …

由于找不到vcruntime140_1.dll怎么修复,详细修复步骤分享

在使用电脑过程中&#xff0c;可能会遇到一些错误提示&#xff0c;其中之一是找不到vcruntime140_1.dll的问题。这使得许多用户感到困扰&#xff0c;不知道该如何解决这个问题。小编将详细介绍vcruntime140_1.dll的作用以及解决找不到该文件的方法&#xff0c;帮助你摆脱困境。…