From a66a8f2d295f4114140de9402928bccdc7e7af80 Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Mon, 16 Oct 2023 17:32:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96@Value=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring-annotation-value/README.md | 406 +++++++++++++++++- 1 file changed, 390 insertions(+), 16 deletions(-) diff --git a/spring-annotation/spring-annotation-value/README.md b/spring-annotation/spring-annotation-value/README.md index e632037..ce16e47 100644 --- a/spring-annotation/spring-annotation-value/README.md +++ b/spring-annotation/spring-annotation-value/README.md @@ -69,8 +69,8 @@ public @interface Value { 1. **提供属性注入** + 允许从不同的配置源(如属性文件、系统属性等)直接向 Spring 管理的 beans 中注入值。 2. **支持表达式**: - - **SpEL (Spring Expression Language) 表达式**:例如,`#{systemProperties.myProp}` 可以从系统属性中获取名为 `myProp` 的值。 - - **属性占位符**:例如,`${my.app.myProp}` 可以从预定义的配置源,如 `application.properties` 或 `application.yml` 文件,获取名为 `my.app.myProp` 的属性值。 + - SpEL (Spring Expression Language) 表达式:例如,`#{systemProperties.myProp}` 可以从系统属性中获取名为 `myProp` 的值。 + - 属性占位符:例如,`${my.app.myProp}` 可以从预定义的配置源,如 `application.properties` 或 `application.yml` 文件,获取名为 `my.app.myProp` 的属性值。 3. **动态值解析** + 与只能在启动时设置静态值相比,`@Value` 注解可以解析动态表达式,从而为字段或构造函数参数提供动态值。 4. **用于字段、方法参数、构造函数参数和注解** @@ -338,7 +338,6 @@ private InjectionMetadata buildAutowiringMetadata(final Class clazz) { ```java public AutowiredAnnotationBeanPostProcessor() { - this.autowiredAnnotationTypes.add(Autowired.class); this.autowiredAnnotationTypes.add(Value.class); // ... [代码部分省略以简化] } @@ -399,11 +398,11 @@ public void inject(Object target, @Nullable String beanName, @Nullable PropertyV ```java @Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { - // 获取代表带有@Autowired注解的字段的Field对象。 + // 步骤1. 获取代表带有@Autowired注解的字段的Field对象。 Field field = (Field) this.member; Object value; - // 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。 + // 步骤2. 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。 if (this.cached) { try { // 从缓存中获取已解析的字段值。 @@ -415,21 +414,21 @@ protected void inject(Object bean, @Nullable String beanName, @Nullable Property } } else { - // 如果字段值未被缓存,直接解析。 + // 步骤3. 如果字段值未被缓存,直接解析。 value = resolveFieldValue(field, bean, beanName); } - // 如果解析到的值不为null... + // 步骤4. 如果解析到的值不为null... if (value != null) { - // 使字段可访问,这是必要的,特别是当字段是private时。 + // 步骤4.1. 使字段可访问,这是必要的,特别是当字段是private时。 ReflectionUtils.makeAccessible(field); - // 实际将解析的值注入到目标bean的字段中。 + // 步骤4.2. 实际将解析的值注入到目标bean的字段中。 field.set(bean, value); } } ``` -在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值。 +首先来到`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中的步骤3。在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值。 ```java @Nullable @@ -448,6 +447,379 @@ private Object resolveFieldValue(Field field, Object bean, @Nullable String bean } ``` +在`org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency`方法中,首先尝试获取一个延迟解析代理。如果无法获得,它会进一步尝试解析依赖。`doResolveDependency` 是实际进行解析工作的方法。 + +```java +public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, + @Nullable Set 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 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 convertIfNecessary(@Nullable Object value, @Nullable Class 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 convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, + @Nullable Class 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) 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表达式** @@ -495,10 +867,12 @@ private Object resolveFieldValue(Field field, Object bean, @Nullable String bean 1. **核心后处理器**: + `AutowiredAnnotationBeanPostProcessor`是处理`@Value`等注解的主要后处理器。它实现了两个关键的接口,`MergedBeanDefinitionPostProcessor`和`InstantiationAwareBeanPostProcessor`,这两个接口允许在bean的生命周期中的关键阶段进行干预,为属性注入提供了机制。 2. **收集阶段**: - + 在`postProcessMergedBeanDefinition`方法中,后处理器确保给定的bean定义与预期的自动装配元数据一致。主要任务是为给定的bean名称和类型查找相关的`InjectionMetadata`,这可能包含了该bean的字段和方法的注入信息。 + - 在`postProcessMergedBeanDefinition`方法中,`AutowiredAnnotationBeanPostProcessor`确保bean的定义与预期的自动装配元数据匹配。 + - `findAutowiringMetadata`方法确保为给定的bean名称和类获取相关的`InjectionMetadata`,并利用缓存机制优化性能。 + - `buildAutowiringMetadata`方法检查类及其所有父类,确定带有`@Autowired`、`@Value`等注解的字段和方法,并为这些元素创建一个统一的`InjectionMetadata`对象。 3. **注入阶段**: - + 在`postProcessProperties`方法中,后处理器处理bean属性的注入,特别是通过`@Value`进行的注入。具体来说,它会获取与bean名称和类相关的`InjectionMetadata`,然后使用这些元数据来注入属性。 -4. **字段注入**: - + 在`AutowiredFieldElement#inject`方法中,首先会检查字段的值是否已经被缓存。如果已缓存,则从缓存中获取,否则重新解析。然后将解析的值注入到目标bean的字段中。 -5. **值解析**: - + 在`AutowiredFieldElement#resolveFieldValue`方法中,后处理器从Spring的bean工厂中解析字段的值。它主要通过`beanFactory.resolveDependency`方法来完成这一工作。 \ No newline at end of file + + `postProcessProperties`方法用于处理bean的属性的后处理,特别是注入由`@Value`等注解标记的属性。 + + `InjectionMetadata#inject`方法用于将所有需要注入的元素(例如带有`@Value`的字段)注入到目标bean中。 + + `AutowiredFieldElement#inject`方法处理具体的字段注入,包括解析`@Value`注解中的值。 + + `DefaultListableBeanFactory#resolveDependency`方法从Spring的bean工厂中解析字段的值。 + + `DefaultListableBeanFactory#doResolveDependency`方法是实际解析工作的主要场所,涉及到处理`@Value`注解中的字符串属性占位符和SpEL表达式,并确保值经过正确的类型转换。 \ No newline at end of file