SpringBoot自動配置原理

   在微服務概念興起的今天,不少公司轉型使用微服務做爲架構。在技術選型上Spring Cloud 是很是好的選擇,它提供了一站式的分佈式系統解決方案,而Spring Cloud中的每一個組件都是基於Spring Boot構建的,Spring Boot提供J2EE一站式解決方案,具備如下優勢:java

  • 快速建立獨立運行的Spring項目以及與主流框架集成
  • 使用嵌入式的Servlet容器,應用無需打成WAR包
  • starters自動依賴與版本控制
  • 大量的自動配置,簡化開發,也可修改默認值
  • 無需配置XML,無代碼生成,開箱即用
  • 準生產環境的運行時應用監控
  • 與雲計算的自然集成

1、Spring Boot的核心組件模塊

模塊 java文件數
spring-boot 551
spring-boot-actuator 423
spring-boot-autoconfigure 783
spring-boot-devtools 169
spring-boot-cli 180
spring-boot-tools 355

從上面的java文件數量大體能夠看出,SpringBoot技術框架的核心組成部分:
spring-boot-autoconfigure
spring-boot
spring-boot-tools

咱們把SpringBoot源碼導入IntelliJ IDEA,查看artifact的所有依賴關係。
IDEA有個Maven Projects窗口,通常在右側可以找到,若是沒有能夠從菜單欄打開:View>Tool Windows>Maven Projects;
選擇要分析的maven module(idea的module至關於eclipse的project),右擊show dependencies,會出來該module的所有依賴關係圖,很是清晰細緻。
例如,spring-boot-starter-freemarker的依賴圖分析以下:react

在spring-boot-build 的pom中,咱們能夠看到:git

<modules>
    <module>spring-boot-dependencies</module>
    <module>spring-boot-parent</module>
    <module>spring-boot-tools</module>
    <module>spring-boot</module>
    <module>spring-boot-test</module>
    <module>spring-boot-autoconfigure</module>
    <module>spring-boot-test-autoconfigure</module>
    <module>spring-boot-actuator</module>
    <module>spring-boot-devtools</module>
    <module>spring-boot-docs</module>
    <module>spring-boot-starters</module>
    <module>spring-boot-actuator-docs</module>
    <module>spring-boot-cli</module>
</modules>

其中,在spring-boot-dependencies中,SpringBoot項目維護了一份龐大依賴。這些依賴的版本都是通過實踐,測試經過,不會發生依賴衝突的。就這樣一個事情,就大大減小了Spring開發過程當中,出現jar包衝突的機率。spring-boot-parent依賴spring-boot-dependencies。github

下面咱們簡要介紹一下SpringBoot子modules。web

  • spring-boot:SpringBoot核心工程。
  • spring-boot-starters:是SpringBoot的啓動服務工程。
  • spring-boot-autoconfigure:是SpringBoot實現自動配置的核心工程。
  • spring-boot-actuator:提供SpringBoot應用的外圍支撐性功能。

  好比:redis

  1. Endpoints,SpringBoot應用狀態監控管理
  2. HealthIndicator,SpringBoot應用健康指示表
  3. 提供metrics支持
  4. 提供遠程shell支持
  • spring-boot-tools:提供了SpringBoot開發者的經常使用工具集。諸如,spring-boot-gradle-plugin,spring-boot-maven-plugin就是這個工程裏面的。
  • spring-boot-cli:是Spring Boot命令行交互工具,可用於使用Spring進行快速原型搭建。你能夠用它直接運行Groovy腳本。若是你不喜歡Maven或Gradle,Spring提供了CLI(Command Line Interface)來開發運行Spring應用程序。你可使用它來運行Groovy腳本,甚至編寫自定義命令。

2、Spring Boot Starters

  Spring boot中的starter概念是很是重要的機制,可以拋棄之前繁雜的配置,統一集成進starter,應用者只須要引入starter jar包,spring boot就能自動掃描到要加載的信息。
  starter讓咱們擺脫了各類依賴庫的處理,須要配置各類信息的困擾。Spring Boot會自動經過classpath路徑下的類發現須要的Bean,並織入bean。
  例如,若是你想使用Spring和用JPA訪問數據庫,你只要依賴 spring-boot-starter-data-jpa 便可。
  目前,github上spring-boot項目的最新的starter列表spring-boot/spring-boot-starters以下:spring

spring-boot-starter
spring-boot-starter-activemq
spring-boot-starter-actuator
spring-boot-starter-amqp
spring-boot-starter-aop
spring-boot-starter-artemis
spring-boot-starter-batch
spring-boot-starter-cache
spring-boot-starter-cloud-connectors
spring-boot-starter-data-cassandra
spring-boot-starter-data-couchbase
spring-boot-starter-data-elasticsearch
spring-boot-starter-data-jpa
spring-boot-starter-data-ldap
spring-boot-starter-data-mongodb
spring-boot-starter-data-mongodb-reactive
spring-boot-starter-data-neo4j
spring-boot-starter-data-redis
spring-boot-starter-data-rest
spring-boot-starter-data-solr
spring-boot-starter-freemarker
spring-boot-starter-groovy-templates
spring-boot-starter-hateoas
spring-boot-starter-integration
spring-boot-starter-jdbc
spring-boot-starter-jersey
spring-boot-starter-jetty
spring-boot-starter-jooq
spring-boot-starter-jta-atomikos
spring-boot-starter-jta-bitronix
spring-boot-starter-jta-narayana
spring-boot-starter-log4j2
spring-boot-starter-logging
spring-boot-starter-mail
spring-boot-starter-mobile
spring-boot-starter-mustache
spring-boot-starter-parent
spring-boot-starter-reactor-netty
spring-boot-starter-security
spring-boot-starter-social-facebook
spring-boot-starter-social-linkedin
spring-boot-starter-social-twitter
spring-boot-starter-test
spring-boot-starter-thymeleaf
spring-boot-starter-tomcat
spring-boot-starter-undertow
spring-boot-starter-validation
spring-boot-starter-web
spring-boot-starter-web-services
spring-boot-starter-webflux
spring-boot-starter-websocket

這是Spring Boot的核心啓動器,包含了自動配置、日誌和YAML。它的項目依賴圖以下:mongodb

 

  能夠看出,這些starter只是配置,真正作自動化配置的代碼的是在spring-boot-autoconfigure裏面。同時spring-boot-autoconfigure依賴spring-boot工程,這個spring-boot工程是SpringBoot的核心。
  SpringBoot會基於你的classpath中的jar包,試圖猜想和配置您可能須要的bean。
  例如,若是你的classpath中有tomcat-embedded.jar,你可能會想要一個TomcatEmbeddedServletContainerFactory Bean (SpringBoot經過獲取EmbeddedServletContainerFactory來啓動對應的web服務器。經常使用的兩個實現類是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。
  其餘的全部基於Spring Boot的starter都依賴這個spring-boot-starter。好比說spring-boot-starter-actuator的依賴樹,以下圖:shell

 

3、SpringBoot 自動配置原理

SpringBoot 自動配置主要經過 @EnableAutoConfiguration, @Conditional, @EnableConfigurationProperties 或者 @ConfigurationProperties 等幾個註解來進行自動配置完成的。
@EnableAutoConfiguration 開啓自動配置,主要做用就是調用 Spring-Core 包裏的 loadFactoryNames(),將 autoconfig 包裏的已經寫好的自動配置加載進來。
@Conditional 條件註解,經過判斷類路徑下有沒有相應配置的 jar 包來肯定是否加載和自動配置這個類。
@EnableConfigurationProperties的做用就是,給自動配置提供具體的配置參數,只須要寫在application.properties 中,就能夠經過映射寫入配置類的 POJO 屬性中。數據庫

一、SpringBoot啓動主程序類

@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

每次咱們直接直接啓動這個啓動類,SpringBoot就啓動成功了,而且幫咱們配置了好多自動配置類。其中最重要是 @SpringBootApplication 這個註解,咱們點進去看一下。

二、@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聲明時,引用了三個重要的註解:

  • @SpringBootConfiguration : SpringBoot的配置類,標註在某個類上,表示這是一個Spring Boot的配置類
  • @EnableAutoConfiguration: 開啓自動配置類,SpringBoot的精華所在。
  • @ComponentScan包掃描

之前咱們須要配置的東西,Spring Boot幫咱們自動配置;@EnableAutoConfiguration告訴SpringBoot開啓自動配置功能;這樣自動配置才能生效;

  • @EnableAutoConfiguration註解

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}

@EnableAutoConfiguration聲明時,引用了兩個重要的註解:

  • @AutoConfigurationPackage:自動配置包
  • @Import: 導入自動配置的組件
  • @AutoConfigurationPackage註解
     static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) { register(registry, new PackageImport(metadata).getPackageName()); }

它實際上是註冊了一個Bean的定義。
new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的同級以及子級的包組件。

以上圖爲例,DemoApplication是和demo包同級,可是demo2這個類是DemoApplication的父級,和example包同級
也就是說,DemoApplication啓動加載的Bean中,並不會加載demo2,這也就是爲何,咱們要把DemoApplication放在項目的最高級中。

  • @Import(AutoConfigurationImportSelector.class)註解

能夠從圖中看出AutoConfigurationImportSelector 繼承了 DeferredImportSelector繼承了ImportSelector,ImportSelector有一個方法爲:selectImports。

  @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }

能夠看到第九行,它實際上是去加載public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。這個外部文件,有不少自動配置的

類。以下:

三、如何自定義本身的Bean

咱們以RedisTemplate爲例:

@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @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 @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }

咱們每次在Spring中使用Redis,都會使用到RedisTemplate這個工具類,可是他默認給咱們返回的這個工具類,可能不是很符合咱們的要求。好比:咱們想要開啓事務,或者想要改變它默認的序列化。

  • 這時候該如何去作呢?

根據前面的分析,只要咱們在容器中放入一個RedisTemplate Bean便可。

 1     @Bean("redisTemplate")  2     public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {  3         RedisTemplate<Object, Object> template = new RedisTemplate<>();  4  template.setConnectionFactory(redisConnectionFactory);  5         // 修改序列化爲Jackson
 6         template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());  7         // 開啓事務
 8         template.setEnableTransactionSupport(true);  9         return template; 10     }

咱們本身定義咱們的RedisTemplate模板,修改序列化,開啓事務等操做。
咱們將咱們本身的Bean加入到IoC容器中之後,他就會默認的覆蓋掉原來的RedisTemplate,達到定製的效果。
咱們在以Kafka爲例:

假設咱們想要消費的對象不是字符串,而是一個對象呢?好比Person對象,或者其餘Object類呢?

  1. 咱們首先去查找KafkaAutoConfiguration(xxxAutoConfiguration),看看是否有關於Serializer屬性的配置
  2. 假設沒有咱們就去KafkaProperties文件查找是否有Serializer的配置

而後直接在application.properties修改默認序列化就好,連Bean都不須要本身重寫。
相似這種,可使用Spring提供的Json序列化,也能夠自動使用第三方框架提供的序列化,好比Avro, Protobuff等

spring.kafka.producer.key-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer spring.kafka.consumer.key-deserializer=com.example.common.MyJson spring.kafka.consumer.value-deserializer=com.example.common.MyJson

 

  • HttpEncodingAutoConfiguration

在這麼多xxxxAutoConfiguration中,咱們以HttpEncodingAutoConfiguration(Http自動編碼)爲例

 

 

 1 @Configuration    
 2 //表示這是一個配置類,之前編寫的配置文件同樣,也能夠給容器中添加組件
 3 
 4 @EnableConfigurationProperties(HttpEncodingProperties.class)
 5 //啓動指定類的ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;並把HttpEncodingProperties加入到ioc容器中
 6 
 7 @ConditionalOnWebApplication
 8 //Spring底層@Conditional註解(Spring註解版),根據不一樣的條件,若是知足指定的條件,整個配置類裏面的配置就會生效;這裏是判斷當前應用是不是web應用,若是是,當前配置類生效
 9 
10 @ConditionalOnClass(CharacterEncodingFilter.class)
11 //判斷當前項目有沒有這個類CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器;
12 
13 @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
14 //判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;若是不存在,判斷也是成立的
15 //即便咱們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的;
16 
17 public class HttpEncodingAutoConfiguration {
18 
19     //他已經和SpringBoot的配置文件映射了
20     private final HttpEncodingProperties properties;
21 
22     //只有一個有參構造器的狀況下,參數的值就會從容器中拿
23     public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
24         this.properties = properties;
25     }
26 
27     @Bean
28     //給容器中添加一個組件,這個組件的某些值須要從properties中獲取
29     @ConditionalOnMissingBean(CharacterEncodingFilter.class)
30     //判斷容器沒有這個組件
31     public CharacterEncodingFilter characterEncodingFilter() {
32         CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
33         filter.setEncoding(this.properties.getCharset().name());
34         filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
35         filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
36         return filter;
37     }
38     }
39     .......
40 }

 

  經過上面的類的註解能夠看到,經過使用@EnableConfigurationProperties,能夠把配置文件中的屬性與HttpEncodingProperties類綁定起來而且加入到IOC容器中,進入HttpEncodingProperties類,能夠看到他是經過@ConfigurationProperties 註解把配置文件中的spring.http.encoding值與該類的屬性綁定起來的。

@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties 

同時咱們能夠注意到上面的類中使用了@ConditionalOnClass@ConditionalOnWebApplication註解,這兩個都是@Conditional的派生註解,做用是必須是@Conditional指定的條件成立,纔給容器中添加組件,配置裏的內容纔會生效

  • Conditional註解

下面咱們以@ConditionalOnClass爲例,來分析一下他的源代碼。

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

進入OnClassCondition類,查看他的類繼承信息,能夠看到他繼承SpringBootCondition類,SpringBootCondition又實現了Condition接口

OnClassCondition又override了SpringBootCondition的getMatchOutcome方法,該方法會返回條件匹配結果。
getMatchOutcome方法源代碼以下:

public ConditionOutcome getMatchOutcome(ConditionContext context,
         .....
List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
       ......
List<String> present = getMatches(onMissingClasses, MatchType.PRESENT, classLoader);
    }

能夠看到getMatchOutcome中主要調用了getMatches方法
進入getMatches方法

private List<String> getMatches(Collection<String> candidates, MatchType matchType,
            ClassLoader classLoader) {
        List<String> matches = new ArrayList<String>(candidates.size());
        for (String candidate : candidates) {
            if (matchType.matches(candidate, classLoader)) {
                matches.add(candidate);
            }
        }
        return matches;
    }

getMatches又調用了MatchType的matches方法。

 private enum MatchType {

        PRESENT {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return isPresent(className, classLoader);
            }

        },

        MISSING {

            @Override
            public boolean matches(String className, ClassLoader classLoader) {
                return !isPresent(className, classLoader);
            }

        };

        private static boolean isPresent(String className, ClassLoader classLoader) {
            if (classLoader == null) {
                classLoader = ClassUtils.getDefaultClassLoader();
            }
            try {
                forName(className, classLoader);
                return true;
            }
            catch (Throwable ex) {
                return false;
            }
        }

        private static Class<?> forName(String className, ClassLoader classLoader)
                throws ClassNotFoundException {
            if (classLoader != null) {
                return classLoader.loadClass(className);
            }
            return Class.forName(className);
        }

        public abstract boolean matches(String className, ClassLoader classLoader);

    }

進入MatchType類中,能夠看到他有兩個枚舉類,進一步看枚舉類中的matches的源代碼能夠發現最終是利用loadClass以及forName 方法,判斷類路徑下有沒有這個指定的類。
下面列舉出的是Spring Boot對@Conditional的擴展註解。

用好SpringBoot只要把握這幾點:

  • SpringBoot啓動會加載大量的自動配置類
  • 所要作的就是咱們須要的功能SpringBoot有沒有幫咱們寫好的自動配置類:
  • 若是有就再來看這個自動配置類中到底配置了哪些組件,Springboot自動配置類裏邊只要咱們要用的組件有,咱們就不須要再來配置了,可是若是說沒有咱們所須要的組件,那麼咱們就須要本身來寫一個配置類來把咱們相應的組件配置起來。
  • 給容器中自動配置類添加組件的時候,會從properties類中獲取某些屬性,而這些屬性咱們就能夠在配置文件指定這些屬性
相關文章
相關標籤/搜索