欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot解析@Value注解型解析注入時(shí)機(jī)及原理分析

 更新時(shí)間:2024年12月10日 09:34:02   作者:流光的咸魚(yú)  
@Value注解可以用來(lái)將外部的值動(dòng)態(tài)注入到Bean中,可以獲取配置文件中的屬性值和通過(guò)SpEl表達(dá)式獲取bean的屬性或方法

@Value的使用

@Value 注解可以用來(lái)將外部的值動(dòng)態(tài)注入到 Bean 中,在 @Value 注解中,可以使${} 與 #{} ,它們的區(qū)別如下:

(1)@Value("${}"):可以獲取對(duì)應(yīng)屬性文件中定義的屬性值。

(2)@Value("#{}"):表示 SpEl 表達(dá)式通常用來(lái)獲取 bean 的屬性,或者調(diào)用 bean 的某個(gè)方法。

根據(jù)注入的內(nèi)容來(lái)源,@ Value屬性注入功能可以分為兩種:通過(guò)配置文件進(jìn)行屬性注入和通過(guò)非配置文件進(jìn)行屬性注入。

基于配置文件的注入

單個(gè)注入

application.properties
user.nick = mimi
@RestController
public class DemoController {

    @Value("${user.nick:如果需要默認(rèn)值格式(:默認(rèn)值)}")
    private String name;
}

注意點(diǎn):當(dāng)文件類(lèi)型是 xx.properties是如果存在中文的話(huà),比如:

application.properties
user.nick = 秘密

就會(huì)出現(xiàn)亂碼,這是因?yàn)樵赟pringBoot的CharacterReader類(lèi)中,默認(rèn)的編碼格式是ISO-8859-1,該類(lèi)負(fù)責(zé).properties文件中系統(tǒng)屬性的讀取。如果系統(tǒng)屬性包含中文字符,就會(huì)出現(xiàn)亂碼。

解決的方法大概有三種:

(1)改為yml、yaml格式的;(2)配置文件里中文轉(zhuǎn)義掉;(3)自己使用的時(shí)候手動(dòng)轉(zhuǎn)換

這里說(shuō)下yml為什么可以,因?yàn)?yml或.yaml格式的配置文件,最終會(huì)使用UnicodeReader類(lèi)進(jìn)行解析,它的init方法中,首先讀取BOM文件頭信息,如果頭信息中有UTF8、UTF16BE、UTF16LE,就采用對(duì)應(yīng)的編碼,如果沒(méi)有,則采用默認(rèn)UTF8編碼。

靜態(tài)變量注入

默認(rèn)被static修飾的變量通過(guò)@Value會(huì)注入失敗,我們注解可以寫(xiě)到方法上:

private static String name;
@Value("${user.nick:默認(rèn)值}")
public void setName(String name) {
    this.name = name;
}

非配置文件的注入

基本類(lèi)型

@Value注解對(duì)這8中基本類(lèi)型和相應(yīng)的包裝類(lèi),有非常良好的支持,例如:

@Value("${user.test:30000}")
private Integer size;
@Value("${user.test:30000}")
private int size;

數(shù)組

但只用上面的基本類(lèi)型是不夠的,特別是很多需要批量處理數(shù)據(jù)的場(chǎng)景中。這時(shí)候可以使用數(shù)組,它在日常開(kāi)發(fā)中使用的頻率很高。我們?cè)诙x數(shù)組時(shí)可以這樣寫(xiě):

@Value("${cs.test:1,2,3,4,5}")
private int[] array;

spring默認(rèn)使用逗號(hào)分隔參數(shù)值。如果用空格分隔,例如:@Value("${kuku.test:1 2 3 4 5}") spring會(huì)自動(dòng)把空格去掉,導(dǎo)致數(shù)據(jù)中只有一個(gè)值:12345,所以注意千萬(wàn)別搞錯(cuò)了。

如果我們把數(shù)組定義成:short、int、long、char、string類(lèi)型,spring是可以正常注入屬性值的。

但如果把數(shù)組定義成:float、double類(lèi)型,啟動(dòng)項(xiàng)目時(shí)就會(huì)直接報(bào)錯(cuò)。如果使用int的包裝類(lèi)Integer[],比如:

@Value("${cs.test:1,2,3,4,5}")
private Integer[] array;

啟動(dòng)項(xiàng)目時(shí)同樣會(huì)報(bào)異常。此外,定義數(shù)組時(shí)一定要注意屬性值的類(lèi)型,必須完全一致才可以,如果出現(xiàn)下面這種情況:

@Value("${cs.test:1.0,aa,3,4,5}")
private int[] array;

屬性值中包含了1.0和aa,顯然都無(wú)法將該字符串轉(zhuǎn)換成int。

集合類(lèi)

List是如何注入屬性值的:

user.test = 10,11,12,13
@Value("${cs.test}")
private List<Integer> test;

其它

注入bean,一般都是用的@Autowired或者@Resource注解,@Value注解也可以注入bean,它是這么做的:

@Value("#{roleService}")
private RoleService roleService;

通過(guò)EL表達(dá)式,@Value注解已經(jīng)可以注入bean了。既然能夠拿到bean實(shí)例,接下來(lái),可以再進(jìn)一步,獲取成員變量、常量、方法、靜態(tài)方法:

@Value("#{roleService.DEFAULT_AGE}")
private int myAge;

前面的內(nèi)容都是基于bean的,但有時(shí)我們需要調(diào)用靜態(tài)類(lèi),比如:Math、xxxUtil等靜態(tài)工具類(lèi)的方法,可以這么寫(xiě):

// 注入系統(tǒng)的路徑分隔符到path中
@Value("#{T(java.io.File).separator}")
private String path;
// 注入一個(gè)隨機(jī)數(shù)到randomValue中
@Value("#{T(java.lang.Math).random()}")
private double randomValue;

還可以進(jìn)行邏輯運(yùn)算:

// 拼接
@Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}")
private String value;
// 邏輯判斷
@Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('蘇三')}")
private String operation;

上面這么多@Value的用法,歸根揭底就是${}和#{}的用法,我們來(lái)看看兩者的區(qū)別:

  • ${}:主要用于獲取配置文件中的系統(tǒng)屬性值,可以設(shè)置默認(rèn)值。如果在配置文件中找不到屬性的配置,則注入時(shí)用默認(rèn)值,如果都沒(méi)有會(huì)直接報(bào)錯(cuò)
  • #{}:主要用于通過(guò)spring的EL表達(dá)式,獲取bean的屬性,或者調(diào)用bean的某個(gè)方法,還有調(diào)用類(lèi)的靜態(tài)常量和靜態(tài)方法,如果是調(diào)用類(lèi)的靜態(tài)方法,則需要加T(包名 + 方法名稱(chēng))。

AutowiredAnnotationBeanPostProcessor 類(lèi)介紹

首先解析的都是我們的Spring管理的Bean,我們的Bean又有配置型Configuration、服務(wù)型Controller、Service等的,但他們都是@Component的,那解析@Value的時(shí)候是什么時(shí)候呢,其實(shí)就是創(chuàng)建Bean的時(shí)候,也就是實(shí)例化的時(shí)候,而實(shí)例化又分懶加載的和隨著SpringBoot啟動(dòng)就會(huì)創(chuàng)建的在刷新方法里的 finishBeanFactoryInitialization 會(huì)對(duì)不是懶加載的Bean進(jìn)行實(shí)例化,這就涉及到Bean的生命周期啦,其實(shí)解析和屬性注入都是通過(guò)后置處理器進(jìn)行的。

  • 解析:doCreateBean 方法里的 applyMergedBeanDefinitionPostProcessors執(zhí)行后置處理器進(jìn)行收集,實(shí)際收集的處理器是:AutowiredAnnotationBeanPostProcessor
  • 注入:populateBean 方法里的 postProcessProperties 執(zhí)行后置處理器進(jìn)行注入,實(shí)際注入的處理器還是:AutowiredAnnotationBeanPostProcessor

我們先看下 AutowiredAnnotationBeanPostProcessor類(lèi)圖:

這個(gè)后置處理是什么時(shí)候加載進(jìn)來(lái)的呢?我們來(lái)看下:

@Value 解析

AutowiredAnnotationBeanPostProcessor 構(gòu)造方法:

可以看到實(shí)例化的時(shí)候,已經(jīng)把 @Autowired和@Value初始化到 autowiredAnnotationTypes 集合中了。

我們先看下解析的方法:

主要方法就是 findAutowiringMetadata,我們進(jìn)去看一下:

核心方法就是 buildAutowiringMetadata,進(jìn)行分析我們進(jìn)去看看:

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {

        // 如果沒(méi)有 Autowired Value 注解信息就返回 EMPTY
		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<>();
            // 遍歷Class中的所有field,根據(jù)注解判斷每個(gè)field是否需要被注入
			ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // 看看field是不是有注解@Autowired 或 @Value
				MergedAnnotation<?> ann = findAutowiredAnnotation(field);
				if (ann != null) {
                    // 不支持靜態(tài)類(lèi)
					if (Modifier.isStatic(field.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static fields: " + field);
						}
						return;
					}
                    // 確定帶注解的字段是否存在required并且是true 默認(rèn)是true
					boolean required = determineRequiredStatus(ann);
                    // AutowiredFieldElement 對(duì)象包裝一下
					currElements.add(new AutowiredFieldElement(field, required));
				}
			});
            // 遍歷Class中的所有method,根據(jù)注解判斷每個(gè)method是否需要注入
			ReflectionUtils.doWithLocalMethods(targetClass, method -> {
                // 這個(gè)方法會(huì)查找給定方法的橋接方法。橋接方法用于處理子類(lèi)重寫(xiě)泛型方法的情況,以保證編譯后的代碼在運(yùn)行時(shí)的類(lèi)型安全。
				Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                // 這個(gè)方法用于檢查 method 和其找到的 bridgedMethod 是否是可見(jiàn)的橋接方法對(duì)。也就是說(shuō),它會(huì)檢查這兩個(gè)方法的訪(fǎng)問(wèn)權(quán)限是否匹配。這一行的意思是,如果 method 和 bridgedMethod 不是可見(jiàn)的橋接方法對(duì),那么就退出當(dāng)前方法,不繼續(xù)執(zhí)行后續(xù)代碼。這確保了只有在 method 和 bridgedMethod 之間的可見(jiàn)性匹配時(shí),才會(huì)繼續(xù)進(jìn)行后續(xù)的邏輯處理。
				if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
					return;
				}
                // 看看方法是不是有注解@Autowired 或 @Value
				MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
				if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                    // 靜態(tài)方法略過(guò)
					if (Modifier.isStatic(method.getModifiers())) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation is not supported on static methods: " + method);
						}
						return;
					}
                    // 參數(shù)為空的方法略過(guò)
					if (method.getParameterCount() == 0) {
						if (logger.isInfoEnabled()) {
							logger.info("Autowired annotation should only be used on methods with parameters: " +
									method);
						}
					}
                    // 判斷是不是有 required
					boolean required = determineRequiredStatus(ann);
                    // 獲取目標(biāo)class中某成員擁有讀或?qū)懛椒ㄅc橋接方法一致的PropertyDescriptor
					PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                    // AutowiredMethodElement 對(duì)象包裝一下
					currElements.add(new AutowiredMethodElement(method, required, pd));
				}
			});

			elements.addAll(0, currElements);
            // 遞歸調(diào)用
			targetClass = targetClass.getSuperclass();
		}
		while (targetClass != null && targetClass != Object.class);
        // 包裝成 InjectionMetadata 對(duì)象  targetClass屬性就是當(dāng)前的類(lèi)   injectedElements屬性就是分析的字段或者方法
		return InjectionMetadata.forElements(elements, clazz);
	}

可以看到會(huì)對(duì)類(lèi)的方法的屬性進(jìn)行遍歷以及父親的遞歸,對(duì)于字段會(huì)忽略掉static修飾的,對(duì)于方法會(huì)也會(huì)忽略掉static以及參數(shù)為空的。最后解析到的屬性會(huì)包裝成 AutowiredFieldElement ,方法會(huì)包裝成 AutowiredMethodElement ,最后統(tǒng)一放進(jìn)集合中,包裝成 InjectionMetadata 對(duì)象返回,并放進(jìn)緩存。

我們拿個(gè)例子:

@Value("${cs.list}")
private List<Integer> list;
private static String name;
@Value("${cs.mimi}")
public void setName(String name) {
    this.name = name;
}

@Value 注入

這里針對(duì)上述的例子進(jìn)行注入

可以看到最后 element.inject 就是在解析階段調(diào)用對(duì)應(yīng)的注入方法進(jìn)行注入。

AutowiredFieldElement # inject 屬性注入

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 帶你玩轉(zhuǎn)Kafka之初步使用

    帶你玩轉(zhuǎn)Kafka之初步使用

    最近開(kāi)發(fā)的項(xiàng)目中,kafka用的比較多,為了方便梳理,所以記錄一些關(guān)于kafka的文章,這篇文章主要給大家介紹了關(guān)于Kafka初步使用的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • Java連接各種數(shù)據(jù)庫(kù)的方法

    Java連接各種數(shù)據(jù)庫(kù)的方法

    這篇文章主要介紹了Java連接各種數(shù)據(jù)庫(kù)的方法,實(shí)例分析了java連接MySQL、SQL Server、Sysbase、Oracle、PostgreSQL及DB2等數(shù)據(jù)庫(kù)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Java數(shù)組添加元素的兩種方法

    Java數(shù)組添加元素的兩種方法

    這篇文章主要介紹了Java數(shù)組添加元素的兩種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • spring boot如何使用AOP統(tǒng)一處理web請(qǐng)求

    spring boot如何使用AOP統(tǒng)一處理web請(qǐng)求

    這篇文章主要介紹了spring boot如何使用AOP統(tǒng)一處理web請(qǐng)求,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Java設(shè)計(jì)模式之策略模式案例詳解

    Java設(shè)計(jì)模式之策略模式案例詳解

    策略模式(Strategy?Pattern)定義了一組同類(lèi)型的算法,在不同的類(lèi)中封裝起來(lái),每種算法可以根據(jù)當(dāng)前場(chǎng)景相互替換,從而使算法的變化獨(dú)立于使用它們的客戶(hù)端即算法的調(diào)用者
    2022-07-07
  • SpringBoot中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移的詳細(xì)流程

    SpringBoot中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移的詳細(xì)流程

    本文介紹了如何在Spring Boot項(xiàng)目中使用Flyway進(jìn)行數(shù)據(jù)庫(kù)遷移,Flyway通過(guò)SQL腳本管理數(shù)據(jù)庫(kù)變更,支持自動(dòng)執(zhí)行和版本控制,避免了手動(dòng)執(zhí)行SQL腳本的錯(cuò)誤和維護(hù)困難,需要的朋友可以參考下
    2025-02-02
  • Java如何獲取Cookie和Session

    Java如何獲取Cookie和Session

    Cookie?和?Session之間主要是通過(guò)?SessionId?關(guān)聯(lián)起來(lái)的,?SessionId是?Cookie?和?Session?之間的橋梁,這篇文章主要介紹了Java獲取Cookie和Session的方法,需要的朋友可以參考下
    2024-01-01
  • java jackson 將對(duì)象轉(zhuǎn)json時(shí),忽略子對(duì)象的某個(gè)屬性操作

    java jackson 將對(duì)象轉(zhuǎn)json時(shí),忽略子對(duì)象的某個(gè)屬性操作

    這篇文章主要介紹了java jackson 將對(duì)象轉(zhuǎn)json時(shí),忽略子對(duì)象的某個(gè)屬性操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-10-10
  • 程序包org.springframework.boot不存在的問(wèn)題解決

    程序包org.springframework.boot不存在的問(wèn)題解決

    本文主要介紹了程序包org.springframework.boot不存在的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-09-09
  • FasfDFS整合Java實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解

    FasfDFS整合Java實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解

    這篇文章主要介紹了FasfDFS整合Java實(shí)現(xiàn)文件上傳下載功能實(shí)例詳解,需要的朋友可以參考下
    2017-08-08

最新評(píng)論