@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();
}
四、主要功能
-
提供屬性注入
- 允許從不同的配置源(如屬性文件、系統(tǒng)屬性等)直接向 Spring 管理的 beans 中注入值。
-
支持表達式:
- SpEL (Spring Expression Language) 表達式:例如,
#{systemProperties.myProp}可以從系統(tǒng)屬性中獲取名為myProp的值。 - 屬性占位符:例如,
${my.app.myProp}可以從預(yù)定義的配置源,如application.properties或application.yml文件,獲取名為my.app.myProp的屬性值。
- SpEL (Spring Expression Language) 表達式:例如,
-
動態(tài)值解析
- 與只能在啟動時設(shè)置靜態(tài)值相比,
@Value注解可以解析動態(tài)表達式,從而為字段或構(gòu)造函數(shù)參數(shù)提供動態(tài)值。
- 與只能在啟動時設(shè)置靜態(tài)值相比,
-
用于字段、方法參數(shù)、構(gòu)造函數(shù)參數(shù)和注解
- 它可以被應(yīng)用到這些元素上,以提供必要的值。
-
與其他注解協(xié)同工作
- 盡管
@Value本身是用于注入值的,但它經(jīng)常與其他如@Component、@Service和@Controller之類的 Spring 注解一起使用,以創(chuàng)建完全由 Spring 管理和配置的 beans。
- 盡管
-
與屬性解析器配合
- 為了正確解析
@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)的操作。
-
MergedBeanDefinitionPostProcessor接口- 此接口提供的
postProcessMergedBeanDefinition方法允許后處理器修改合并后的bean定義。合并后的bean定義是一個已經(jīng)考慮了所有父bean定義屬性的bean定義。對于@Value注解的處理,這一步通常涉及到收集需要被解析的@Value注解信息并準備對其進行后續(xù)處理。 - ?? MergedBeanDefinitionPostProcessor接口傳送門
- 此接口提供的
-
InstantiationAwareBeanPostProcessor接口- 此接口提供了幾個回調(diào)方法,允許后處理器在bean實例化之前和實例化之后介入bean的創(chuàng)建過程。特別是,
postProcessProperties方法允許后處理器對bean的屬性進行操作。對于@Value注解,這通常涉及到實際地解析注解中的表達式或?qū)傩哉嘉环?,并將解析得到的值注入到bean中。 - ?? InstantiationAwareBeanPostProcessor接口傳送門
- 此接口提供了幾個回調(diào)方法,允許后處理器在bean實例化之前和實例化之后介入bean的創(chuàng)建過程。特別是,
收集階段
在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;
}
八、注意事項
-
SpEL表達式
-
@Value可以用來解析Spring Expression Language (SpEL) 表達式。確保我們的表達式是正確的,以防止運行時錯誤。
-
-
默認值
- 我們可以為
@Value注解提供默認值,以防止某個屬性在屬性文件中未被定義。例如:@Value("${some.property:default}")。
- 我們可以為
-
類型轉(zhuǎn)換
- 確保
@Value提供的值可以被轉(zhuǎn)換為字段或方法參數(shù)的類型。Spring會嘗試自動進行這種轉(zhuǎn)換,但不一定總是成功。
- 確保
-
不適用于復(fù)雜類型
- 盡管
@Value可以用于簡單的類型(如字符串、整數(shù)、枚舉等),但不應(yīng)用于復(fù)雜的bean注入,這時應(yīng)該使用@Autowired或@Inject。
- 盡管
-
不可用于
BeanPostProcessor或BeanFactoryPostProcessor-
@Value注解在BeanPostProcessor或BeanFactoryPostProcessor實現(xiàn)中是不起作用的,因為它們在Spring容器生命周期中的處理時機早于@Value的處理。
-
-
占位符解析器的配置
- 要使用屬性占位符(如
${property.name}),需要確保已配置了PropertySourcesPlaceholderConfigurer或PropertyPlaceholderConfigurer。
- 要使用屬性占位符(如
-
環(huán)境變量與系統(tǒng)屬性
- 我們可以使用
@Value來引用環(huán)境變量或系統(tǒng)屬性,例如:@Value("${JAVA_HOME}")。
- 我們可以使用
-
防止注入敏感信息
- 不要使用
@Value來注入敏感信息,如密碼,除非它們是適當加密的。考慮使用專門的解決方案,如Spring Cloud Config的Vault集成。
- 不要使用
-
循環(huán)依賴
- 盡管與
@Autowired不同,但需要注意的是,使用@Value可能間接導(dǎo)致循環(huán)依賴,尤其是當注入的值是其他bean的屬性時。
- 盡管與
-
性能考慮
- 大量使用SpEL表達式可能對性能產(chǎn)生輕微的影響,因為這些表達式需要在運行時進行解析。
九、總結(jié)
最佳實踐總結(jié)
-
啟動類入口:
- 使用
AnnotationConfigApplicationContext來啟動Spring上下文,該上下文支持基于Java注解的配置。 - 在創(chuàng)建上下文時,為其提供了
MyConfiguration作為配置類。
- 使用
-
配置類:
-
MyConfiguration類標記為@Configuration,表示它提供了bean定義的配置信息。 - 使用
@PropertySource指定一個屬性文件application.properties來為上下文加載屬性。 - 定義了一個bean:
MyService,確保其在Spring容器中被創(chuàng)建和初始化。
-
-
屬性文件:
- 在
application.properties文件中定義了幾個屬性,這些屬性可以在應(yīng)用程序中使用。
- 在
-
屬性注入:
- 在
MyService類中,展示了如何使用@Value注解進行五種不同方式的屬性注入,從直接注入字符串值到使用SpEL表達式。
- 在
-
注入結(jié)果的驗證:
- 實現(xiàn)
InitializingBean接口并重寫afterPropertiesSet方法來驗證注入的屬性值。 - 運行應(yīng)用后,該方法會打印出所有注入屬性的值,從而驗證
@Value注解正確地解析并注入了預(yù)期的值。
- 實現(xiàn)
源碼分析總結(jié)
-
核心后處理器:
-
AutowiredAnnotationBeanPostProcessor是處理@Value等注解的主要后處理器。它實現(xiàn)了兩個關(guān)鍵的接口,MergedBeanDefinitionPostProcessor和InstantiationAwareBeanPostProcessor,這兩個接口允許在bean的生命周期中的關(guān)鍵階段進行干預(yù),為屬性注入提供了機制。
-
-
收集階段:
- 在
postProcessMergedBeanDefinition方法中,AutowiredAnnotationBeanPostProcessor確保bean的定義與預(yù)期的自動裝配元數(shù)據(jù)匹配。 -
findAutowiringMetadata方法確保為給定的bean名稱和類獲取相關(guān)的InjectionMetadata,并利用緩存機制優(yōu)化性能。 -
buildAutowiringMetadata方法檢查類及其所有父類,確定帶有@Autowired、@Value等注解的字段和方法,并為這些元素創(chuàng)建一個統(tǒng)一的InjectionMetadata對象。
- 在
-
注入階段:
-
postProcessProperties方法用于處理bean的屬性的后處理,特別是注入由@Value等注解標記的屬性。 -
InjectionMetadata#inject方法用于將所有需要注入的元素(例如帶有@Value的字段)注入到目標bean中。 -
AutowiredFieldElement#inject方法處理具體的字段注入,包括解析@Value注解中的值。 -
DefaultListableBeanFactory#resolveDependency方法從Spring的bean工廠中解析字段的值。 -
DefaultListableBeanFactory#doResolveDependency方法是實際解析工作的主要場所,涉及到處理@Value注解中的字符串屬性占位符和SpEL表達式,并確保值經(jīng)過正確的類型轉(zhuǎn)換。
-