@Value注解源碼分析

@Value

一、基本信息

?? 作者 - Lex ?? 博客 - 我的CSDN ?? 文章目錄 - 所有文章 ?? 源碼地址 - @Value源碼

二、注解描述

@Value 注解,是一個非常有用的功能,它允許我們從配置文件中注入屬性值到Java類的字段或方法參數(shù)中。這樣,我們可以將配置和代碼分離,使應(yīng)用更容易配置和維護。

三、注解源碼

@Value注解是 Spring 框架自 3.1 版本開始引入的一個核心注解,主要目的是允許我們在Spring管理的bean中直接注入來自各種源(如屬性文件、系統(tǒng)屬性等)的值,而不需要顯式地編寫代碼來解析這些值。

/**
 * 用于字段或方法/構(gòu)造函數(shù)參數(shù)級別的注解,
 * 表示被注解元素的默認值表達式。
 *
 * 通常用于基于表達式或?qū)傩缘囊蕾囎⑷搿? * 也支持動態(tài)解析處理器方法參數(shù),例如在Spring MVC中。
 *
 * 常見的使用場景是使用 `#{systemProperties.myProp}` 這樣的SpEL(Spring表達式語言)表達式來注入值。
 * 或者,使用 `${my.app.myProp}` 這樣的屬性占位符來注入值。
 *
 * 注意,@Value 注解的實際處理是由 BeanPostProcessor 執(zhí)行的,
 * 這意味著我們**不能**在 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}`,
     * 或?qū)傩哉嘉环?`${my.app.myProp}`。
     */
    String value();
}

四、主要功能

  1. 提供屬性注入
    • 允許從不同的配置源(如屬性文件、系統(tǒng)屬性等)直接向 Spring 管理的 beans 中注入值。
  2. 支持表達式
    • SpEL (Spring Expression Language) 表達式:例如,#{systemProperties.myProp} 可以從系統(tǒng)屬性中獲取名為 myProp 的值。
    • 屬性占位符:例如,${my.app.myProp} 可以從預(yù)定義的配置源,如 application.propertiesapplication.yml 文件,獲取名為 my.app.myProp 的屬性值。
  3. 動態(tài)值解析
    • 與只能在啟動時設(shè)置靜態(tài)值相比,@Value 注解可以解析動態(tài)表達式,從而為字段或構(gòu)造函數(shù)參數(shù)提供動態(tài)值。
  4. 用于字段、方法參數(shù)、構(gòu)造函數(shù)參數(shù)和注解
    • 它可以被應(yīng)用到這些元素上,以提供必要的值。
  5. 與其他注解協(xié)同工作
    • 盡管 @Value 本身是用于注入值的,但它經(jīng)常與其他如 @Component、@Service@Controller 之類的 Spring 注解一起使用,以創(chuàng)建完全由 Spring 管理和配置的 beans。
  6. 與屬性解析器配合
    • 為了正確解析 @Value 中的表達式,Spring 應(yīng)用上下文中需要有一個屬性解析器,例如 PropertySourcesPlaceholderConfigurer。在 Spring Boot 項目中,這已經(jīng)默認配置好了。

五、最佳實踐

首先來看看啟動類入口,上下文環(huán)境使用AnnotationConfigApplicationContext(此類是使用Java注解來配置Spring容器的方式),構(gòu)造參數(shù)我們給定了一個MyConfiguration組件類。

public class ValueApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
    }
}

這里使用@Bean注解,定義了一個Bean,是為了確保 MyService 被 Spring 容器執(zhí)行,另外使用@PropertySource注解從類路徑下的application.properties文件中加載屬性。這意味著我們可以在這個文件中定義屬性,然后在應(yīng)用中使用Environment對象來訪問它們。

@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {

    @Bean
    public MyService myService(){
        return new MyService();
    }
}

application.properties文件在src/main/resources目錄中,并添加以下內(nèi)容。

app.name=My Spring Application
app.servers=server1,server2,server3
app.val1=10
app.val2=20

MyService類,展示了如何使用@Value注解的五種不同方式進行屬性注入。

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);
    }
}

運行結(jié)果發(fā)現(xiàn), @Value 注解都被正確地解析并注入了預(yù)期的值。

直接注入值: Some String Value
從屬性文件中注入值: My Spring Application
使用默認值: 我是默認值
注入列表和屬性: [server1, server2, server3]
使用Spring的SpEL: 30

六、時序圖

sequenceDiagram
Title: @Value注解時序圖
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName)<br>應(yīng)用Bean定義的后置處理器
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessMergedBeanDefinition(beanDefinition,beanType,beanName)<br>處理已合并的Bean定義
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)<br>查找自動注入的元數(shù)據(jù)
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:buildAutowiringMetadata(clazz)<br>構(gòu)建自動注入的元數(shù)據(jù)
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>將元數(shù)據(jù)存入緩存
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)<br>填充Bean的屬性值
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName)<br>后處理Bean的屬性
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)<br>再次查找自動注入的元數(shù)據(jù)
Note right of AutowiredAnnotationBeanPostProcessor:<br>從緩存中獲取注入的元數(shù)據(jù)
AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(bean, beanName, pvs)<br>執(zhí)行實際的屬性注入
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>設(shè)置Bean字段的值

七、源碼分析

前置條件

在Spring中,AutowiredAnnotationBeanPostProcessor是處理@Value等注解的關(guān)鍵類,它實現(xiàn)了下述兩個接口。因此,為了深入理解@Value的工作方式,研究這個類是非常有用的。簡而言之,為了完全理解@Value的工作機制,了解下述接口確實是必要的。這兩個接口提供了對bean生命周期中關(guān)鍵階段的干預(yù),從而允許進行屬性注入和其他相關(guān)的操作。

  1. MergedBeanDefinitionPostProcessor接口
    • 此接口提供的postProcessMergedBeanDefinition方法允許后處理器修改合并后的bean定義。合并后的bean定義是一個已經(jīng)考慮了所有父bean定義屬性的bean定義。對于@Value注解的處理,這一步通常涉及到收集需要被解析的@Value注解信息并準備對其進行后續(xù)處理。
    • ?? MergedBeanDefinitionPostProcessor接口傳送門
  2. InstantiationAwareBeanPostProcessor接口
    • 此接口提供了幾個回調(diào)方法,允許后處理器在bean實例化之前和實例化之后介入bean的創(chuàng)建過程。特別是,postProcessProperties方法允許后處理器對bean的屬性進行操作。對于@Value注解,這通常涉及到實際地解析注解中的表達式或?qū)傩哉嘉环?,并將解析得到的值注入到bean中。
    • ?? InstantiationAwareBeanPostProcessor接口傳送門

收集階段

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition方法中,主要確保給定的bean定義與其預(yù)期的自動裝配元數(shù)據(jù)一致。

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 對于給定的bean名稱和類型,它首先嘗試查找相關(guān)的InjectionMetadata,這可能包含了該bean的字段和方法的注入信息
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    
    // 使用找到的InjectionMetadata來驗證bean定義中的配置成員是否與預(yù)期的注入元數(shù)據(jù)匹配。
    metadata.checkConfigMembers(beanDefinition);
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata方法中,確保了始終為給定的bean名稱和類獲取最新和相關(guān)的InjectionMetadata,并利用緩存機制優(yōu)化性能。

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // 如果beanName為空,則使用類名作為緩存鍵。
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 首先嘗試從并發(fā)緩存中獲取InjectionMetadata。
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 檢查獲取到的元數(shù)據(jù)是否需要刷新。
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        // 使用雙重檢查鎖定確保線程安全。
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                // 如果有舊的元數(shù)據(jù),清除它。
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 為給定的類構(gòu)建新的InjectionMetadata。
                metadata = buildAutowiringMetadata(clazz);
                // 將新構(gòu)建的元數(shù)據(jù)更新到緩存中。
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    // 返回找到的或新構(gòu)建的元數(shù)據(jù)。
    return metadata;
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata方法中,查找類及其所有父類中的字段和方法,以找出所有帶有自動裝配注解的字段和方法,并為它們創(chuàng)建一個統(tǒng)一的InjectionMetadata對象。

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) {
                // 忽略靜態(tài)字段。
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                boolean required = determineRequiredStatus(ann);
                // 創(chuàng)建一個新的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))) {
                // 忽略靜態(tài)方法。
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                // 只處理帶參數(shù)的方法。
                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);
                // 創(chuàng)建一個新的AutowiredMethodElement并加入到列表。
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });

        // 將當前類的注入元素加入到總的注入元素列表的開頭。
        elements.addAll(0, currElements);
        // 處理父類。
        targetClass = targetClass.getSuperclass();
    }
    // 循環(huán)直至Object類。
    while (targetClass != null && targetClass != Object.class);

    // 返回為元素列表創(chuàng)建的新的InjectionMetadata。
    return InjectionMetadata.forElements(elements, clazz);
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#autowiredAnnotationTypes字段中,主要的用途是告訴AutowiredAnnotationBeanPostProcessor哪些注解它應(yīng)該處理。當Spring容器解析bean定義并創(chuàng)建bean實例時,如果這個bean的字段、方法或構(gòu)造函數(shù)上的注解被包含在這個autowiredAnnotationTypes集合中,那么AutowiredAnnotationBeanPostProcessor就會對它進行處理。

public AutowiredAnnotationBeanPostProcessor() {
   this.autowiredAnnotationTypes.add(Value.class);
   // ... [代碼部分省略以簡化]
}

注入階段

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中,用于處理bean屬性的后處理,特別是通過@Value等注解進行的屬性注入。

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 獲取與bean名稱和類相關(guān)的InjectionMetadata。
    // 這包括該bean需要進行注入的所有字段和方法。
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    
    try {
        // 使用獲取到的InjectionMetadata,實際進行屬性的注入。
        metadata.inject(bean, beanName, pvs);
    }
    // 如果在注入過程中出現(xiàn)BeanCreationException,直接拋出。
    catch (BeanCreationException ex) {
        throw ex;
    }
    // 捕獲其他異常,并以BeanCreationException的形式拋出,提供詳細的錯誤信息。
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    // 返回原始的PropertyValues,因為這個方法主要關(guān)注依賴注入而不是修改屬性。
    return pvs;
}

org.springframework.beans.factory.annotation.InjectionMetadata#inject方法中,主要目的是將所有需要注入的元素(例如帶有@Value等注解的字段或方法)注入到目標bean中。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取已經(jīng)檢查的元素。通常,在初始化階段,所有的元素都會被檢查一次。
    Collection<InjectedElement> checkedElements = this.checkedElements;

    // 如果已經(jīng)有檢查過的元素,則使用它們,否則使用所有注入的元素。
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);

    // 如果有需要注入的元素...
    if (!elementsToIterate.isEmpty()) {
        // 遍歷每個元素并注入到目標bean中。
        for (InjectedElement element : elementsToIterate) {
            // 對每個元素(字段或方法)執(zhí)行注入操作。
            element.inject(target, beanName, pvs);
        }
    }
}

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject方法中,首先檢查字段的值是否已經(jīng)被緩存。如果已緩存,則從緩存中獲取,否則重新解析。然后,它確保字段是可訪問的(特別是對于私有字段),并將解析的值設(shè)置到目標bean的相應(yīng)字段中。

@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. 如果字段的值已經(jīng)被緩存(即先前已解析過),則嘗試從緩存中獲取。
    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工廠中解析字段的值。

@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 是實際進行解析工作的方法。

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表達式。最后,確保值根據(jù)目標字段或參數(shù)類型進行正確的類型轉(zhuǎn)換,并將其注入相應(yīng)的位置。

@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名稱相關(guān)的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. 獲取類型轉(zhuǎn)換器并進行必要的類型轉(zhuǎn)換
            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相關(guān)的注解值,特別是@Value注解。如果字段或方法參數(shù)上有@Value注解,它會從注解中提取相應(yīng)的值或表達式。

@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
    // 從描述符的注解中查找@Value注解提供的值
    Object value = findValue(descriptor.getAnnotations());
    
    // 如果在描述符的注解中沒有找到,檢查是否存在與此描述符關(guān)聯(lián)的方法參數(shù)
    if (value == null) {
        MethodParameter methodParam = descriptor.getMethodParameter();
        
        if (methodParam != null) {
            // 如果存在方法參數(shù),再從方法參數(shù)的注解中查找@Value提供的值
            value = findValue(methodParam.getMethodAnnotations());
        }
    }
    // 返回找到的值,如果沒有找到則返回null
    return value;
}

org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#findValue方法中,目標是在提供的注解集合中找到并返回@Value注解的值。如果沒有找到,它會返回null。

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注解的值。如果沒有提供值,它會拋出異常。

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(或其他相關(guān)的后處理器)來獲取并解析這個屬性值。在我們的最佳實踐下,extractValue 方法就是從注解屬性中提取該屬性占位符的邏輯,即返回值為 "${app.description:我是默認值}"。這個值隨后會被 Spring 的屬性解析器進一步處理,解析真實的值或使用默認值,并最終注入到 appDescription 字段中。

${app.description:我是默認值}

然后我們來到在org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency方法中的步驟2。在org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue方法中,用于解析給定字符串中的內(nèi)嵌值。它遍歷所有注冊的StringValueResolver解析器,對給定的字符串值進行連續(xù)解析,以處理可能存在的多重內(nèi)嵌值或引用。例如,如果字符串中有一個${property}形式的屬性,它可以通過注冊的解析器進行處理和解析為實際的屬性值。

@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
    // 初始檢查:如果提供的值為null,則直接返回null
    if (value == null) {
        return null;
    }
    
    String result = value;
    
    // 遍歷所有的內(nèi)嵌值解析器
    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對字符串值進行評估,并考慮到相關(guān)的作用域上下文。

@Nullable
protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) {
    // 如果沒有設(shè)置bean表達式解析器,直接返回原始值
    if (this.beanExpressionResolver == null) {
        return value;
    }

    Scope scope = null;

    // 如果提供了bean定義
    if (beanDefinition != null) {
        // 獲取bean的作用域
        String scopeName = beanDefinition.getScope();

        // 如果作用域名稱不為空,則嘗試從已注冊的作用域中獲取對應(yīng)的作用域
        if (scopeName != null) {
            scope = getRegisteredScope(scopeName);
        }
    }

    // 使用bean表達式解析器解析提供的值,并返回結(jié)果
    // 這可以處理例如使用Spring EL的情況
    return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}

org.springframework.context.expression.StandardBeanExpressionResolver#evaluate方法中,主要目的是解析并評估給定的值(可能是一個Spring EL表達式)。為了提高性能,它使用緩存來存儲先前解析的表達式和評估上下文。此方法首先從緩存中檢索或解析表達式,然后準備一個評估上下文,并使用它評估表達式。這個評估上下文被配置為能夠訪問與Spring容器相關(guān)的各種內(nèi)容,如beans、環(huán)境屬性等。

@Override
@Nullable
public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
    // 如果提供的值為空或沒有內(nèi)容,直接返回該值
    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);
        // 如果緩存中沒有評估上下文,則創(chuàng)建一個新的,并進行一些初始化配置
        if (sec == null) {
            sec = new StandardEvaluationContext(evalContext);
            // 添加各種屬性訪問器以支持對特定類型的屬性的訪問
            sec.addPropertyAccessor(new BeanExpressionContextAccessor());
            sec.addPropertyAccessor(new BeanFactoryAccessor());
            sec.addPropertyAccessor(new MapAccessor());
            sec.addPropertyAccessor(new EnvironmentAccessor());
            // 設(shè)置bean解析器和類型定位器
            sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
            sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
            // 如果有可用的轉(zhuǎn)換服務(wù),則設(shè)置類型轉(zhuǎn)換器
            ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
            if (conversionService != null) {
                sec.setTypeConverter(new StandardTypeConverter(conversionService));
            }
            // 自定義評估上下文,允許子類提供額外的配置
            customizeEvaluationContext(sec);
            // 將創(chuàng)建的評估上下文放入緩存
            this.evaluationCache.put(evalContext, sec);
        }

        // 使用已準備好的評估上下文評估表達式并返回結(jié)果
        return expr.getValue(sec);
    }
    catch (Throwable ex) {
        // 如果在解析或評估過程中出現(xiàn)任何異常,拋出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進行實際的轉(zhuǎn)換工作

@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進行實際的轉(zhuǎn)換工作
        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)方法中,負責(zé)將一個值轉(zhuǎn)換為必需的類型。首先,它會嘗試查找對應(yīng)的自定義編輯器。如果沒有找到編輯器但設(shè)置了自定義的轉(zhuǎn)換服務(wù),它會嘗試使用此服務(wù)進行轉(zhuǎn)換。如果上述兩步都失敗,該方法還會嘗試執(zhí)行一些標準的轉(zhuǎn)換規(guī)則,例如從字符串到枚舉或從數(shù)字到其他數(shù)字類型的轉(zhuǎn)換。如果所有嘗試都失敗,該方法會拋出相應(yīng)的異常,指出不能執(zhí)行所需的轉(zhuǎn)換。

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;  // 記錄轉(zhuǎn)換嘗試失敗
            }
        }
    }

    Object convertedValue = newValue;

    // 如果值不是所需類型,進行轉(zhuǎn)換
    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);  // 進行轉(zhuǎn)換
    }

    // 對于特定情況嘗試標準類型轉(zhuǎn)換,如字符串到枚舉、數(shù)字轉(zhuǎn)換等
    boolean standardConversion = false;
    if (requiredType != null) {
        if (convertedValue != null) {
            // 若目標類型為Object,則直接返回轉(zhuǎn)換值
            if (Object.class == requiredType) {
                return (T) convertedValue;
            }
            // 若目標類型為數(shù)組,進行數(shù)組轉(zhuǎn)換
            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,則嘗試轉(zhuǎn)換集合或映射的內(nèi)容
            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();
        }

        // 如果經(jīng)過上述所有轉(zhuǎn)換后,值仍不匹配目標類型,則拋出異常
        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("轉(zhuǎn)換失敗");  // 實際異常消息會更詳細,這里簡化了
        }
    }

    if (conversionAttemptEx != null) {
        throw conversionAttemptEx;
    }

    return (T) convertedValue;
}

八、注意事項

  1. SpEL表達式
    • @Value可以用來解析Spring Expression Language (SpEL) 表達式。確保我們的表達式是正確的,以防止運行時錯誤。
  2. 默認值
    • 我們可以為@Value注解提供默認值,以防止某個屬性在屬性文件中未被定義。例如:@Value("${some.property:default}")。
  3. 類型轉(zhuǎn)換
    • 確保@Value提供的值可以被轉(zhuǎn)換為字段或方法參數(shù)的類型。Spring會嘗試自動進行這種轉(zhuǎn)換,但不一定總是成功。
  4. 不適用于復(fù)雜類型
    • 盡管@Value可以用于簡單的類型(如字符串、整數(shù)、枚舉等),但不應(yīng)用于復(fù)雜的bean注入,這時應(yīng)該使用@Autowired@Inject。
  5. 不可用于BeanPostProcessorBeanFactoryPostProcessor
    • @Value注解在BeanPostProcessorBeanFactoryPostProcessor實現(xiàn)中是不起作用的,因為它們在Spring容器生命周期中的處理時機早于@Value的處理。
  6. 占位符解析器的配置
    • 要使用屬性占位符(如${property.name}),需要確保已配置了PropertySourcesPlaceholderConfigurerPropertyPlaceholderConfigurer
  7. 環(huán)境變量與系統(tǒng)屬性
    • 我們可以使用@Value來引用環(huán)境變量或系統(tǒng)屬性,例如:@Value("${JAVA_HOME}")
  8. 防止注入敏感信息
    • 不要使用@Value來注入敏感信息,如密碼,除非它們是適當加密的。考慮使用專門的解決方案,如Spring Cloud Config的Vault集成。
  9. 循環(huán)依賴
    • 盡管與@Autowired不同,但需要注意的是,使用@Value可能間接導(dǎo)致循環(huán)依賴,尤其是當注入的值是其他bean的屬性時。
  10. 性能考慮
    • 大量使用SpEL表達式可能對性能產(chǎn)生輕微的影響,因為這些表達式需要在運行時進行解析。

九、總結(jié)

最佳實踐總結(jié)

  1. 啟動類入口
    • 使用AnnotationConfigApplicationContext來啟動Spring上下文,該上下文支持基于Java注解的配置。
    • 在創(chuàng)建上下文時,為其提供了MyConfiguration作為配置類。
  2. 配置類
    • MyConfiguration類標記為@Configuration,表示它提供了bean定義的配置信息。
    • 使用@PropertySource指定一個屬性文件application.properties來為上下文加載屬性。
    • 定義了一個bean:MyService,確保其在Spring容器中被創(chuàng)建和初始化。
  3. 屬性文件
    • application.properties文件中定義了幾個屬性,這些屬性可以在應(yīng)用程序中使用。
  4. 屬性注入
    • MyService類中,展示了如何使用@Value注解進行五種不同方式的屬性注入,從直接注入字符串值到使用SpEL表達式。
  5. 注入結(jié)果的驗證
    • 實現(xiàn)InitializingBean接口并重寫afterPropertiesSet方法來驗證注入的屬性值。
    • 運行應(yīng)用后,該方法會打印出所有注入屬性的值,從而驗證@Value注解正確地解析并注入了預(yù)期的值。

源碼分析總結(jié)

  1. 核心后處理器
    • AutowiredAnnotationBeanPostProcessor是處理@Value等注解的主要后處理器。它實現(xiàn)了兩個關(guān)鍵的接口,MergedBeanDefinitionPostProcessorInstantiationAwareBeanPostProcessor,這兩個接口允許在bean的生命周期中的關(guān)鍵階段進行干預(yù),為屬性注入提供了機制。
  2. 收集階段
    • postProcessMergedBeanDefinition方法中,AutowiredAnnotationBeanPostProcessor確保bean的定義與預(yù)期的自動裝配元數(shù)據(jù)匹配。
    • findAutowiringMetadata方法確保為給定的bean名稱和類獲取相關(guān)的InjectionMetadata,并利用緩存機制優(yōu)化性能。
    • buildAutowiringMetadata方法檢查類及其所有父類,確定帶有@Autowired、@Value等注解的字段和方法,并為這些元素創(chuàng)建一個統(tǒng)一的InjectionMetadata對象。
  3. 注入階段
    • postProcessProperties方法用于處理bean的屬性的后處理,特別是注入由@Value等注解標記的屬性。
    • InjectionMetadata#inject方法用于將所有需要注入的元素(例如帶有@Value的字段)注入到目標bean中。
    • AutowiredFieldElement#inject方法處理具體的字段注入,包括解析@Value注解中的值。
    • DefaultListableBeanFactory#resolveDependency方法從Spring的bean工廠中解析字段的值。
    • DefaultListableBeanFactory#doResolveDependency方法是實際解析工作的主要場所,涉及到處理@Value注解中的字符串屬性占位符和SpEL表達式,并確保值經(jīng)過正確的類型轉(zhuǎn)換。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容