From da078591a540b70f2be293b6888230dab7b1a33c Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Fri, 13 Oct 2023 17:47:50 +0800 Subject: [PATCH] =?UTF-8?q?@Value=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +- spring-annotation/pom.xml | 1 + .../spring-annotation-value/README.md | 504 ++++++++++++++++++ .../spring-annotation-value/pom.xml | 19 + .../java/com/xcs/spring/ValueApplication.java | 15 + .../xcs/spring/config/MyConfiguration.java | 20 + .../com/xcs/spring/service/MyService.java | 53 ++ .../src/main/resources/application.properties | 4 + 8 files changed, 626 insertions(+), 9 deletions(-) create mode 100644 spring-annotation/spring-annotation-value/README.md create mode 100644 spring-annotation/spring-annotation-value/pom.xml create mode 100644 spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/ValueApplication.java create mode 100644 spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/config/MyConfiguration.java create mode 100644 spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/service/MyService.java create mode 100644 spring-annotation/spring-annotation-value/src/main/resources/application.properties diff --git a/README.md b/README.md index ad6176a..3625963 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ ### 💡 后置处理器与初始化 -*深入 Spring 的后处理技巧,揭示它如何优雅地初始化和管理 beans。* +*深入 Spring 的后置处理技巧,揭示它如何优雅地初始化和管理 beans。* - [**深入BeanFactoryPostProcessor**](spring-interface/spring-interface-beanFactoryPostProcessor/README.md) - 理解 Spring 如何在 bean 初始化之前进行微调。 - [**分析BeanDefinitionRegistryPostProcessor**](spring-interface/spring-interface-beanDefinitionRegistryPostProcessor/README.md) - 揭示如何修改应用的 bean 定义。 @@ -59,14 +59,14 @@ - [**深究BeanNameAware**](spring-aware/spring-aware-beanNameAware/README.md) - 当一个 bean 需要知道其在容器中的名字时。 - [**BeanClassLoaderAware的探查**](spring-aware/spring-aware-beanClassLoaderAware/README.md) - 揭示如何为 bean 提供类加载器的引用。 - [**深度了解BeanFactoryAware**](spring-aware/spring-aware-beanFactoryAware/README.md) - 探索 bean 如何与其工厂互动。 -- [**环境感知: EnvironmentAware**](spring-aware/spring-aware-environmentAware/README.md) - 了解 bean 如何感知并与其运行的环境互动。 -- [**嵌入式值解析: EmbeddedValueResolverAware**](spring-aware/spring-aware-embeddedValueResolverAware/README.md) - 探查如何提供字符串值解析策略给 bean。 -- [**资源加载策略: ResourceLoaderAware**](spring-aware/spring-aware-resourceLoaderAware/README.md) - 理解如何为 bean 提供一个资源加载器。 -- [**事件发布感知: ApplicationEventPublisherAware**](spring-aware/spring-aware-applicationEventPublisherAware/README.md) - 揭露 bean 如何发布事件到应用上下文。 -- [**消息源感知: MessageSourceAware**](spring-aware/spring-aware-messageSourceAware/README.md) - 深入了解 bean 如何访问消息源。 -- [**应用启动感知: ApplicationStartupAware**](spring-aware/spring-aware-applicationStartupAware/README.md) - 理解 bean 如何感知应用的启动过程。 -- [**上下文感知: ApplicationContextAware**](spring-aware/spring-aware-applicationContextAware/README.md) - 探索 bean 如何访问其运行的应用上下文。 -- [**导入感知: ImportAware**](spring-aware/spring-aware-importAware/README.md) - 揭露 bean 如何知道与其关联的导入元数据。 +- [**环境感知EnvironmentAware**](spring-aware/spring-aware-environmentAware/README.md) - 了解 bean 如何感知并与其运行的环境互动。 +- [**嵌入式值解析EmbeddedValueResolverAware**](spring-aware/spring-aware-embeddedValueResolverAware/README.md) - 探查如何提供字符串值解析策略给 bean。 +- [**资源加载策略ResourceLoaderAware**](spring-aware/spring-aware-resourceLoaderAware/README.md) - 理解如何为 bean 提供一个资源加载器。 +- [**事件发布感知ApplicationEventPublisherAware**](spring-aware/spring-aware-applicationEventPublisherAware/README.md) - 揭露 bean 如何发布事件到应用上下文。 +- [**消息源感知MessageSourceAware**](spring-aware/spring-aware-messageSourceAware/README.md) - 深入了解 bean 如何访问消息源。 +- [**应用启动感知ApplicationStartupAware**](spring-aware/spring-aware-applicationStartupAware/README.md) - 理解 bean 如何感知应用的启动过程。 +- [**上下文感知ApplicationContextAware**](spring-aware/spring-aware-applicationContextAware/README.md) - 探索 bean 如何访问其运行的应用上下文。 +- [**导入感知ImportAware**](spring-aware/spring-aware-importAware/README.md) - 揭露 bean 如何知道与其关联的导入元数据。 ### 🎖 核心注解 @@ -80,6 +80,7 @@ - [**掌控顺序@DependsOn**](spring-annotation/spring-annotation-dependsOn/README.md) - 精确控制 Spring Beans 的加载顺序。 - [**条件注册解密@Conditional注解**](spring-annotation/spring-annotation-conditional/README.md) - 从基础使用到源码分析,全方位理解Spring的条件注册策略。 - [**深入探究@Lazy注解**](spring-annotation/spring-annotation-lazy/README.md) - 如何优雅地实现 Spring Beans 的延迟加载。 +- [**属性绑定@Value**](spring-annotation/spring-annotation-value/README.md) - 如何在Spring中优雅地注入配置属性。 ## 🔄持续更新中 diff --git a/spring-annotation/pom.xml b/spring-annotation/pom.xml index ff2c0fb..93c1e44 100644 --- a/spring-annotation/pom.xml +++ b/spring-annotation/pom.xml @@ -21,6 +21,7 @@ spring-annotation-dependsOn spring-annotation-lazy spring-annotation-conditional + spring-annotation-value \ No newline at end of file diff --git a/spring-annotation/spring-annotation-value/README.md b/spring-annotation/spring-annotation-value/README.md new file mode 100644 index 0000000..e632037 --- /dev/null +++ b/spring-annotation/spring-annotation-value/README.md @@ -0,0 +1,504 @@ +## @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}` 这样的SpEL(Spring表达式语言)表达式来注入值。 + * 或者,使用 `${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 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] +使用Spring的SpEL: 30 +``` + +### 六、时序图 + +~~~mermaid +sequenceDiagram +Title: @Value注解时序图 +AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName)
应用Bean定义的后置处理器 +AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessMergedBeanDefinition(beanDefinition,beanType,beanName)
处理已合并的Bean定义 +AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
查找自动注入的元数据 +AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:buildAutowiringMetadata(clazz)
构建自动注入的元数据 +AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalFields(clazz,fc)
处理类的本地字段 +ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的字段 +AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalMethods(clazz,fc)
处理类的本地方法 +ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的方法 +AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:injectionMetadataCache.put(cacheKey, metadata)
将元数据存入缓存 +AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)
填充Bean的属性值 +AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName)
后处理Bean的属性 +AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
再次查找自动注入的元数据 +Note right of AutowiredAnnotationBeanPostProcessor:
从缓存中获取注入的元数据 +AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(bean, beanName, pvs)
执行实际的属性注入 +InjectionMetadata->>AutowiredFieldElement:inject(target, beanName, pvs)
注入特定的字段元素 +AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName)
解析字段的值 +AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
解析字段的依赖 +DefaultListableBeanFactory->>AutowiredFieldElement:返回解析后的value值
返回解析后的属性值 +AutowiredFieldElement->>Field:field.set(bean, value)
设置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 elements = new ArrayList<>(); + Class targetClass = clazz; + + do { + // 当前类中要注入的元素列表。 + final List 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(Autowired.class); + 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 checkedElements = this.checkedElements; + + // 如果已经有检查过的元素,则使用它们,否则使用所有注入的元素。 + Collection 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 { + // 获取代表带有@Autowired注解的字段的Field对象。 + Field field = (Field) this.member; + + Object value; + // 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。 + if (this.cached) { + try { + // 从缓存中获取已解析的字段值。 + value = resolvedCachedArgument(beanName, this.cachedFieldValue); + } + catch (NoSuchBeanDefinitionException ex) { + // 如果缓存中的bean已被意外删除 -> 重新解析。 + value = resolveFieldValue(field, bean, beanName); + } + } + else { + // 如果字段值未被缓存,直接解析。 + value = resolveFieldValue(field, bean, beanName); + } + + // 如果解析到的值不为null... + if (value != null) { + // 使字段可访问,这是必要的,特别是当字段是private时。 + ReflectionUtils.makeAccessible(field); + // 实际将解析的值注入到目标bean的字段中。 + field.set(bean, value); + } +} +``` + +在`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; +} +``` + +### 八、注意事项 + +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`方法中,后处理器确保给定的bean定义与预期的自动装配元数据一致。主要任务是为给定的bean名称和类型查找相关的`InjectionMetadata`,这可能包含了该bean的字段和方法的注入信息。 +3. **注入阶段**: + + 在`postProcessProperties`方法中,后处理器处理bean属性的注入,特别是通过`@Value`进行的注入。具体来说,它会获取与bean名称和类相关的`InjectionMetadata`,然后使用这些元数据来注入属性。 +4. **字段注入**: + + 在`AutowiredFieldElement#inject`方法中,首先会检查字段的值是否已经被缓存。如果已缓存,则从缓存中获取,否则重新解析。然后将解析的值注入到目标bean的字段中。 +5. **值解析**: + + 在`AutowiredFieldElement#resolveFieldValue`方法中,后处理器从Spring的bean工厂中解析字段的值。它主要通过`beanFactory.resolveDependency`方法来完成这一工作。 \ No newline at end of file diff --git a/spring-annotation/spring-annotation-value/pom.xml b/spring-annotation/spring-annotation-value/pom.xml new file mode 100644 index 0000000..13c2317 --- /dev/null +++ b/spring-annotation/spring-annotation-value/pom.xml @@ -0,0 +1,19 @@ + + + + spring-annotation + com.xcs.spring + 0.0.1-SNAPSHOT + + 4.0.0 + + spring-annotation-value + + + 11 + 11 + + + \ No newline at end of file diff --git a/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/ValueApplication.java b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/ValueApplication.java new file mode 100644 index 0000000..a8d69ff --- /dev/null +++ b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/ValueApplication.java @@ -0,0 +1,15 @@ +package com.xcs.spring; + +import com.xcs.spring.config.MyConfiguration; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author xcs + * @date 2023年08月07日 16时21分 + **/ +public class ValueApplication { + + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); + } +} diff --git a/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/config/MyConfiguration.java b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/config/MyConfiguration.java new file mode 100644 index 0000000..2b3d167 --- /dev/null +++ b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/config/MyConfiguration.java @@ -0,0 +1,20 @@ +package com.xcs.spring.config; + +import com.xcs.spring.service.MyService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +/** + * @author xcs + * @date 2023年10月13日 14时54分 + **/ +@Configuration +@PropertySource("classpath:application.properties") +public class MyConfiguration { + + @Bean + public MyService myService(){ + return new MyService(); + } +} diff --git a/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/service/MyService.java b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/service/MyService.java new file mode 100644 index 0000000..3e966df --- /dev/null +++ b/spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/service/MyService.java @@ -0,0 +1,53 @@ +package com.xcs.spring.service; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; + +import java.util.List; + +/** + * @author xcs + * @date 2023年10月13日 14时55分 + **/ + +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 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); + } +} diff --git a/spring-annotation/spring-annotation-value/src/main/resources/application.properties b/spring-annotation/spring-annotation-value/src/main/resources/application.properties new file mode 100644 index 0000000..5f9c6d3 --- /dev/null +++ b/spring-annotation/spring-annotation-value/src/main/resources/application.properties @@ -0,0 +1,4 @@ +app.name=My Spring Application +app.servers=server1,server2,server3 +app.val1=10 +app.val2=20 \ No newline at end of file