最近項目在整合shiro權限認證模塊時,給本身挖了一個深坑,也是分析了很久才定位到問題的所在,根本緣由仍是對spring相關的技術點掌握的不夠嫺熟。本文基於springboot 2.1.5進行分析。下面會用簡單的Demo去還原問題的場景。java
簡單將遇到的問題還原一下,這段代碼中ShiroProperties 始終注入不到TestController中去。也就是shiroProperties
始終是null。spring
1@ConfigurationProperties(prefix = "test")
2@Data
3public class ShiroProperties {
4
5 private String name;
6 private List<String> chain;
7}
複製代碼
1@Configuration
2@EnableConfigurationProperties(ShiroProperties.class)
3public class ShiroConfiguration {
4}
複製代碼
1@Controller
2public class TestController {
3
4 @Autowired
5 private ShiroProperties shiroProperties;
6
7 @Bean
8 public Test test(){
9 System.out.println(shiroProperties);
10 return new Test();
11 }
12
13 @Bean
14 private TestProcessor testProcessor(){
15 return new TestProcessor();
16 }
17}
複製代碼
1public class Test {
2}
複製代碼
1public class TestProcessor implements
2 BeanFactoryPostProcessor, PriorityOrdered {
3
4 @Override
5 public void postProcessBeanFactory
6 (ConfigurableListableBeanFactory beanFactory) throws BeansException {
7
8 }
9
10 @Override
11 public int getOrder() {
12 return 0;
13 }
14}
複製代碼
配置文件application.properties:緩存
test.name=mars
test.chain[0]=aa
test.chain[1]=bbspringboot
最開始問題定位爲ShiroProperties 沒有註冊到容器中,因此注入時經過getBean沒法取到相應的bean。可是Debug也會發現ShiroProperties 的BeanDefinition已經在容器中持有,因此這個方向基本被排除了。
併發
從新定位問題,暫時定位在populateBean
屬性注入時初始化ShiroProperties 失敗。後面debug查看了一下populateBean
時的TestController的BeanDefinition,發現ShiroProperties 這個屬性根本沒有在依賴注入的範圍內。因此壓根就沒初始化。
app
propertyValues
屬性中。
例如@Autowired註解的屬性會經過類AutowiredAnnotationBeanPostProcessor去處理。我們簡單看一下AutowiredAnnotationBeanPostProcessor的構造函數。less
實例化的時候就會將Autowired和Value給緩存在一個autowiredAnnotationTypes
集合中。在後面進行BeanDefinition合併的時候,會遍歷autowiredAnnotationTypes
集合,取出註解對應的字段(例如Autowired和Value對應的字段),最後存放到BeanDefinition的propertyValues
屬性中,供後面的populateBean
調用時進行屬性的注入。具體調用方法以下圖:ide
上面的方法主要乾了兩個活:函數
shiroProperties
屬性。propertyValues
屬性中。因此這裏我們須要把思路往前推一下,須要判斷出了什麼問題纔會致使TestController的propertyValues
屬性爲空。beanDefinition的合併發生在doCreateBean
方法中。以下圖:源碼分析
經過這個方法進去,我們將全部的BeanPostProcessor打印出來,結果以下圖所示,並無AutowiredAnnotationBeanPostProcessor這個後置處理器。這也就能解釋我們的ShiroProperties爲啥注入不進去了,雖然找到了緣由,可是這不是一個正常的結果,正常狀況下依賴注入都是沒問題的,畢竟依賴注入是Spring的核心三大板塊之一。
這裏我們深刻分析一下AutowiredAnnotationBeanPostProcessor這個類是啥時候註冊到Spring容器的。
衆所周知,在容器初始化的過程當中,有一個關鍵性的方法refresh
,在refresh
方法調用過程當中,會調用invokeBeanFactoryPostProcessors
方法,全部的後置處理器都在這裏進行初始化。我們看看一共有多少個BeanPostProcessor:
從上圖能夠觀察到正常狀況下全部的postProcessorNames都是會被註冊到Spring容器中的。可是這裏有個例外,從上面的圖片能夠看出還有個testProcessor也在其中,這個是我們自定義的BeanPostProcessor。Spring在註冊這些BeanPostProcessor時會按一種規則去註冊:
因此TestProcessor會在AutowiredAnnotationBeanPostProcessor以前進行註冊,而TestProcessor是在TestController中的,因此說TestController做爲TestProcessor的factoryBean
,固然要先進行初始化,這樣最後致使TestController在初始化時沒法正常使用AutowiredAnnotationBeanPostProcessor的功能,而後使得ShiroProperties沒法正常注入。
到這裏一切疑惑迎刃而解~,針對上述問題,我們能夠另起一個無需初始化的PostProcessorConfig類去專門處理相似的BeanPostProcessor便可。
看一下Spring對於PriorityOrdered這個接口的註釋
Note: {@code PriorityOrdered} post-processor beans are initialized in a special phase, ahead of other post-processor beans. This subtly affects their autowiring behavior: they will only be autowired against beans which do not require eager initialization for type matching.
其實官方也有給我們提示這一點的,用了這個接口的後置處理器將會影響依賴注入的過程,推薦放在不須要初始化的bean中進行裝配。
PS:固然,這種狀況通常也遇不到,只是最近在整合shiro時,shiro有提供一個LifecycleBeanPostProcessor 處理器去管理相關bean的生命週期,須要註冊到Spring容器。而後就致使上面的狀況,和LifecycleBeanPostProcessor 在的同一個類的@Autowired,@Value聲明的字段均沒法正常注入。
下面是關於這個問題可能涉及到的一些源碼的分析。
通常狀況下,有兩種組合可使用。
兩則實現的目的一致,都是將@ConfigurationProperties註解的類交由Spring容器進行託管,在容器中註冊BeanDefinition,以供後期bean的裝配和獲取。
下面的內容將以第一種組合展開進行講解。咱們能夠先看看@EnableConfigurationProperties這個註解。
1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4@Import(EnableConfigurationPropertiesImportSelector.class)
5public @interface EnableConfigurationProperties {
6
7 /**
8 * Convenient way to quickly register {@link ConfigurationProperties} annotated beans
9 * with Spring. Standard Spring Beans will also be scanned regardless of this value.
10 * @return {@link ConfigurationProperties} annotated beans to register
11 */
12 Class<?>[] value() default {};
13
14}
複製代碼
這個註解上有@Import(EnableConfigurationPropertiesImportSelector.class)這樣一個註解,對於@Import這個註解,感興趣的朋友能夠去看看ConfigurationClassParser中的processImports
方法。大概流程就是調用EnableConfigurationPropertiesImportSelector中的selectImports
方法,並將解析出來的全部類註冊到容器中。
這裏一共是將兩個類注入到了容器中。他們都實現了ImportBeanDefinitionRegistrar接口,這個接口只有一個方法registerBeanDefinitions
,看方法名也就略知一二了吧。
ConfigurationPropertiesBeanRegistrar這個類的主要目的是用來收集EnableConfigurationProperties 中value值指定的類,並將其註冊到容器中。代碼量並很少,我截取下關鍵性代碼。
Class[]
註冊到Spring容器中。ConfigurationPropertiesBindingPostProcessorRegistrar這個類註冊了兩個後置處理器。
從代碼不難看出,上面的邏輯在一個容器中有且只會執行一次。執行過程當中會註冊一個實現BeanPostProcessor的bean後置處理器,還會註冊一個實現BeanFactoryPostProcessor的beanFactory後置處理器。
beansFactoryMetadata
集合中。bind
方法中進行。解析過程略顯複雜,這裏不作過多說明。
前面介紹了兩個ImportBeanDefinitionRegistrar接口的實現類,可是registerBeanDefinitions
方法的觸發點還未揭祕。瞭解過spring源碼的同窗想必對refresh
這個方法應該不陌生。我們從這裏開始挖掘一波~
refresh中有一個方法
invokeBeanFactoryPostProcessors
。
這個是觸發點的入口,一步步點進去,直到PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,這裏面會執行一些定義好的BeanFactoryPostProcessor中的postProcessBeanDefinitionRegistry
方法,
我們要關注的就是ConfigurationClassPostProcessor這個類。這個類其實幹了不少活,包括前面提到的對於@Import這個註解的處理等等。
概括成爲三步,每一步都內嵌在前一步中:
1 private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
2 registrars.forEach((registrar, metadata) ->
3 registrar.registerBeanDefinitions(metadata, this.registry));
4 }
複製代碼
這一塊就是我們的registerBeanDefinitions
方法觸發的地方了。也與前面一塊的講解也就串起來了。
解決問題的同時也是對本身成長的一種促進,此次源碼分析補充了前面不少強行看源碼時的一些疑惑點。畢竟spring盤子太大了,不必定全部的使用注意事項都會在官方文檔加以註釋,遇見了搜索引擎解決不了的問題仍是得本身手擼源碼------>O(∩_∩)O哈哈~。