spring boot自動裝配機制 學習筆記
- 閒談
- spring以及spring boot相關概念理解
- 約定大於配置的體現
- 註解的理解
- 簡單分析@Configuration
- 簡單分析@ComponentScan
- EnableAutoConfiguration 非常重要,壓軸
- 1. @Import理解
- 2. ImportSelector理解
- 3. ImportBeanDefinitionRegistrar 理解
- 5@AutoConfigurationPackage
- 通過@Configuration裝配Bean
- 通過@Import(OtherConfiguration.class) 裝配別的路徑下@Configuration註解的Bean
- 通過ImportBeanDefinitionRegistrar 和 ImportSelector 裝配Bean實戰
- 1. 簡單的三個Javabean
- 2.自定義配置類 ImportSelector
- 3.自定義配置類 LoggerDefinetionRegister 擴展點
- 4.自定義註解中,導入CacheImportSelector.class,LoggerDefinetionRegister.class
- 測試註解,並添加 exclude進行條件裝配,
- 將類的加載條件放到配置文件中去實戰
閒談
爲什麼陳咬金靠着三板斧就可以成爲國公?
那學習spring自動裝配的三板斧就必須得瞭解了,學好spring是學好spring boot的基礎,因爲相比spring,spring boot沒有新的技術點,它是服務於spring框架的框架,用於快速構建springweb項目,遵守約定大於配置的原則,
前邊我們通過spring的SPI機制(滿足目錄一致,文件名一致,key要存在並符合當前的加載)自定義了一個自己的starter組件,並在springboot項目中成功引用,通過測試,但是當時我們只是用到了最簡單的制動裝配,現在我們來羅列一下springboot中的自動裝配機制
spring以及spring boot相關概念理解
IOC spring mvc DI DL AOP
最早spring 的bean的配置,在applicationContext.xml 中,配置bean 標籤,
spring 3.0開始,由於Java5 開始支持Javaconfig ,所有spring開始支持註解的方式代替之前的大量的濫用的xml配置文件,有了spring 3.0,纔有了springboot 出現的可能
約定大於配置的體現
1.maven 或gradle的目錄結構,默認會以jar的方式打包,默認會有resource文件夾
2.spring-boot-starter-web:內置Tomcat、resource、templete,static
3.默認的application.properties 或yml
4.默認通過 spring.profiles.active 屬性來決定運行環境時讀取的配置文件
5.EnableAutoConfiguration 默認對於依賴的 starter 進行自動裝載
註解的理解
1 @AutoConfiguration
2 starter
3. actuator
4. SpringBoot CLI
IOC控制反轉
@SpringBootApplication 複合註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
SpringBootApplication 本質上是由 3 個註解組成,分別是
- @Configuration //配置類註解
- @EnableAutoConfiguration //自動裝配註解
- @ComponentScan//掃描註解
簡單分析@Configuration
1.定義普通Javabean
//希望這個類被springj加載 xml的形式 <bean class="com.DemoClass"
public class DemoClass {
public void say(){
System.out.println("say hello");
}
}
2.定義配置類,加載普通Javabean
相當於一個applicationContext.xml(在配置的時代,我們通常會將不同的spring xml 導入到applicationContext.xml中,達到業務bean配置的分離)加載bean,同理@Configuration也可以是多個
@Configuration
public class DemoConfiguration {
//單利,每次通過容器獲取,返回同一個
@Bean //等同於 <bean class="com.DemoClass" 同樣的 @Scope 上也有很多屬性
public DemoClass getDemo(){
return new DemoClass();
}
}
3.模擬springboot 啓動 run()測試加載過程
public class DemoMainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoConfiguration.class);
DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
System.out.println(demoClass);
}
}
4.測試結果
20:58:26.312 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'getDemo'
com.example.demo.demoone.DemoClass@77f80c04
簡單分析@ComponentScan
等同於<context:component-scan
掃描什麼?
掃描 @Component 註解以及其派生出來的註解
如 @Service @Controller@Repository @RestController 爲什麼會掃描這些註解呢,因爲他們都是Component派生出來的,屬於Component系列
託管這些註解標註的類
1.創建帶@Component 註解的Javabean
@Component
public class DemoClass {
public void say(){
System.out.println("say hello");
}
}
2.利用@ComponentScan 掃描當前類所在的包,或者掃描指定的包下,加載bean
@ComponentScan //默認掃描當前路徑下的包
public class DemoMainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(DemoMainTest.class);
DemoClass demoClass= (DemoClass) annotationConfigApplicationContext.getBean(DemoClass.class);
System.out.println(demoClass);
}
}
3測試結果
21:31:17.956 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demoClass'
com.example.demo.demotow.DemoClass@4f6ee6e4
EnableAutoConfiguration 非常重要,壓軸
EnableAutoConfiguration 的 主 要 作 用 其 實 就 是 幫 助springboot 應用把所有符合條件的@Configuration 配置都加載到當前 SpringBoot 創建並使用的 IoC 容器中。
爲表重要,我們貼點代碼,前兩個都不用貼
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@AutoConfigurationPackage
和@Import({AutoConfigurationImportSelector.class})
有興趣也瞭解一下@EnableWebMvc 引入了 MVC 框架在 Spring 應用中需要用到的所有
bean; @EnableScheduling 開啓計劃任務的支持(定時任務)
1. @Import理解
等價於 xml配置的 <import resource .../>
import 就是將多個分開的配置合併在一個配置中。與在JavaConfig 中該註解所表達的意義是一樣的。
@Import可以配置三種不同的 class
1. 第一種基於普通 bean 或者帶有
@Configuration 的 bean 進行諸如
2. 實現 ImportSelector 接口進行動態注入 將方法返回的字符串數組作爲bean注入到容器中
3. 實現 ImportBeanDefinitionRegistrar 接口進行動態注入 自定義註冊bean
2. ImportSelector理解
上面的代碼@Import({AutoConfigurationImportSelector.class})
,是因爲AutoConfigurationImportSelector
實現了importSelector
接口,其實現藉助了Spring 框架提供的一個工具類 SpringFactoriesLoader
的支持,以及用到了 Spring 提供的條件註解@ConditionalOnXXXXX(*.class)
等同於spring-autoconfiguration-metadata.properties 中的條件 XXX.ConditionalOnMissingClass=,和過濾‘exclude’ 選擇性的針對需要加載的 bean 進行條件過濾
SpringFactoriesLoader理解
加載器,它其實和java 中的 SPI 機制的原理是一樣,不過它比 SPI 更好的點,在於不會一次性加載所有的類,而是根據 key 進行加載。首先, SpringFactoriesLoader 的作用是從classpath/META-INF/spring.factories 文件中,根據 key 來加載對應的類到 spring IoC 容器中。
AutoConfigurationImportSelector
藉助它完成類的加載,它的key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration
,對應N個字符串,SpringFactoriesLoader 加載後,逐一處理
如有以下的values
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
SpringFactoriesLoader 這個類我們也可以直接用,搞一些我們自己特殊東西
AutoConfigurationImportSelector 理解
會先掃描 spring-autoconfiguration-metadata.properties文件,最後在掃描 spring.factories 對應的類時,會結合前面的元數據進行過濾,爲什麼要過濾呢? 原因是很多的@Configuration 其實是依託於其他的框架來加載的,如果當前的 classpath 環境下沒有相關聯的依賴,則意味着這些類沒必要進行加載,所以,通過這種條件過濾可以有效的減少@configuration 類的數量從而降低SpringBoot 的啓動時間
Conditional 和Conditional系列的 理解
Conditional 有兩種方式,可以在 spring-autoconfiguration-metadata.properties 中定義,如com.test.BeanConfiguration.ConditionalOnMissingClass=com.alibaba.fastjson.JSON
也可以直接使用@ConditionalOnMissingClass("com.alibaba.fastjson.JSON")
這種註解,他們是等價的
Conditions 描述
@ConditionalOnBean 在存在某個 bean 的時候
@ConditionalOnMissingBean 不存在某個 bean 的時候
@ConditionalOnClass 當前 classpath 可以找到某個類型的類時
@ConditionalOnMissingClass 當前 classpath 不可以找到某個類型的類時
@ConditionalOnResource 當前 classpath 是否存在某個資源文件
@ConditionalOnProperty 當前 jvm 是否包含某個系統屬性爲某個值
@ConditionalOnWebApplication 當前 spring context 是否是 web 應用程序
3. ImportBeanDefinitionRegistrar 理解
和ImportSelector 功能類似,也支持條件注入,此方法可以對裝配的類做一個過濾處理
主要工作的方法是
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
5@AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
最新的版本中,這個註解,導入了Registrar.class,它是ImportBeanDefinitionRegistrar的一個子類,之前的版本中是直接導入ImportBeanDefinitionRegistrar.class
通過@Configuration裝配Bean
1創建now包,並創建簡單Java bean
public class BeanOne {
}
2.創建other 包,並創建另一個簡單Java bean
public class BeanTow {
}
3.now包創建配置類
@Configuration
public class BeanConfiguration {
@Bean
public BeanOne getBeanOne(){
return new BeanOne();
}
}
4.now包創建main類驗證加載
public class DemoMainTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(BeanConfiguration.class);
String [] defNames=annotationConfigApplicationContext.getBeanDefinitionNames();
for (String str:defNames) {
System.out.println(str);
}
}
}
5.返回結果
beanConfiguration
getBeanOne
沒有加載到BeanTow 怎麼才能加載到呢,在other包中創建 配置類,有import能導入@Configuration 註解的類,進行加入
通過@Import(OtherConfiguration.class) 裝配別的路徑下@Configuration註解的Bean
6.在 5 的基礎上other包中創建 配置類
@Configuration
public class OtherConfiguration {
@Bean
public BeanTow getBeanTow(){
return new BeanTow();
}
}
7.在now包的配置類中@ import()導入 other 包中的配置類OtherConfiguration
@Configuration
@Import(OtherConfiguration.class)
public class BeanConfiguration {
@Bean
public BeanOne getBeanOne(){
return new BeanOne();
}
}
8.驗證other 包 中的類有沒有被自動裝配
返回結果,加載成功
beanConfiguration
com.example.demo.demnthree.other.OtherConfiguration
getBeanTow
getBeanOne
通過ImportBeanDefinitionRegistrar 和 ImportSelector 裝配Bean實戰
1. 簡單的三個Javabean
public class AttachService {
}
public class CacheService {
}
public class LoggerService {
}
2.自定義配置類 ImportSelector
public class CacheImportSelector implements ImportSelector {
//AnnotationMetadata 註解中的元數據
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//獲取@EnableDefineService註解的元信息也就是 exclude = {LoggerService.class}
Map<String,Object> attributes= annotationMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
//可以返回一些信息,暫時寫死
System.out.println(attributes.get("exclude").getClass().getName());
//排除邏輯的判斷,即不加載哪個類,可以將此放入到配置文件中
if(attributes.get("exclude")==null){
//將CacheService託管給spring ioc
return new String[] {CacheService.class.getName()};
}
else{
//將CacheService和AttachService託管給spring ioc
return new String[] {CacheService.class.getName(),AttachService.class.getName()};
}
//將擴展點放入到配置文件中,SP擴展點相當於從所有resource/MATE-INF/spring.fatories配置文件中,
//實現這個要依賴類似於SpringFactoriesLoader 的使用
// return new String[] {"org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration",
// "org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration",
// "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"};
//總而言之,只能根據元數據,或者condition 做過條件濾處理,返回一個需要加載的 類的字符串數組,由spring來加載
}
}
3.自定義配置類 LoggerDefinetionRegister 擴展點
public class LoggerDefinetionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class beanClass =LoggerService.class;
RootBeanDefinition beanDefinition =new RootBeanDefinition(beanClass);//封裝成BeanDefinition
//相當於<bean name=“XX”
String name= StringUtils.uncapitalize(beanClass.getSimpleName());//首字母小寫
//可以自定義bean name 與CacheImportSelector 不同name,而且直接就能裝配了,而CacheImportSelector 只能 過濾,返回字符串數組,由spring裝配
registry.registerBeanDefinition(name,beanDefinition);
}
}
4.自定義註解中,導入CacheImportSelector.class,LoggerDefinetionRegister.class
前邊說過@import
2. 實現 ImportSelector 接口進行動態注入
3. 實現 ImportBeanDefinitionRegistrar 接口進行動態注入
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CacheImportSelector.class,LoggerDefinetionRegister.class}) //導入的不是Configuration,而是ImportSelector和ImportBeanDefinitionRegistrar
public @interface EnableDefineService {
//配置一些方法
Class<?>[] exclude() default {};
}
測試註解,並添加 exclude進行條件裝配,
@SpringBootApplication
@EnableDefineService(exclude = {LoggerService.class})
public class EnableDefineMain {
public static void main(String[] args) {
ConfigurableApplicationContext ca= SpringApplication.run(EnableDefineMain.class,args);
String[] names=ca.getBeanDefinitionNames();
for (String s:names) {
System.out.println(s);
}
}
}
運行結果
com.example.demo.demofour.CacheService
com.example.demo.demofour.AttachService
org.springframework.boot.autoconfigure.AutoConfigurationPackages
loggerService
將類的加載條件放到配置文件中去實戰
請查看 手把手 自定義XXX-spring-boot-starter組件章節
本次學習結束,之前我們已經自定義了starter
下一章是進行自定義多數據庫連接