springboot中@Value的工作原理

本文深入分析了SpringBoot环境下@Value注解的工作原理,详细解释了其如何在bean实例化和初始化阶段,利用AutowiredAnnotationBeanPostProcessor实现属性绑定。
部署运行你感兴趣的模型镜像

我们知道springboot中的Bean组件的成员变量(属性)如果加上了@Value注解,可以从有效的配置属性资源中找到配置项进行绑定,那么这一切是怎么发生的呢?下文将简要分析一下@Value的工作原理。

springboot版本: springboot-2.0.6.RELEASE


概述

springboot启动过程中,有两个比较重要的过程,如下:
1 扫描,解析容器中的bean注册到beanFactory上去,就像是信息登记一样。
2 实例化、初始化这些扫描到的bean。

@Value的解析就是在第二个阶段。BeanPostProcessor定义了bean初始化前后用户可以对bean进行操作的接口方法,它的一个重要实现类AutowiredAnnotationBeanPostProcessor正如javadoc所说的那样,为bean中的@Autowired@Value注解的注入功能提供支持。

解析流程

调用链时序图

@Value解析过程中的主要调用链,我用以下时序图来表示:
在这里插入图片描述
这里先简单介绍一下图上的几个类的作用。

AbstractAutowireCapableBeanFactory: 提供了bean创建,属性填充,自动装配,初始胡。支持自动装配构造函数,属性按名称和类型装配。实现了AutowireCapableBeanFactory接口定义的createBean方法。

AutowiredAnnotationBeanPostProcessor: 装配bean中使用注解标注的成员变量,setter方法, 任意的配置方法。比较典型的是@Autowired注解和@Value注解。

InjectionMetadata: 类的注入元数据,可能是类的方法或属性等,在AutowiredAnnotationBeanPostProcessor类中被使用。

AutowiredFieldElement: 是AutowiredAnnotationBeanPostProcessor的一个私有内部类,继承InjectionMetadata.InjectedElement,描述注解的字段。

StringValueResolver: 一个定义了处置字符串值的接口,只有一个接口方法resolveStringValue,可以用来解决占位符字符串。本文中的主要实现类在PropertySourcesPlaceholderConfigurer#processProperties方法中通过lamda表达式定义的。供ConfigurableBeanFactory类使用。

PropertySourcesPropertyResolver: 属性资源处理器,主要功能是获取PropertySources属性资源中的配置键值对。

PropertyPlaceholderHelper: 一个工具类,用来处理带有占位符的字符串。形如${name}的字符串在该工具类的帮助下,可以被用户提供的值所替代。替代途经可能通过Properties实例或者PlaceholderResolver(内部定义的接口)。

PropertyPlaceholderConfigurerResolver: 上一行所说的PlaceholderResolver接口的一个实现类,是PropertyPlaceholderConfigurer类的一个私有内部类。实现方法resolvePlaceholder中调用了外部类的resolvePlaceholder方法。

调用链说明

这里主要介绍一下调用链中的比较重要的方法。

AbstractAutowireCapableBeanFactory#populateBean方法用于填充bean属性,执行完后可获取属性装配后的bean。

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {       
...
if (hasInstAwareBpps) {
	// 遍历所有InstantiationAwareBeanPostProcessor实例设置属性字段值。
	for (BeanPostProcessor bp : getBeanPostProcessors()) {
		// AutowiredAnnotationBeanPostProcessor会进入此分支
		if (bp instanceof InstantiationAwareBeanPostProcessor) {
			InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
			pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
		//上行代码执行后,bw.getWrappedInstance()就得到了@Value注解装配属性后的bean了
			if (pvs == null) {
				return;
			}
		}
	}
}
...
}

InjectionMetadata#inject逐个装配bean的配置属性。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
	    // 依次注入属性
		for (InjectedElement element : elementsToIterate) {
			if (logger.isDebugEnabled()) {
				logger.debug("Processing injected element of bean '" + beanName + "': " + element);
			}
			element.inject(target, beanName, pvs);
		}
	}
}

PropertyPlaceholderHelper#parseStringValue解析属性值

/**
 *  一个参数示例 value = "${company.ceo}"
 *
 */
protected String parseStringValue(
		String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

	StringBuilder result = new StringBuilder(value);

	// this.placeholderPrefix = "${"
	int startIndex = value.indexOf(this.placeholderPrefix);
	while (startIndex != -1) {
		// 占位符的结束位置,以value = "${company.ceo}"为例,endIndex=13
		int endIndex = findPlaceholderEndIndex(result, startIndex);
		if (endIndex != -1) {
			// 获取{}里的真正属性名称,此例为"company.ceo"
			String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
			String originalPlaceholder = placeholder;
			if (!visitedPlaceholders.add(originalPlaceholder)) {
				throw new IllegalArgumentException(
						"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
			}
			// Recursive invocation, parsing placeholders contained in the placeholder key.
			// 递归调用本方法,因为属性键中可能仍然有占位符
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// Now obtain the value for the fully resolved key...
			// 获取属性键placeholder对应的属性值
			String propVal = placeholderResolver.resolvePlaceholder(placeholder);
			// 此处逻辑是当company.ceo=${bi:li}时,company.ceo最终被li所替代的原因
			// 所以配置文件中,最好不要出现类似${}的东西,因为它本身就会被spring框架所解析
			if (propVal == null && this.valueSeparator != null) {
				int separatorIndex = placeholder.indexOf(this.valueSeparator);
				if (separatorIndex != -1) {
					String actualPlaceholder = placeholder.substring(0, separatorIndex);
					String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
					propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
					if (propVal == null) {
						propVal = defaultValue;
					}
				}
			}
			if (propVal != null) {
				// Recursive invocation, parsing placeholders contained in the
				// previously resolved placeholder value.
				propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
				// 将${company.ceo}替换为li
				result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
				if (logger.isTraceEnabled()) {
					logger.trace("Resolved placeholder '" + placeholder + "'");
				}
				startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
			}
			else if (this.ignoreUnresolvablePlaceholders) {
				// Proceed with unprocessed value.
				startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
			}
			else {
				throw new IllegalArgumentException("Could not resolve placeholder '" +
						placeholder + "'" + " in value \"" + value + "\"");
			}
			visitedPlaceholders.remove(originalPlaceholder);
		}
		else {
			startIndex = -1;
		}
	}

	return result.toString();
}

总结

@Value注解标注的bean属性装配是依靠AutowiredAnnotationBeanPostProcessor在bean的实例化、初始化阶段完成的。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>我们被要求解释SpringBoot中的几个核心注解。根据引用内容,我们可以整理如下:1.@SpringBootApplication:这是一个组合注解,它结合了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan三个注解的功能。用于标识一个主程序类,表示这是一个SpringBoot应用。2.@Configuration:标记一个类作为配置类,替代传统的XML配置文件。被注解的类内部包含一个或多个被@Bean注解的方法,这些方法会被Spring容器处理,用于构建Bean定义。3.@EnableAutoConfiguration:启用SpringBoot的自动配置机制。它尝试根据项目添加的jar依赖自动配置Spring应用。4.@ComponentScan:自动扫描并注册当前包及其子包下的组件(如@Component,@Service,@Repository,@Controller等)为SpringBean。5.@RestController:一个组合注解,它结合了@Controller和@ResponseBody。用于标记一个类作为控制器,并且该类中所有方法的返回值都直接写入HTTP响应体,通常用于构建RESTfulWeb服务。6.@Value:用于注入属性值,可以注入基本类型的值,也可以使用SpEL表达式。通常用于从配置文件(如application.properties)中读取值。下面我们逐一详细说明:</think>### Spring Boot 核心注解详解 #### 1. `@SpringBootApplication` **功能**:Spring Boot 应用的入口注解,**组合了三个核心注解**: - `@SpringBootConfiguration`:标记主配置类(本质是 `@Configuration` 的变体) - `@EnableAutoConfiguration`:启用自动配置机制 - `@ComponentScan`:自动扫描当前包及子包的组件 **用途**: ```java @SpringBootApplication public class MyApp { // 主启动类 public static void main(String[] args) { SpringApplication.run(MyApp.class, args); } } ``` > 简化配置,一键启动Spring Boot应用[^1][^5] --- #### 2. `@Configuration` **功能**:声明类为**配置类**,替代XML配置文件 **核心机制**: - 类中方法使用 `@Bean` 注册组件到容器 - `proxyBeanMethods` 控制代理行为(默认true保证单例) ```java @Configuration(proxyBeanMethods = false) // 关闭代理提升性能 public class AppConfig { @Bean // 将返回值注册为Bean public DataSource dataSource() { return new HikariDataSource(); } } ``` > 相当于Spring的 `<beans>` 标签[^3][^4] --- #### 3. `@EnableAutoConfiguration` **功能**:**启用自动配置**的核心注解 **工作原理**: 1. 通过 `META-INF/spring.factories` 加载配置类 2. 根据类路径依赖自动配置组件(如检测到JDBC驱动则配DataSource) **典型场景**: - 自动配置数据源、Web服务器、安全框架等 - 可通过 `exclude` 禁用特定配置: ```java @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) ``` > 极大减少手动配置量[^2][^5] --- #### 4. `@ComponentScan` **功能**:**自动扫描组件**并注册为Bean **扫描范围**: - 默认扫描**当前包及其子包** - 可自定义路径:`@ComponentScan("com.example.package")` **识别注解**: - `@Component`(通用组件) - `@Service`(服务层) - `@Repository`(数据层) - `@Controller`(控制层) > 替代XML中的 `<context:component-scan>`[^5] --- #### 5. `@RestController` **功能**:**RESTful控制器**的专用注解,组合: - `@Controller`:声明为控制器 - `@ResponseBody`:方法返回值直接写入HTTP响应体 **示例**: ```java @RestController @RequestMapping("/api") public class UserController { @GetMapping("/users") public List<User> getUsers() { // 自动转为JSON响应 return userService.findAll(); } } ``` > 适用于构建API接口[^5] --- #### 6. `@Value` **功能**:**注入属性值**到字段或方法参数 **数据来源**: - 配置文件(如 `application.properties`) - 环境变量 - 命令行参数 **使用方式**: ```java @Component public class EmailService { @Value("${email.host:smtp.default.com}") // 默认值语法 private String host; @Value("#{systemProperties['user.timezone']}") // SpEL表达式 private String timezone; } ``` **配置文件**: ```properties # application.properties email.host=smtp.example.com ``` > 实现配置与代码解耦[^5] --- ### 注解协作关系 ```mermaid graph LR A[@SpringBootApplication] --> B[@SpringBootConfiguration] A --> C[@EnableAutoConfiguration] A --> D[@ComponentScan] B --> E[@Configuration] D --> F[@RestController] F --> G[@Value] ```
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值