spring-reading./spring-annotation/spring-annotation-value/README.md

878 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## @Value
- [@Value](#value)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [七、源码分析](#七源码分析)
- [前置条件](#前置条件)
- [收集阶段](#收集阶段)
- [注入阶段](#注入阶段)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133815435) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Value源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-value)
### 二、注解描述
`@Value` 注解是一个非常有用的功能它允许我们从配置文件中注入属性值到Java类的字段或方法参数中。这样我们可以将配置和代码分离使应用更容易配置和维护。
### 三、注解源码
`@Value`注解是 Spring 框架自 3.1 版本开始引入的一个核心注解主要目的是允许我们在Spring管理的bean中直接注入来自各种源如属性文件、系统属性等的值而不需要显式地编写代码来解析这些值。
```java
/**
* 用于字段或方法/构造函数参数级别的注解,
* 表示被注解元素的默认值表达式。
*
* 通常用于基于表达式或属性的依赖注入。
* 也支持动态解析处理器方法参数例如在Spring MVC中。
*
* 常见的使用场景是使用 `#{systemProperties.myProp}` 这样的SpELSpring表达式语言表达式来注入值。
* 或者,使用 `${my.app.myProp}` 这样的属性占位符来注入值。
*
* 注意,@Value 注解的实际处理是由 BeanPostProcessor 执行的,
* 这意味着我们**不能**在 BeanPostProcessor 或 BeanFactoryPostProcessor 类型中使用 @Value。
* 请查阅 AutowiredAnnotationBeanPostProcessor 类的javadoc默认检查此注解的存在
*
* @author Juergen Hoeller
* @since 3.0
* @see AutowiredAnnotationBeanPostProcessor
* @see Autowired
* @see org.springframework.beans.factory.config.BeanExpressionResolver
* @see org.springframework.beans.factory.support.AutowireCandidateResolver#getSuggestedValue
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
/**
* 实际的值表达式,如 `#{systemProperties.myProp}`
* 或属性占位符,如 `${my.app.myProp}`。
*/
String value();
}
```
### 四、主要功能
1. **提供属性注入**
+ 允许从不同的配置源(如属性文件、系统属性等)直接向 Spring 管理的 beans 中注入值。
2. **支持表达式**
- SpEL (Spring Expression Language) 表达式:例如,`#{systemProperties.myProp}` 可以从系统属性中获取名为 `myProp` 的值。
- 属性占位符:例如,`${my.app.myProp}` 可以从预定义的配置源,如 `application.properties``application.yml` 文件,获取名为 `my.app.myProp` 的属性值。
3. **动态值解析**
+ 与只能在启动时设置静态值相比,`@Value` 注解可以解析动态表达式,从而为字段或构造函数参数提供动态值。
4. **用于字段、方法参数、构造函数参数和注解**
+ 它可以被应用到这些元素上,以提供必要的值。
5. **与其他注解协同工作**
+ 尽管 `@Value` 本身是用于注入值的,但它经常与其他如 `@Component`、`@Service` 和 `@Controller` 之类的 Spring 注解一起使用,以创建完全由 Spring 管理和配置的 beans。
6. **与属性解析器配合**
+ 为了正确解析 `@Value` 中的表达式Spring 应用上下文中需要有一个属性解析器,例如 `PropertySourcesPlaceholderConfigurer`。在 Spring Boot 项目中,这已经默认配置好了。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个`MyConfiguration`组件类。
```java
public class ValueApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
```
这里使用`@Bean`注解定义了一个Bean是为了确保 `MyService` 被 Spring 容器执行,另外使用`@PropertySource`注解从类路径下的`application.properties`文件中加载属性。这意味着我们可以在这个文件中定义属性,然后在应用中使用`Environment`对象来访问它们。
```java
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
@Bean
public MyService myService(){
return new MyService();
}
}
```
`application.properties`文件在`src/main/resources`目录中,并添加以下内容。
```properties
app.name=My Spring Application
app.servers=server1,server2,server3
app.val1=10
app.val2=20
```
`MyService`类,展示了如何使用`@Value`注解的五种不同方式进行属性注入。
```java
public class MyService implements InitializingBean {
/**
* 直接注入值
*/
@Value("Some String Value")
private String someString;
/**
* 从属性文件中注入值方式
*/
@Value("${app.name}")
private String appName;
/**
* 使用默认值方式
*/
@Value("${app.description:我是默认值}")
private String appDescription;
/**
* 注入列表和属性
*/
@Value("#{'${app.servers}'.split(',')}")
private List<String> servers;
/**
* 使用Spring的SpEL
*/
@Value("#{${app.val1} + ${app.val2}}")
private int sumOfValues;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("直接注入值: " + someString);
System.out.println("从属性文件中注入值: " + appName);
System.out.println("使用默认值: " + appDescription);
System.out.println("注入列表和属性: " + servers);
System.out.println("使用Spring的SpEL: " + sumOfValues);
}
}
```
运行结果发现, `@Value` 注解都被正确地解析并注入了预期的值。
```java
直接注入值: Some String Value
从属性文件中注入值: My Spring Application
使用默认值: 我是默认值
注入列表和属性: [server1, server2, server3]
使用SpringSpEL: 30
```
### 六、时序图
~~~mermaid
sequenceDiagram
Title: @Value注解时序图
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName)<br>应用Bean定义的后置处理器
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessMergedBeanDefinition(beanDefinition,beanType,beanName)<br>处理已合并的Bean定义
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)<br>查找自动注入的元数据
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:buildAutowiringMetadata(clazz)<br>构建自动注入的元数据
AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalFields(clazz,fc)<br>处理类的本地字段
ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的字段
AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalMethods(clazz,fc)<br>处理类的本地方法
ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的方法
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:injectionMetadataCache.put(cacheKey, metadata)<br>将元数据存入缓存
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)<br>填充Bean的属性值
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName)<br>后处理Bean的属性
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)<br>再次查找自动注入的元数据
Note right of AutowiredAnnotationBeanPostProcessor:<br>从缓存中获取注入的元数据
AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(bean, beanName, pvs)<br>执行实际的属性注入
InjectionMetadata->>AutowiredFieldElement:inject(target, beanName, pvs)<br>注入特定的字段元素
AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName)<br>解析字段的值
AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)<br>解析字段的依赖
DefaultListableBeanFactory->>AutowiredFieldElement:返回解析后的value值<br>返回解析后的属性值
AutowiredFieldElement->>Field:field.set(bean, value)<br>设置Bean字段的值
~~~
### 七、源码分析
#### 前置条件
在Spring中`AutowiredAnnotationBeanPostProcessor`是处理`@Value`等注解的关键类,它实现了下述两个接口。因此,为了深入理解`@Value`的工作方式,研究这个类是非常有用的。简而言之,为了完全理解`@Value`的工作机制了解下述接口确实是必要的。这两个接口提供了对bean生命周期中关键阶段的干预从而允许进行属性注入和其他相关的操作。
1. **`MergedBeanDefinitionPostProcessor`接口**
+ 此接口提供的`postProcessMergedBeanDefinition`方法允许后处理器修改合并后的bean定义。合并后的bean定义是一个已经考虑了所有父bean定义属性的bean定义。对于`@Value`注解的处理,这一步通常涉及到收集需要被解析的`@Value`注解信息并准备对其进行后续处理。
+ 🔗 [MergedBeanDefinitionPostProcessor接口传送门](https://github.com/xuchengsheng/spring-reading/tree/master/spring-interface/spring-interface-mergedBeanDefinitionPostProcessor)
2. **`InstantiationAwareBeanPostProcessor`接口**
+ 此接口提供了几个回调方法允许后处理器在bean实例化之前和实例化之后介入bean的创建过程。特别是`postProcessProperties`方法允许后处理器对bean的属性进行操作。对于`@Value`注解这通常涉及到实际地解析注解中的表达式或属性占位符并将解析得到的值注入到bean中。
+ 🔗 [InstantiationAwareBeanPostProcessor接口传送门](https://github.com/xuchengsheng/spring-reading/tree/master/spring-interface/spring-interface-instantiationAwareBeanPostProcessor)
#### 收集阶段
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition`方法中主要确保给定的bean定义与其预期的自动装配元数据一致。
```java
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
// 对于给定的bean名称和类型它首先尝试查找相关的InjectionMetadata这可能包含了该bean的字段和方法的注入信息
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
// 使用找到的InjectionMetadata来验证bean定义中的配置成员是否与预期的注入元数据匹配。
metadata.checkConfigMembers(beanDefinition);
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata`方法中确保了始终为给定的bean名称和类获取最新和相关的`InjectionMetadata`,并利用缓存机制优化性能。
```java
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// 如果beanName为空则使用类名作为缓存键。
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// 首先尝试从并发缓存中获取InjectionMetadata。
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
// 检查获取到的元数据是否需要刷新。
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// 使用双重检查锁定确保线程安全。
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// 如果有旧的元数据,清除它。
if (metadata != null) {
metadata.clear(pvs);
}
// 为给定的类构建新的InjectionMetadata。
metadata = buildAutowiringMetadata(clazz);
// 将新构建的元数据更新到缓存中。
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
// 返回找到的或新构建的元数据。
return metadata;
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata`方法中,查找类及其所有父类中的字段和方法,以找出所有带有自动装配注解的字段和方法,并为它们创建一个统一的`InjectionMetadata`对象。
```java
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
// 检查类是否含有自动装配注解若无则直接返回空的InjectionMetadata。
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<>();
// 处理类中的所有字段。
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// 查找字段上的自动装配注解。
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;
}
boolean required = determineRequiredStatus(ann);
// 创建一个新的AutowiredFieldElement并加入到列表。
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 处理类中的所有方法。
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
// 查找方法上的自动装配注解。
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);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
// 创建一个新的AutowiredMethodElement并加入到列表。
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
// 将当前类的注入元素加入到总的注入元素列表的开头。
elements.addAll(0, currElements);
// 处理父类。
targetClass = targetClass.getSuperclass();
}
// 循环直至Object类。
while (targetClass != null && targetClass != Object.class);
// 返回为元素列表创建的新的InjectionMetadata。
return InjectionMetadata.forElements(elements, clazz);
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#autowiredAnnotationTypes`字段中,主要的用途是告诉`AutowiredAnnotationBeanPostProcessor`哪些注解它应该处理。当Spring容器解析bean定义并创建bean实例时如果这个bean的字段、方法或构造函数上的注解被包含在这个`autowiredAnnotationTypes`集合中,那么`AutowiredAnnotationBeanPostProcessor`就会对它进行处理。
```java
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Value.class);
// ... [代码部分省略以简化]
}
```
#### 注入阶段
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties`方法中用于处理bean属性的后处理特别是通过`@Value`等注解进行的属性注入。
```java
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 获取与bean名称和类相关的InjectionMetadata。
// 这包括该bean需要进行注入的所有字段和方法。
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 使用获取到的InjectionMetadata实际进行属性的注入。
metadata.inject(bean, beanName, pvs);
}
// 如果在注入过程中出现BeanCreationException直接抛出。
catch (BeanCreationException ex) {
throw ex;
}
// 捕获其他异常并以BeanCreationException的形式抛出提供详细的错误信息。
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
// 返回原始的PropertyValues因为这个方法主要关注依赖注入而不是修改属性。
return pvs;
}
```
在`org.springframework.beans.factory.annotation.InjectionMetadata#inject`方法中,主要目的是将所有需要注入的元素(例如带有`@Value`等注解的字段或方法注入到目标bean中。
```java
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()) {
// 遍历每个元素并注入到目标bean中。
for (InjectedElement element : elementsToIterate) {
// 对每个元素(字段或方法)执行注入操作。
element.inject(target, beanName, pvs);
}
}
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中首先检查字段的值是否已经被缓存。如果已缓存则从缓存中获取否则重新解析。然后它确保字段是可访问的特别是对于私有字段并将解析的值设置到目标bean的相应字段中。
```java
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 步骤1. 获取代表带有@Autowired注解的字段的Field对象。
Field field = (Field) this.member;
Object value;
// 步骤2. 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。
if (this.cached) {
try {
// 从缓存中获取已解析的字段值。
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// 如果缓存中的bean已被意外删除 -> 重新解析。
value = resolveFieldValue(field, bean, beanName);
}
}
else {
// 步骤3. 如果字段值未被缓存,直接解析。
value = resolveFieldValue(field, bean, beanName);
}
// 步骤4. 如果解析到的值不为null...
if (value != null) {
// 步骤4.1. 使字段可访问这是必要的特别是当字段是private时。
ReflectionUtils.makeAccessible(field);
// 步骤4.2. 实际将解析的值注入到目标bean的字段中。
field.set(bean, value);
}
}
```
首先来到`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中的步骤3。在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值。
```java
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
// ... [代码部分省略以简化]
Object value;
try {
// 通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
// ... [代码部分省略以简化]
return value;
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency`方法中,首先尝试获取一个延迟解析代理。如果无法获得,它会进一步尝试解析依赖。`doResolveDependency` 是实际进行解析工作的方法。
```java
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ... [代码部分省略以简化]
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中,首先从`DependencyDescriptor`获取注解值然后处理其中的字符串属性占位符和SpEL表达式。最后确保值根据目标字段或参数类型进行正确的类型转换并将其注入相应的位置。
```java
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ... [其他代码部分省略以简化]
try {
// 尝试快速解析依赖
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// 获取依赖的类型
Class<?> type = descriptor.getDependencyType();
// 步骤1. 获取依赖的建议值,例如@Value注解的值
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
// 如果建议的值是字符串类型
if (value instanceof String) {
// 步骤2. 解析嵌入的值,如处理属性占位符
String strVal = resolveEmbeddedValue((String) value);
// 获取与bean名称相关的BeanDefinition
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
// 步骤3. 对Bean定义字符串进行评估如处理SpEL表达式
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
// 步骤4. 获取类型转换器并进行必要的类型转换
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// ... [其他代码部分省略以简化]
}
// ... [其他代码部分省略以简化]
}
// ... [其他代码部分省略以简化]
}
```
首先来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤1。在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#getSuggestedValue`方法中,主要是用于解析与`DependencyDescriptor`相关的注解值,特别是`@Value`注解。如果字段或方法参数上有`@Value`注解,它会从注解中提取相应的值或表达式。
```java
@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
// 从描述符的注解中查找@Value注解提供的值
Object value = findValue(descriptor.getAnnotations());
// 如果在描述符的注解中没有找到,检查是否存在与此描述符关联的方法参数
if (value == null) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
// 如果存在方法参数,再从方法参数的注解中查找@Value提供的值
value = findValue(methodParam.getMethodAnnotations());
}
}
// 返回找到的值如果没有找到则返回null
return value;
}
```
在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#findValue`方法中,目标是在提供的注解集合中找到并返回`@Value`注解的值。如果没有找到它会返回null。
```java
protected Object findValue(Annotation[] annotationsToSearch) {
if (annotationsToSearch.length > 0) { // qualifier annotations have to be local
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
```
在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#extractValue`方法中,目的是从`AnnotationAttributes`对象中直接提取`@Value`注解的值。如果没有提供值,它会抛出异常。
```java
protected Object extractValue(AnnotationAttributes attr) {
Object value = attr.get(AnnotationUtils.VALUE);
if (value == null) {
throw new IllegalStateException("Value annotation must have a value attribute");
}
return value;
}
```
当我们使用 `@Value("${app.description:我是默认值}")` 在你的字段上时Spring 会在运行时尝试解析这个属性占位符。当 Spring 容器处理这个字段的注入时,它会使用 `QualifierAnnotationAutowireCandidateResolver`(或其他相关的后处理器)来获取并解析这个属性值。在我们的最佳实践下,`extractValue` 方法就是从注解属性中提取该属性占位符的逻辑,即返回值为 `"${app.description:我是默认值}"`。这个值随后会被 Spring 的属性解析器进一步处理,解析真实的值或使用默认值,并最终注入到 `appDescription` 字段中。
```java
${app.description:我是默认值}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤2。在`org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue`方法中,用于解析给定字符串中的内嵌值。它遍历所有注册的`StringValueResolver`解析器,对给定的字符串值进行连续解析,以处理可能存在的多重内嵌值或引用。例如,如果字符串中有一个`${property}`形式的属性,它可以通过注册的解析器进行处理和解析为实际的属性值。
```java
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
// 初始检查如果提供的值为null则直接返回null
if (value == null) {
return null;
}
String result = value;
// 遍历所有的内嵌值解析器
for (StringValueResolver resolver : this.embeddedValueResolvers) {
// 使用当前解析器解析result中的值
result = resolver.resolveStringValue(result);
// 如果解析后的值为null则直接返回null
if (result == null) {
return null;
}
}
// 返回所有解析器处理后的最终值
return result;
}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤3。在`org.springframework.beans.factory.support.AbstractBeanFactory#evaluateBeanDefinitionString`方法中用于评估给定的字符串值特别是处理可能包含Spring表达式语言 (SpEL) 表达式的字符串。首先,它检查是否有一个`beanExpressionResolver`可用来解析SpEL。如果有它可能会获取bean定义的作用域如果提供了bean定义然后使用`beanExpressionResolver`对字符串值进行评估,并考虑到相关的作用域上下文。
```java
@Nullable
protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) {
// 如果没有设置bean表达式解析器直接返回原始值
if (this.beanExpressionResolver == null) {
return value;
}
Scope scope = null;
// 如果提供了bean定义
if (beanDefinition != null) {
// 获取bean的作用域
String scopeName = beanDefinition.getScope();
// 如果作用域名称不为空,则尝试从已注册的作用域中获取对应的作用域
if (scopeName != null) {
scope = getRegisteredScope(scopeName);
}
}
// 使用bean表达式解析器解析提供的值并返回结果
// 这可以处理例如使用Spring EL的情况
return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}
```
在`org.springframework.context.expression.StandardBeanExpressionResolver#evaluate`方法中主要目的是解析并评估给定的值可能是一个Spring EL表达式。为了提高性能它使用缓存来存储先前解析的表达式和评估上下文。此方法首先从缓存中检索或解析表达式然后准备一个评估上下文并使用它评估表达式。这个评估上下文被配置为能够访问与Spring容器相关的各种内容如beans、环境属性等。
```java
@Override
@Nullable
public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
// 如果提供的值为空或没有内容,直接返回该值
if (!StringUtils.hasLength(value)) {
return value;
}
try {
// 从缓存中尝试获取表达式
Expression expr = this.expressionCache.get(value);
// 如果缓存中没有表达式,则使用表达式解析器解析该值,并将其放入缓存中
if (expr == null) {
expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
this.expressionCache.put(value, expr);
}
// 尝试从缓存中获取评估上下文
StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
// 如果缓存中没有评估上下文,则创建一个新的,并进行一些初始化配置
if (sec == null) {
sec = new StandardEvaluationContext(evalContext);
// 添加各种属性访问器以支持对特定类型的属性的访问
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
sec.addPropertyAccessor(new BeanFactoryAccessor());
sec.addPropertyAccessor(new MapAccessor());
sec.addPropertyAccessor(new EnvironmentAccessor());
// 设置bean解析器和类型定位器
sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
// 如果有可用的转换服务,则设置类型转换器
ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
if (conversionService != null) {
sec.setTypeConverter(new StandardTypeConverter(conversionService));
}
// 自定义评估上下文,允许子类提供额外的配置
customizeEvaluationContext(sec);
// 将创建的评估上下文放入缓存
this.evaluationCache.put(evalContext, sec);
}
// 使用已准备好的评估上下文评估表达式并返回结果
return expr.getValue(sec);
}
catch (Throwable ex) {
// 如果在解析或评估过程中出现任何异常抛出BeanExpressionException
throw new BeanExpressionException("Expression parsing failed", ex);
}
}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤4。在`org.springframework.beans.TypeConverterSupport#convertIfNecessary(value,requiredType,typeDescriptor)`方法中,又重新委托给`typeConverterDelegate`进行实际的转换工作
```java
@Nullable
@Override
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
// 委托给typeConverterDelegate进行实际的转换工作
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
```
在`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(propertyName,oldValue,newValue,requiredType,typeDescriptor)`方法中,负责将一个值转换为必需的类型。首先,它会尝试查找对应的自定义编辑器。如果没有找到编辑器但设置了自定义的转换服务,它会尝试使用此服务进行转换。如果上述两步都失败,该方法还会尝试执行一些标准的转换规则,例如从字符串到枚举或从数字到其他数字类型的转换。如果所有尝试都失败,该方法会抛出相应的异常,指出不能执行所需的转换。
```java
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 查找此类型的自定义编辑器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
// 用于捕获ConversionService尝试失败的异常
ConversionFailedException conversionAttemptEx = null;
// 没有自定义编辑器但是否指定了自定义ConversionService
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
conversionAttemptEx = ex; // 记录转换尝试失败
}
}
}
Object convertedValue = newValue;
// 如果值不是所需类型,进行转换
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class<?> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType); // 如果没有自定义编辑器,找默认的
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); // 进行转换
}
// 对于特定情况尝试标准类型转换,如字符串到枚举、数字转换等
boolean standardConversion = false;
if (requiredType != null) {
if (convertedValue != null) {
// 若目标类型为Object则直接返回转换值
if (Object.class == requiredType) {
return (T) convertedValue;
}
// 若目标类型为数组,进行数组转换
else if (requiredType.isArray()) {
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
// 如果是Collection或Map,则尝试转换集合或映射的内容
else if (convertedValue instanceof Collection) {
convertedValue = convertToTypedCollection((Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
else if (convertedValue instanceof Map) {
convertedValue = convertToTypedMap((Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
return (T) convertedValue.toString();
}
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
String trimmedValue = ((String) convertedValue).trim();
if (requiredType.isEnum() && trimmedValue.isEmpty()) {
return null;
}
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion = true;
}
else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
convertedValue = NumberUtils.convertNumberToTargetClass((Number) convertedValue, (Class<Number>) requiredType);
standardConversion = true;
}
}
else if (requiredType == Optional.class) {
convertedValue = Optional.empty();
}
// 如果经过上述所有转换后,值仍不匹配目标类型,则抛出异常
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (conversionAttemptEx != null) {
throw conversionAttemptEx;
}
else if (conversionService != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
throw new IllegalStateException("转换失败"); // 实际异常消息会更详细,这里简化了
}
}
if (conversionAttemptEx != null) {
throw conversionAttemptEx;
}
return (T) convertedValue;
}
```
### 八、注意事项
1. **SpEL表达式**
+ `@Value`可以用来解析Spring Expression Language (SpEL) 表达式。确保我们的表达式是正确的,以防止运行时错误。
2. **默认值**
+ 我们可以为`@Value`注解提供默认值,以防止某个属性在属性文件中未被定义。例如:`@Value("${some.property:default}")`。
3. **类型转换**
+ 确保`@Value`提供的值可以被转换为字段或方法参数的类型。Spring会尝试自动进行这种转换但不一定总是成功。
4. **不适用于复杂类型**
+ 尽管`@Value`可以用于简单的类型如字符串、整数、枚举等但不应用于复杂的bean注入这时应该使用`@Autowired`或`@Inject`。
5. **不可用于`BeanPostProcessor`或`BeanFactoryPostProcessor`**
+ `@Value`注解在`BeanPostProcessor`或`BeanFactoryPostProcessor`实现中是不起作用的因为它们在Spring容器生命周期中的处理时机早于`@Value`的处理。
6. **占位符解析器的配置**
+ 要使用属性占位符(如`${property.name}`),需要确保已配置了`PropertySourcesPlaceholderConfigurer`或`PropertyPlaceholderConfigurer`。
7. **环境变量与系统属性**
+ 我们可以使用`@Value`来引用环境变量或系统属性,例如:`@Value("${JAVA_HOME}")`。
8. **防止注入敏感信息**
+ 不要使用`@Value`来注入敏感信息如密码除非它们是适当加密的。考虑使用专门的解决方案如Spring Cloud Config的Vault集成。
9. **循环依赖**
+ 尽管与`@Autowired`不同,但需要注意的是,使用`@Value`可能间接导致循环依赖尤其是当注入的值是其他bean的属性时。
10. **性能考虑**
+ 大量使用SpEL表达式可能对性能产生轻微的影响因为这些表达式需要在运行时进行解析。
### 九、总结
#### 最佳实践总结
1. **启动类入口**
- 使用`AnnotationConfigApplicationContext`来启动Spring上下文该上下文支持基于Java注解的配置。
- 在创建上下文时,为其提供了`MyConfiguration`作为配置类。
2. **配置类**
- `MyConfiguration`类标记为`@Configuration`表示它提供了bean定义的配置信息。
- 使用`@PropertySource`指定一个属性文件`application.properties`来为上下文加载属性。
- 定义了一个bean`MyService`确保其在Spring容器中被创建和初始化。
3. **属性文件**
- 在`application.properties`文件中定义了几个属性,这些属性可以在应用程序中使用。
4. **属性注入**
- 在`MyService`类中,展示了如何使用`@Value`注解进行五种不同方式的属性注入从直接注入字符串值到使用SpEL表达式。
5. **注入结果的验证**
- 实现`InitializingBean`接口并重写`afterPropertiesSet`方法来验证注入的属性值。
- 运行应用后,该方法会打印出所有注入属性的值,从而验证`@Value`注解正确地解析并注入了预期的值。
#### 源码分析总结
1. **核心后处理器**
+ `AutowiredAnnotationBeanPostProcessor`是处理`@Value`等注解的主要后处理器。它实现了两个关键的接口,`MergedBeanDefinitionPostProcessor`和`InstantiationAwareBeanPostProcessor`这两个接口允许在bean的生命周期中的关键阶段进行干预为属性注入提供了机制。
2. **收集阶段**
- 在`postProcessMergedBeanDefinition`方法中,`AutowiredAnnotationBeanPostProcessor`确保bean的定义与预期的自动装配元数据匹配。
- `findAutowiringMetadata`方法确保为给定的bean名称和类获取相关的`InjectionMetadata`,并利用缓存机制优化性能。
- `buildAutowiringMetadata`方法检查类及其所有父类,确定带有`@Autowired`、`@Value`等注解的字段和方法,并为这些元素创建一个统一的`InjectionMetadata`对象。
3. **注入阶段**
+ `postProcessProperties`方法用于处理bean的属性的后处理特别是注入由`@Value`等注解标记的属性。
+ `InjectionMetadata#inject`方法用于将所有需要注入的元素(例如带有`@Value`的字段注入到目标bean中。
+ `AutowiredFieldElement#inject`方法处理具体的字段注入,包括解析`@Value`注解中的值。
+ `DefaultListableBeanFactory#resolveDependency`方法从Spring的bean工厂中解析字段的值。
+ `DefaultListableBeanFactory#doResolveDependency`方法是实际解析工作的主要场所,涉及到处理`@Value`注解中的字符串属性占位符和SpEL表达式并确保值经过正确的类型转换。