如果你覺得内容對你有幫助的話,不如給個贊,鼓勵一下更新😂。
本文内容來自《小馬哥講Spring核心程式設計思想》,基本涵蓋了工作中用到的注入相關的概念,所涵蓋的知識點較多。
依賴注入的模式和類型
依賴注入的模式
手動模式 - 配置或者程式設計的方式,提前安排注入規則
- XML 資源配置元資訊
- Java 注解配置元資訊,比如@Autowired、@Resource
- API 配置元資訊
自動模式 - 實作方提供依賴自動關聯的方式,按照內建的注入規則
- Autowiring(自動綁定)
依賴注入的類型
依賴注入類型 | 配置中繼資料舉例 |
---|---|
Setter 方法 | |
構造器 | |
字段 | @Autowired User user; |
方法 | @Autowired public void user(User user) { … } |
接口回調 | class MyBean implements BeanFactoryAware { … } |
構造器和Setter注入的利與弊
使用構造器注入的好處:
- 保證依賴不可變(final關鍵字)
- 保證依賴不為空(省去了我們對其檢查)
- 保證傳回用戶端(調用)的代碼的時候是完全初始化的狀态
- 避免了循環依賴
- 避免了和容器的高度耦合,提升了代碼的可複用性
總結: 構造器注入适用于強制對象注入;Setter 注入适合于可選對象注入;并且構造器注入在構造過程中可以保證線程的安全
自動綁定(Autowiring)的模式
模式 | 說明 |
---|---|
no | 預設值,未激活 Autowiring,需要手動指定依賴注入對象。 |
byName | 根據被注入屬性的名稱作為 Bean 名稱進行依賴查找,并将對象設定到該屬性。 |
byType | 根據被注入屬性的類型作為依賴類型進行查找,并将對象設定到該屬性。 |
constructor | 特殊 byType 類型,用于構造器參數。 |
延遲依賴查找、延遲依賴注入
Bean 延遲依賴查找接口
- org.springframework.beans.factory.ObjectFactory
- org.springframework.beans.factory.ObjectProvider
Bean 延遲依賴注入也是這兩個:
- 使用 API ObjectFactory 延遲注入
- 使用 API ObjectProvider 延遲注入(推薦)
限定注入@Qualifier
- 通過Bean名稱限定
- 通過分組限定
實作@Autowired和@Resource等注解的後置處理器
- AutowiredAnnotationBeanPostProcessor
- 處理 @Autowired 以及 @Value 注解
- 在postProcessMergedBeanDefinition方法中查找自動注入的元資訊來封裝注入元素資訊
- 在postProcessProperties方法中實作屬性和方法注入
- CommonAnnotationBeanostProcessor(生命周期注解也是在這裡面實作的)
- (條件激活)處理 JSR-250 注解,如 @PostConstruct 等
@Autowired注入過程
所有方法都在AutowiredAnnotationBeanPostProcessor#類裡
- 調用postProcessProperties()方法(spring 5.1之後才是這個方法名,5.1之前是postProcessPropertyValues)
- postProcessProperties方法會比bean的setXX()方法先調用
- findAutowiringMetadata()方法會找出一個bean加了@Autowired注解的字段(包括父類的),并且該方法做了緩存
- xml配置的bean與bean之間是可以有繼承關系的,有另一個周期(不是autowired的流程)是把配置super bean的屬性合并到目前bean,之後會調用後置方法postProcessMergedBeanDefinition,該方法也會調用一次findAutowiringMetadata
- 經測試,postProcessMergedBeanDefinition會比postProcessProperties先執行,是以調用postProcessProperties時都是直接拿緩存
- 調用inject方法, 底層調用的是AutowiredFieldElement和AutowiredMethodElement的inject方法。(獲得對應的bean,然後通過反射注入到類的字段上)
- inject方法會調用DefaultListableBeanFactory#resolveDependency方法,這方法會根據@Autowired字段資訊來比對出符合條件的bean
Spring Bean的來源
依賴查找的來源
來源 | 配置中繼資料 |
---|---|
Spring BeanDefinition | |
@Bean public User user(){…} | |
BeanDefinitionBuilder | |
單例對象 | API實作 |
依賴注入的來源
來源 | 配置中繼資料 |
---|---|
Spring BeanDefinition | |
@Bean public User user(){…} | |
BeanDefinitionBuilder | |
單例對象 | API實作 |
非Spring容器管理對象 | |
外部化配置作為依賴來源 |
Spring容器管理和遊離對象
來源 | 配置中繼資料 | 生命周期管理 | 配置元資訊 | 使用場景 |
---|---|---|---|---|
Spring BeanDefinition | 是 | 是 | 有 | 依賴查找、依賴注入 |
單例對象 | 是 | 否 | 無 | 依賴查找、依賴注入 |
ResolvableDependency | 否 | 否 | 無 | 依賴注入 |
Spring BeanDefinition 作為依賴來源
- 中繼資料:BeanDefinition
- 注冊:
- 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 非命名方式: BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be anDefinitionRegistry)
- 配置類方式:AnnotatedBeanDefinitionReader#register(Class…)
- 類型:延遲和非延遲
- 順序:Bean 生命周期順序按照注冊順序
- 舉例:
- 實作
ImportBeanDefinitionRegistrar
- 實作
BeanDefinitionRegistryPostProcessor
- 實作
單例對象作為依賴來源
- 要素
- 來源:外部普通Java 對象(不一定是POJO)
- 注冊:SingletonBeanRegistry#registerSingleton
- 限制
- 無生命周期管理
- 無法實作延遲初始化Bean
非Spring容器管理對象作為依賴來源
- 要素
- 注冊:ConfigurableListableBeanFactory#registerResolvableDependency
- 限制
- 無生命周期管理
- 無法實作延遲初始化Bean
- 無法通過依賴查找
registerResolvableDependency和registerSingleton的差別:
前者是注冊可以解析的依賴關系,當注入的類型為dependencyType的時候,注入autowiredValue,并将注入類型與注入值的關系存儲在map中。常用于解決在Spring架構内部自動裝配的時候如果一個接口有多個實作類,并且都已經放到IOC中去了,那麼自動裝配的時候就會出異常,因為spring不知道把哪個實作類注入進去的問題。
外部化配置作為依賴來源
- 要素
- 類型:非正常Spring對象依賴來源
- 限制
- 無生命周期管理
- 無法實作延遲初始化Bean
- 無法通過依賴查找
Spring 依賴處理的過程
Spring依賴處理是依賴注入的一個環節,就是說在注入過程中我們把這個對象的依賴來進行解析,然後利用反射進行指派注入。
- 入口 - DefaultListableBeanFactory#resolveDependency
- 依賴描述符 - DependencyDescriptor
- 自定綁定候選對象處理器 - AutowireCandidateResolver
bean的屬性填充populateBean方法最終會走到DefaultListableBeanFactory#resolveDependency方法,最後還是會走到AbstractBeanFactory#getBean方法中去擷取Bean
如何解決單例Bean中需要注入Prototype作用域Bean的問題
在大多數應用場景中,容器中大部分bean都是singleton。當一個單例bean需要與另一個單例bean協作或一個非單例bean需要與另一個非單例bean協作時,通常是将一個bean定義為另一個bean的屬性來處理依賴關系。當bean的生命周期不同時,就會出現問題。可能是在A的每個方法調用上,假設單例bean A需要使用非單例(原型)bean B。容器隻會建立單例bean A一次,是以隻有一次機會來設定屬性。容器無法在每次進行A方法調用時,都為bean A提供一個全新的bean B執行個體。(注:也就是多例可以調用單例,但是單例無法調用多例)
解決方案是放棄一些控制翻轉。通過實作
ApplicationContextAware
接口,進而使bean A能夠拿到容器的上下文 ,并在每次bean A 需要bean B時,通過調用容器上下文的
getBean("B")
方法來請求得到(通常是新建立的)bean B執行個體。
使用ApplicationContextAware回調接口解決
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
但是以上方法并不可取,因為業務代碼會使用Spring内部的上下文,也就是會和Spring Framework耦合到一起
方法注入
查找方法注入是容器覆寫受容器管理的bean上的方法并傳回容器中另一個命名的bean的查找結果的能力。查找通常涉及一個原型bean。Spring架構通過使用CGLIB庫中的位元組碼生成來動态生成覆寫該方法的子類。
方法注入條件:
- 為了使這動态子類起作用,Spring容器的子類class不能用
修飾,且重寫的方法也不能用final
修飾。final
- 對具有抽象方法的類進行單元測試,需要你自己建立這個類的子類并對
方法進行實作。abstract
- 元件掃描也需要具體的方法,這需要具體的類去支援。
- 進一步的關鍵限制是查找方法對工廠方法不起作用,尤其不适用于配置類中的
方法,因為在這種情況下,容器不負責建立執行個體,是以不能動态地建立運作時生成的子類。@Bean
在包含注入方法的用戶端類(本例中
CommandManager
)中,要求注入的方法具有以下簽名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是
abstract
的,則動态生成的子類将實作該方法。否則,動态生成的子類将覆寫原始類中定義的具體方法。
注解版本實作:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
XML實作:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
靜态@Bean方法和執行個體@Bean方法的差別
設定方法為static,是因為bean定義的一個周期性問題:
- 如果是非static,那麼這個bean的建構是依賴于聲明類的這個bean來處理的。
- 如果是static,那麼它就脫離了這個實作,變成了一個獨立的bean,是以如果你需要bean初始化或提前初始化,那麼可以選擇性的标注成static。
具體細節在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法中,如下:
if (metadata.isStatic()) {
// static @Bean method
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
beanDef.setFactoryMethodName(methodName);
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}
@Bean方法處理的時機
詳細請看ConfigurationClassPostProcessor類 => ConfigurationClassParser
怎麼實作不注入Spring容器,卻也完成依賴注入?
本着"減輕"Spring容器"負擔"的目的,"手動"精細化控制Spring内的Bean元件。像有的這種解析器其實是完全沒必要放進容器内的,需要什麼元件讓容器幫你完成注入即可,自己就沒必要放進去
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
private ApplicationContext applicationContext;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CurrUserArgumentResolver resolver = new CurrUserArgumentResolver();
// 利用工廠給容器外的對象注入所需元件
applicationContext.getAutowireCapableBeanFactory().autowireBean(resolver);
argumentResolvers.add(resolver);
}
}
本姿勢的技巧是利用了
AutowireCapableBeanFactory
巧妙完成了給外部對象賦能,進而即使自己并不是容器内的Bean,也能自由注入、使用容器内
Bean
的能力(同樣可以随意使用
@Autowired
注解了~),這種方式是侵入性最弱的。