淺析springboot自動配置原理
夢裡尋他千百度——Springboot自動配置
還記得曾經為了引入一個框架,而在spring的xml檔案裡面寫一大堆的配置或者以註解的形式,定義一大堆的配置類,簡直不要太繁瑣,稍不注意還很容易出錯。終於有一天,springboot出現了,簡直就是天使般的存在,自從用上springboot,腰也不疼了,頭髮掉的也少了。這一切都源於Springboot自動配置的特性。
Springboot遵循“約定優於配置”的原則,使用註解對一些常規的配置項做預設配置,減少或不使用xml配置,讓你的專案快速執行起來。Springboot還為大量的開發常用框架封裝了starter,如今引入框架只要引入一個starter,你就可以使用這個框架,只需少量的配置甚至是不需要任何配置。
找了兩篇文章,對比在spring應用中引入mybatis的程式碼量和複雜度。
ofollow,noindex">Spring整合MyBatis完整示例
springboot(六):如何優雅的使用mybatisSpringboot自動配置原理
舉個可能不是很恰當的例子,SpringBoot的自動配置原理,跟餐廳的機制很類似。以我最近很喜歡的探魚來說,如果將SpringBoot比喻成探魚,把吃飯比做我們的應用,我們來到探魚吃飯的時候(相當於在應用中加入了@SpringBootApplication),服務員會引導我們開始在選單點餐紙上點餐(選單點餐紙是預先定義好的,就相當於spring.factories檔案,預先定義了我們可以使用的自動配置資訊),探魚既可以自行搭配烤魚口味,也可以直接點店家為我們搭配好的口味(springboot也是如此,比如訊息中介軟體,就有好多種口味可以選,比如rabbitmq,kafka,根據業務場景而定),我們在喜歡的菜上進行勾選(相當於在pom檔案中引入所需框架的starter),然後確定下單(啟動springboot應用)。我很喜歡吃花菜,可惜探魚沒有這道輔菜,但是我們可以自己準備然後帶過去啊,烤魚上了就加進去煮,真是騷操作(這就是加入自定義的自動配置了,這一步比較麻煩,需要自行封裝starter)。
這麼好用的東西,到底springboot是怎麼實現的,一直十分好奇,今天就跟著原始碼一步一步的來了解一下。
@SpringBootApplication public class MyApplication { public static void main(String[] args) {SpringApplication.run(MyApplication .class, args);} }
我們看到,MyApplication作為入口類,入口類中有一個main方法,這個方法其實就是一個標準的Java應用的入口方法,一般在main方法中使用SpringApplication.run()來啟動整個應用。值得注意的是,這個入口類要使用@SpringBootApplication註解宣告,它是SpringBoot的核心註解。
@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 { // 略 }
這個註解裡面,最主要的就是@EnableAutoConfiguration,這麼直白的名字,一看就知道它要開啟自動配置,SpringBoot要開始騷了,於是默默進去@EnableAutoConfiguration的原始碼。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { // 略 }
可以看到,在@EnableAutoConfiguration註解內使用到了@import註解來完成匯入配置的功能,而EnableAutoConfigurationImportSelector內部則是使用了SpringFactoriesLoader.loadFactoryNames方法進行掃描具有META-INF/spring.factories檔案的jar包。下面是1.5.8.RELEASE實現原始碼:
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); //掃描具有META-INF/spring.factories檔案的jar包 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); //去重 configurations = removeDuplicates(configurations); //排序 configurations = sort(configurations, autoConfigurationMetadata); //刪除需要排除的類 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return configurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); } }
//主要實現 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
任何一個springboot應用,都會引入spring-boot-autoconfigure,而spring.factories檔案就在該包下面。spring.factories檔案是Key=Value形式,多個Value時使用,隔開,該檔案中定義了關於初始化,監聽器等資訊,而真正使自動配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:
# Auto Configure 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.cloud.CloudAutoConfiguration,\ ....... org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
上面的EnableAutoConfiguration配置了多個類,這些都是Spring Boot中的自動配置相關類;在啟動過程中會解析對應類配置資訊。每個Configuation類都定義了相關bean的例項化配置。都說明了哪些bean可以被自動配置,什麼條件下可以自動配置,並把這些bean例項化出來。如果我們自定義了一個starter的話,也要在該starter的jar包中提供 spring.factories檔案,並且為其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration對應的配置類。所有框架的自動配置流程基本都是一樣的,判斷是否引入框架,獲取配置引數,根據配置引數初始化框架相應元件。下面的初始化資料來源的部分原始碼:
@Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) public class DataSourceAutoConfiguration { private static final Log logger = LogFactory .getLog(DataSourceAutoConfiguration.class); @Bean @ConditionalOnMissingBean public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties, ApplicationContext applicationContext) { return new DataSourceInitializer(properties, applicationContext); } //略 }
我們可以看到自動化配置程式碼中有很多我們之前沒有用到的註解配置,下面一一介紹。
@Configuration:這個配置就不用多做解釋了,我們一直在使用
@EnableConfigurationProperties:這是一個開啟使用配置引數的註解,value值就是我們配置實體引數對映的ClassType,將配置實體作為配置來源。
以下為SpringBoot內建條件註解:
@ConditionalOnBean:當SpringIoc容器記憶體在指定Bean的條件
@ConditionalOnClass:當SpringIoc容器記憶體在指定Class的條件
@ConditionalOnExpression:基於SpEL表示式作為判斷條件
@ConditionalOnJava:基於JVM版本作為判斷條件
@ConditionalOnJndi:在JNDI存在時查詢指定的位置
@ConditionalOnMissingBean:當SpringIoc容器內不存在指定Bean的條件
@ConditionalOnMissingClass:當SpringIoc容器內不存在指定Class的條件
@ConditionalOnNotWebApplication:當前專案不是Web專案的條件
@ConditionalOnProperty:指定的屬性是否有指定的值
@ConditionalOnResource:類路徑是否有指定的值
@ConditionalOnSingleCandidate:當指定Bean在SpringIoc容器內只有一個,或者雖然有多個但是指定首選的Bean
@ConditionalOnWebApplication:當前專案是Web專案的條件
以上註解都是元註解@Conditional演變而來的,根據不用的條件對應建立以上的具體條件註解。
總結
Springboot的設計思想以及理念都非常前衛,多理解多學習,完全可以在工作中運用起來,Springboot真是個好東西。