SpringBoot自動配置原理

springboot的自動配置流程:

主程序啟動會掃描@SpringBootApplication
@SpringBootApplication
public class HelloWorldMainApplication {

    public static void main(String[] args) {

        SpringApplication.run(HelloWorldMainApplication.class,args);
    }
}
@SpringBootApplication元信息

起到主要作用的是@SpringBootConfiguration和@EnableAutoConfiguration

@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 {

@SpringBootConfiguration:
其實就是@Configuration污它,標注當前類為主配置類(主容器)衫贬,當前類指的HelloWorldMainApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@EnableAutoConfiguration:
包含兩個重要注解,@AutoConfigurationPackage和@Import({EnableAutoConfigurationImportSelector.class})

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

@AutoConfigurationPackage的作用:相當于包掃描器缴守,掃描主配置類所在包下的所有自定義的bean,例如@Controller摊聋、@Service等注解標識的bean麻裁。
它的內部是通過@Import({Registrar.class})煎源,利用ImportBeanDefinitionRegistrar的registerBeanDefinitions方法手销,向BeanDefinitionRegistry注冊主配置類所在包下自定義的邏輯組件

@Import({EnableAutoConfigurationImportSelector.class})的作用:通過ImportSelector選擇器執(zhí)行selectImports方法獲得最終啟用(符合當前場景)的自動配置類的全類名的String數(shù)組锋拖,主配置類就會把這些自動配置類的bean加載進spring容器兽埃,@Configuration其實也是spring容器的組件柄错,內部@Component修飾

selectImports方法內部:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
       if (!this.isEnabled(annotationMetadata)) {
           return NO_IMPORTS;
       } else {
           try {
               AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
               AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
               List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
               configurations = this.removeDuplicates(configurations);
               configurations = this.sort(configurations, autoConfigurationMetadata);
               Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
               this.checkExcludedClasses(configurations, exclusions);
               configurations.removeAll(exclusions);
               configurations = this.filter(configurations, autoConfigurationMetadata);
               this.fireAutoConfigurationImportEvents(configurations, exclusions);
               return (String[])configurations.toArray(new String[configurations.size()]);
           } catch (IOException var6) {
               throw new IllegalStateException(var6);
           }
       }
   } 

自動配置的核心就在于selectImports方法,下面一步步的剖析這個方法

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
    }
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jms.artemis.ArtemisXAConnectionFactoryConfiguration=
    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();

            while(urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
            }

            return loadMetadata(properties);
        } catch (IOException var4) {
            throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
        }
    }

loadMetadata方法內部遍歷應用下的所有jar,加載了spring-boot-autoconfigure-1.5.9.RELEASE.jar下的META-INF/spring-autoconfigure-metadata.properties恒削,配置文件里面的信息為自動配置類xxxAutoConfiguration的@ConditionalOnClass蔓同、@AutoConfigureAfter蹲诀、@AutoConfigureOrder等注解參數(shù)的key-value信息则北,因為自動配置類是否啟用要看符不符合這些條件痕慢,如果啟用掖举,還要對這些自動配置類的加載順序進行排序方篮,返回的autoConfigurationMetadata 封裝了這些信息的Properties励负,Properties把spring-autoconfigure-metadata.properties封裝了

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

參數(shù)annotationMetadata為主啟動類的信息(以及它的注解及元注解信息)巾表,返回的attributes為@EnableAutoConfiguration注解的exclude和excludeName的值

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

只截取一部分,org.springframework.boot.autoconfigure.EnableAutoConfiguration為key

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.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\

getCandidateConfigurations方法返回所有自動配置類的全類名集合(從META-INF/spring.factories原封不動地拿),參數(shù)getSpringFactoriesLoaderFactoryClass()其實就EnableAutoConfiguration.class類對象偶妖,loadFactoryNames方法內部會讀取該應用所有jar包下的META-INF/spring.factories趾访,(其實只有spring-boot-autoconfigure-1.5.9.RELEASE.jar包下有)取出factories文件中key為EnableAutoConfiguration全類名的值的集合扼鞋,裝進集合configurations 返回

configurations = this.removeDuplicates(configurations);

返回原集合云头,不必理會

configurations = this.sort(configurations, autoConfigurationMetadata);

對集合排序溃槐,根據(jù)autoConfigurationMetadata里面的AutoConfigureOrder猴鲫、AutoConfigureAfter谣殊、AutoConfigureBefore等信息對自動配置類集合configurations排序拂共,自動配置類加載是有順序的,
例如WebMvcAutoConfiguration姻几,就必須等到DispatcherServletAutoConfiguration加載成功之后再加載宜狐,而且還要根據(jù)@Order的派生注解@AutoConfigureOrder決定加載優(yōu)先級

@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);

處理需要排除的自動配置類,不必理會

configurations = this.filter(configurations, autoConfigurationMetadata);

根據(jù)@ConditionalOnClass(類.Class)蛇捌、@ConditionalOnMissingBean(類.Class)等注解肌厨,判斷注解中的類.Class是否存在或不存在,從而篩選出符合啟用的自動配置類豁陆。
例如我們的應用沒有導入redis場景的starter柑爸,而RedisAutoConfiguration里

@Configuration
@ConditionalOnClass({JedisConnection.class, RedisOperations.class, Jedis.class})
@EnableConfigurationProperties({RedisProperties.class})
public class RedisAutoConfiguration {

如果我們應用里沒有這三個JedisConnection.class, RedisOperations.class, Jedis.class,那么RedisAutoConfiguration就不會生效盒音,filter方法會把它過濾掉譬圣,最終返回的configurations就是經(jīng)過過濾后的维哈、符合我們當前應用場景的自動配置類的String數(shù)組飘庄。

這些自動配置類在被@Import進主容器之后碾盐,spring創(chuàng)建這些自動配置類的bean,這些bean各自完成初始化自己內部組件的工作
至此,springboot自動配置類的默認初始化完成

springboot修改默認配置:

springboot為我們自動配置了各個場景下的所有默認配置,那它是如何設置的默認配置呢,再者如果我們需要修改默認配置,應該從哪入手饱溢?

首先要明確一點肋杖,每個自動配置類的主要功能就是給容器中加入所需的組件

以HttpEncodingAutoConfiguration為例

例如HttpEncodingAutoConfiguration類給容器中加入一個解決編碼的CharacterEncodingFilter組件
HttpEncodingAutoConfiguration類信息如下:

@Configuration //表示這是一個配置類津畸,以前編寫的配置文件一樣,也可以給容器中添加組件
@EnableConfigurationProperties(HttpEncodingProperties.class) //啟動指定類的 ConfigurationProperties功能;將配置文件中對應的值和HttpEncodingProperties綁定起來;并把 HttpEncodingProperties加入到ioc容器中,以便自動配置類以有參構造器的方式注入Properties
@ConditionalOnWebApplication //Spring底層@Conditional注解(Spring注解版)纸巷,根據(jù)不同的條件,如果 滿足指定的條件,整個配置類里面的配置就會生效; 判斷當前應用是否是web應用,如果是,當前配置類生效
@ConditionalOnClass(CharacterEncodingFilter.class) //判斷當前項目有沒有這個類,自動配置類是否啟動要看這個注解; CharacterEncodingFilter;SpringMVC中進行亂碼解決的過濾器;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) //判斷配置文件中是否存在某個配置 spring.http.encoding.enabled;如果不存在,判斷也是成立的 //即使我們配置文件中不配置pring.http.encoding.enabled=true,也是默認生效的;
public class HttpEncodingAutoConfiguration {

private final HttpEncodingProperties properties;

public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
    this.properties = properties;
    }
@Bean //給容器中添加一個組件,這個組件的某些屬性值需要從properties中獲取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判斷容器有沒有這個組件濒析? 沒有才會生效
public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
         filter.setEncoding(this.properties.getCharset().name());
         filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
         filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
         return filter;
    }
}

我們只需要指定HttpEncodingProperties類中的charset屬性的值即可盾致,而HttpEncodingProperties通過@ConfigurationProperties(prefix = "")又和配置文件綁定了护赊,
這里說的配置文件是我們應用的配置文件判耕,因為spring-boot-autoconfigure-1.5.9.RELEASE.jar沒有配置文件请毛,會掃描我們應用的配置文件仙蚜,
只要對應上prefix前綴和HttpEncodingProperties類的屬性名就行了

HttpEncodingProperties類如下:
可以看出charset的默認值是UTF-8

@ConfigurationProperties(prefix = "spring.http.encoding") //從配置文件(我們自己應用的配置文件)中獲取指定的值和bean的屬性進行綁定
public class HttpEncodingProperties {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Charset charset;
    private Boolean force;
    private Boolean forceRequest;
    private Boolean forceResponse;
    private Map<Locale, Charset> mapping;
    public HttpEncodingProperties() {
            this.charset = DEFAULT_CHARSET;
        }
    set/get方法......
}

我們自己在application.properties中自定義配置

spring.http.encoding.enabled=true
spring.http.encoding.charset=gbk
spring.http.encoding.force=true
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斤程,隨后出現(xiàn)的幾起案子亿柑,更是在濱河造成了極大的恐慌役耕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闸度,死亡現(xiàn)場離奇詭異,居然都是意外死亡青柄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門糜芳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峭竣,“玉大人扛吞,你說我怎么就攤上這事盲泛。” “怎么了纺阔?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵中贝,是天一觀的道長囤捻。 經(jīng)常有香客問我,道長邻寿,這世上最難降的妖魔是什么蝎土? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮绣否,結果婚禮上誊涯,老公的妹妹穿的比我還像新娘。我一直安慰自己蒜撮,他們只是感情好暴构,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淀弹,像睡著了一般丹壕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上薇溃,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天菌赖,我揣著相機與錄音,去河邊找鬼沐序。 笑死琉用,一個胖子當著我的面吹牛堕绩,可吹牛的內容都是我干的。 我是一名探鬼主播邑时,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼奴紧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晶丘?” 一聲冷哼從身側響起黍氮,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浅浮,沒想到半個月后沫浆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡滚秩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年专执,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郁油。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡本股,死狀恐怖,靈堂內的尸體忽然破棺而出桐腌,到底是詐尸還是另有隱情拄显,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布哩掺,位于F島的核電站凿叠,受9級特大地震影響涩笤,放射性物質發(fā)生泄漏嚼吞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一蹬碧、第九天 我趴在偏房一處隱蔽的房頂上張望舱禽。 院中可真熱鬧,春花似錦恩沽、人聲如沸誊稚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽里伯。三九已至,卻和暖如春渤闷,著一層夾襖步出監(jiān)牢的瞬間疾瓮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工飒箭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狼电,地道東北人蜒灰。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像肩碟,于是被迫代替她去往敵國和親强窖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內容