InstantiationAwareBeanPostProcessor和@Autowired,@Value的依赖注入

本文深入解析Spring框架中@Autowired和@Value注解的依赖注入过程,从InjectionMetadata对象的生成到依赖注入的实现,详细阐述了如何通过BeanFactory完成依赖查找与注入。

在上篇博文中https://blog.youkuaiyun.com/luoyang_java/article/details/85709475我们说了,MergedBeanDefinitionPostProcessor扫描了bean,并且把bean中有@Autowired和@Value注解的field和method封装成了InjectionMetadata对象。这篇博文,我们来说说,spring是怎么根据封装的InjectionMetadata对象来镜像IOC依赖注入的。

首先IOC依赖注入的入口是:

populateBean(beanName, mbd, instanceWrapper);

进入这个方法,其中@Autowired和@Value的依赖注入是在

 实现了InstantiationAwareBeanPostProcessor接口的bean,是@Autowired和@Value的依赖注入的关键。

同样是这个类,但是调用的方法是postProcessPropertyValues,我们点进这个方法。

 首先根据beanName找到这个bean对应的InjectionMetadata对象,这个对象中封装了需要依赖注入的属性或者方法。然后调用inject方法。点进去。

 这里循环InjectionMetadata里面的injectedElements,上篇博文中我们讲过,injectedElements是封装了有注解的Field和Method,并且有PropertyDescriptor 描述对象。就是挨个对每一个Field或者Method进行依赖注入处理。

这里我们对Field依赖注入看看源码:

		@Override
		protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			try {
				Object value;
				if (this.cached) {
					value = resolvedCachedArgument(beanName, this.cachedFieldValue);
				}
				else {
					DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
					desc.setContainingClass(bean.getClass());
					Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
					TypeConverter typeConverter = beanFactory.getTypeConverter();
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
					synchronized (this) {
						if (!this.cached) {
							if (value != null || this.required) {
								this.cachedFieldValue = desc;
								registerDependentBeans(beanName, autowiredBeanNames);
								if (autowiredBeanNames.size() == 1) {
									String autowiredBeanName = autowiredBeanNames.iterator().next();
									if (beanFactory.containsBean(autowiredBeanName)) {
										if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
											this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName);
										}
									}
								}
							}
							else {
								this.cachedFieldValue = null;
							}
							this.cached = true;
						}
					}
				}
				if (value != null) {
					ReflectionUtils.makeAccessible(field);
					field.set(bean, value);
				}
			}
			catch (Throwable ex) {
				throw new BeanCreationException("Could not autowire field: " + field, ex);
			}
		}

 这个代码是Field的依赖注入过程,其中的关键是@Autowired是要根据Field的类型,把该类型对应的实例从BeanFactory中拿到对应实例。这个代码在:value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

点进去看:

 点进这个代码中,我们来看看关键代码:

String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);

我们都知道这个代码是根本类型type找到这个type对应的所有bean的名称,

 然后在这里根据bean的名称获取到bean的实例,然后建立映射关系。entry.getValue()是从对应的map中拿到了这个类型对应的实例。这样@Autowired对应的类型对应的实例从BeanFactory找到了。接下来就需要设置到Field中了。

在这里一个简单的反射,根据bean和value把对应的value值设置到了对应的bean中。这样基于@Autowired的依赖注入就完成了。这个是Field的依赖注入。而方法的依赖注入基本上跟Field的差不多,只是方法的是method.invoke()调用而已,获取值的过程是一模一样的。

这个就是@Autowired和@Value依赖注入过程,谢谢大家!!

 

 

在 Spring 框架中,`@Value` 注解用于将配置文件中的属性值注入到 Bean 的字段中。然而,在实际使用过程中,有时会遇到 `@Value` 无法正确获取值的情况。以下是对此类问题的排查与解决方法的详细分析。 ### 一、`@Value` 注解无法获取值的常见原因 1. **Bean 未被 Spring 容器管理** 如果目标类没有被 Spring 容器管理(例如缺少 `@Component`、`@Service`、`@Repository` 或 `@Configuration` 等注解),则 `@Value` 注解不会生效。Spring 只会对容器中的 Bean 进行依赖注入处理。[^1] 2. **手动使用 `new` 创建对象** 如果对象是通过 `new` 关键字手动创建的,而不是通过 Spring 容器获取的实例,那么 `@Value` 注解也不会生效。Spring 无法对非容器管理的对象进行属性注入。[^1] 3. **字段被 `static` 或 `final` 修饰** `@Value` 注解不能用于 `static` 或 `final` 修饰的字段。因为 Spring 依赖于实例化后的字段注入机制,而 `static` 字段属于类级别,`final` 字段在构造后不可变,这都导致 Spring 无法完成注入。 4. **代理类导致字段未被注入** 当类使用了如 `@Async`、`@Transactional` 等注解时,Spring 会使用 CGLIB 动态代理机制创建代理类。在某些情况下,代理类的创建过程会绕过正常的构造函数初始化流程,从而导致 `@Value` 注入失败。[^4] 5. **Bean 的生命周期干预不当** Spring 在 Bean 的实例化初始化阶段,会调用 `InstantiationAwareBeanPostProcessor` 接口的 `postProcessBeforeInstantiation` `postProcessAfterInstantiation` 方法。如果这些方法返回了非 `null` 或非 `true` 的值,则可能导致 `@Value` 注入失败。[^3] 6. **配置项缺失或路径错误** 如果配置文件中缺少对应的属性键,或者 `@Value` 注解中引用的键路径错误(如拼写错误、未使用占位符语法 `${}`),则注入值将为 `null`。例如,`@Value("${spring.redis.host}")` 依赖于 `application.properties` 中存在 `spring.redis.host` 配置项。[^2] ### 二、解决方案与建议 1. **确保 Bean 被 Spring 管理** 为目标类添加 `@Component`、`@Service`、`@Repository` 或 `@Configuration` 等注解,确保其被 Spring 容器管理。[^1] 2. **避免手动创建对象** 应通过 `@Autowired` 注入 Bean,或通过 `ApplicationContext.getBean()` 方法获取实例,避免使用 `new` 创建对象。 3. **避免在 `static` 或 `final` 字段上使用 `@Value`** 如果确实需要使用静态字段存储配置值,可以通过一个非静态字段注入后再赋值给静态字段,或使用 `@PostConstruct` 方法进行初始化。[^1] 4. **检查代理机制对字段注入的影响** 对于使用了 `@Async` 或 `@Transactional` 的类,建议避免在代理类中直接使用 `@Value` 注入字段,或者确保字段在代理类中能被正确初始化。[^4] 5. **合理使用 `@PostConstruct` 或构造方法初始化** 如果字段依赖注入的值进行初始化,可以将初始化逻辑放在 `@PostConstruct` 注解的方法中,或通过构造方法注入。[^3] 6. **使用 `@ConfigurationProperties` 替代 `@Value`** 对于需要集中管理的配置项,推荐使用 `@ConfigurationProperties` 注解,结合 `@Validated` 使用可以对配置项进行校验,提前发现配置缺失或格式错误问题。 示例代码如下: ```java @Component @ConfigurationProperties(prefix = "spring.redis") @Validated public class RedisProperties { @NotBlank(message = "Host is required") private String host; private int port; // Getter and Setter } ``` 7. **日志调试与异常捕获** 在启动应用时,启用 Spring 的调试日志(如 `org.springframework.context.support.AbstractResourceBasedMessageSource`)有助于发现注入失败的具体原因。同时,可以在字段注入后添加日志输出,确认是否成功注入。 ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值