SpringBoot入門(四)——自動配置
本文來自網易雲社群
SpringBoot之所以能夠快速構建專案,得益於它的2個新特性,一個是起步依賴前面已經介紹過,另外一個則是自動配置。起步依賴用於降低專案依賴的複雜度,自動配置負責減少人工配置的工作量。
@EnableAutoConfiguration
前一篇留了一個註解沒介紹,@EnableAutoConfiguration註解是開啟自動配置的入口。其定義如下:
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { ... }
看到@Import註解,引數是AutoConfigurationImportSelector類。
先來補充下@Import的知識,最早是為了方便引入java配置類,後來進行了擴充套件,目前有一下功能:
引數為@Configuration註解的bean,那麼就引入這個配置類。相當於用xml配置時的
引數為ImportBeanDefinitionRegistrar介面或者ImportSelector介面的實現類,那麼就通過介面方法來實現自定義注入
引數為普通類,直接將該類建立到Spring的IoC容器
這裡的AutoConfigurationImportSelector類屬於第二種情況,實現了ImportSelector介面。ImportSelector介面只宣告瞭一個方法,要求返回一個包含類全限定名的String陣列,這些類將會被Spring新增到IoC容器。
看下AutoConfigurationImportSelector的方法實現:
@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } try { AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); // 獲取候選配置的類路徑 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 StringUtils.toStringArray(configurations); } catch (IOException ex) { throw new IllegalStateException(ex); } }
通過某種方法獲取配置類的路徑,然後去重排序一系列處理之後返回出去交給Spring去處理。
接下來看下某種方法是個什麼操作:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 從SpringFactoriesLoader取候選配置的類路徑 // 第一個引數getSpringFactoriesLoaderFactoryClass()得到的是EnableAutoConfiguration.class 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; }
呼叫了SpringFactoriesLoader.loadFactoryNames():
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // "org.springframework.boot.autoconfigure.EnableAutoConfiguration" String factoryClassName = factoryClass.getName(); // 先調了下面的loadSpringFactories方法,得到一個Map // 從Map中用EnableAutoConfiguration類的全限定名取一個String列表(其實也是一些類的全限定名列表) return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } // 上面方法先從這裡取了個Map private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 這裡有個快取,記一下,後面會提到 MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) return result; try { // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" // 載入所有META-INF包下的spring.factories檔案 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { // 逗號分隔的String轉為List List<String> factoryClassNames = Arrays.asList( StringUtils.commaDelimitedListToStringArray((String) entry.getValue())); result.addAll((String) entry.getKey(), factoryClassNames); } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
整個流程其實就是讀取專案中的META-INF/spring.factories檔案,然後挑一挑交給Spring去加到上下文中。
那麼META-INF/spring.factories到底是個什麼gui呢。以之前建立的Hello World Web為例,工程中一共有2個META-INF/spring.factories檔案,一個在spring-boot包中,一個在spring-boot-autoconfigure包中。
看下spring-boot-autoconfigure包中的內容(片段):
# Initializersorg.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listenersorg.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listenersorg.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filtersorg.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnClassCondition# Auto Configureorg.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.context.ConfigurationPropertiesAutoConfiguration,\ ...
看到一堆的xxxConfiguration,沒錯,這些都註解了@Configuration。spring-boot-autoconfigure為我們引入了一大堆的java配置類作為元件的預設配置。
包括常見元件的預設配置,有rabbit相關的,有redis相關的,有Security相關的等等。
那麼問題來了,難道所有的配置類都會被載入嗎,答案是顯而易見的。那麼來看看是如何實現的,以Redis的配置類RedisAutoConfiguration為例,看下類原始碼:
@Configuration// 當存在RedisOperations時,才會載入該配置類@ConditionalOnClass(RedisOperations.class)@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration { @Bean // 當不存在名為"redisTemplate"的bean時,才會建立該bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean // 當Spring上下文中不存在StringRedisTemplate類例項的時候,才會建立該bean @ConditionalOnMissingBean(StringRedisTemplate.class) public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
這裡利用了Spring條件註解的特性,通過設定一定的條件來實現不同場景下載入不同的配置。
自動配置類生效的條件通常是我們引入了相關的元件,如果沒有引入元件,那麼就算包含在spring.factories檔案中也不會被載入。
而是否要注入Bean則要看當前上下文中是否已經存在相應的Bean。如果不存在,那麼由預設配置來補充。如果已經存在了,自動配置會不滿足註解條件,就不會被建立。
有了這兩點,可以做到當我們不做任何配置的時候可以用預設配置來運用新元件,而當我們需要對配置進行調整的時候用自定義的配置來覆蓋即可。
Tips
上面原始碼中標記了一個快取。在讀取META-INF/spring.factories檔案後相關資料是會儲存到SpringFactoriesLoader類的快取中的。而這裡第一次讀取META-INF/spring.factories的時機並不是在自動配置這裡,早在上一篇的SpringApplication構造方法中就已經快取了:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = deduceWebApplicationType(); // 設定初始化器,這裡就已經讀取了META-INF/spring.factories setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
小結
這一節通過@EnableAutoConfiguration註解分析了SpringBoot自動配置的實現。通過@Import註解新增自動配置選擇器(AutoConfigurationImportSelector),選擇器中首先讀取META-INF路徑下的spring.factories檔案。在spring.factories檔案中,SpringBoot官方提供了許多常見元件的預設配置,以java配置類形式存在。在這些java配置類中又利用了Spring的條件註解,讓我們可以在預設配置和自定義配置之間靈活切換。
可以這麼認為:SpringBoot在Spring原有的基礎上,通過拼湊組合又實現了一個強大的特性——自動配置。
自動配置讓我們可以在不做任何配置的情況下直接使用一個新的類庫(前提是足夠普遍),也能滿足我們自定義配置的需求。除此之外,我們還可以利用這個思路,實現具有團隊特色的自動配置,讓團隊開發也更加高效。
網易雲新使用者大禮包:https://www.163yun.com/gift
本文來自網易實踐者社群,經作者金港生授權釋出。
相關文章
- SpringBoot | 自動配置原理Spring Boot
- Springboot MVC 自動配置Spring BootMVC
- SpringBoot的自動配置Spring Boot
- SpringBoot(03)——自動配置Spring Boot
- SpringBoot(二)自動配置Spring Boot
- SpringBoot自動配置原理Spring Boot
- springboot 自動配置原理Spring Boot
- springboot_自動配置原理Spring Boot
- SpringBoot的自動配置原理Spring Boot
- springboot 自動配置案例分析Spring Boot
- SpringBoot--自動載入Spring Boot
- SpringBoot自動配置原理解析Spring Boot
- SpringBoot原始碼解析-Bean的載入與自動化配置Spring Boot原始碼Bean
- springboot自動配置原理和啟動流程Spring Boot
- Android自動化測試入門(四)單元測試Android
- SpringBoot | 4.1 SpringMVC的自動配置Spring BootSpringMVC
- SpringBoot(04)——建立自己的自動配置Spring Boot
- composer自動載入配置
- springboot 配置熱啟動 不需重啟自動部署Spring Boot
- 讓SpringBoot自動化配置不再神祕Spring Boot
- 如何實現自己的SpringBoot自動配置Spring Boot
- SpringBoot整合Nacos自動重新整理配置Spring Boot
- SpringBoot-04-自動配置原理再理解Spring Boot
- webpack自動化架構入門Web架構
- SpringBoot入門Spring Boot
- 圖解原始碼 | SpringBoot中自動配置原理圖解原始碼Spring Boot
- SpringBoot自動配置原理原始碼級別分析Spring Boot原始碼
- SpringBoot——自定義自動配置與起步依賴Spring Boot
- SpringBoot2.x入門教程:理解配置檔案Spring Boot
- RobotFramework + Python 自動化入門 四 (Web進階)FrameworkPythonWeb
- 【自動化測試入門】自動化測試思維
- 遊戲製作詳解自----OpenGL入門教程(四)(轉)遊戲
- Jest前端自動化測試入門前端
- ansible自動化運維入門運維
- 新人如何入門自動化-心得篇
- 笑死,面試官又問我SpringBoot自動配置原理面試Spring Boot
- Springboot快速入門Spring Boot
- Springboot入門教程Spring Boot