第5章 Spring Boot自動(dòng)配置原理

第5章 Spring Boot自動(dòng)配置原理

5.1 SpringBoot的核心組件模塊

首先割按,我們來(lái)簡(jiǎn)單統(tǒng)計(jì)一下SpringBoot核心工程的源碼java文件數(shù)量:

我們cd到spring-boot-autoconfigure工程根目錄下悟狱。執(zhí)行

$ tree | grep -c .java$
模塊 java文件數(shù)
spring-boot 551
spring-boot-actuator 423
spring-boot-autoconfigure 783
spring-boot-devtools 169
spring-boot-cli 180
spring-boot-tools 355

我們可以看到有783個(gè)java文件静浴。spring-boot核心工程有551個(gè)java文件。從上面的java文件數(shù)量大致可以看出挤渐,SpringBoot技術(shù)框架的核心組成部分:

spring-boot-autoconfigure
spring-boot
spring-boot-tools

我們把SpringBoot源碼導(dǎo)入IntelliJ IDEA苹享,查看artifact的全部依賴關(guān)系。

IDEA有個(gè)Maven Projects窗口浴麻,一般在右側(cè)能夠找到得问,如果沒(méi)有可以從菜單欄打開(kāi):View>Tool Windows>Maven Projects;

選擇要分析的maven module(idea的module相當(dāng)于eclipse的project),右擊show dependencies,會(huì)出來(lái)該module的全部依賴關(guān)系圖,非常清晰細(xì)致软免。

例如宫纬,spring-boot-starter-freemarker的依賴圖分析如下:

在spring-boot-build 的pom中,我們可以看到:

           <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項(xiàng)目維護(hù)了一份龐大依賴。這些依賴的版本都是經(jīng)過(guò)實(shí)踐榛泛,測(cè)試通過(guò)蝌蹂,不會(huì)發(fā)生依賴沖突的。就這樣一個(gè)事情挟鸠,就大大減少了Spring開(kāi)發(fā)過(guò)程中叉信,出現(xiàn)jar包沖突的概率。spring-boot-parent依賴spring-boot-dependencies艘希。

下面我們簡(jiǎn)要介紹一下SpringBoot子modules硼身。

spring-boot

SpringBoot核心工程。

spring-boot-starters

是SpringBoot的啟動(dòng)服務(wù)工程覆享。

spring-boot-autoconfigure

是SpringBoot實(shí)現(xiàn)自動(dòng)配置的核心工程佳遂。

spring-boot-actuator

提供SpringBoot應(yīng)用的外圍支撐性功能。 比如:

  • Endpoints撒顿,SpringBoot應(yīng)用狀態(tài)監(jiān)控管理
  • HealthIndicator丑罪,SpringBoot應(yīng)用健康指示表
  • 提供metrics支持
  • 提供遠(yuǎn)程shell支持

spring-boot-tools

提供了SpringBoot開(kāi)發(fā)者的常用工具集。諸如,spring-boot-gradle-plugin吩屹,spring-boot-maven-plugin就是這個(gè)工程里面的跪另。

spring-boot-cli

是Spring Boot命令行交互工具,可用于使用Spring進(jìn)行快速原型搭建煤搜。你可以用它直接運(yùn)行Groovy腳本免绿。如果你不喜歡Maven或Gradle,Spring提供了CLI(Command Line Interface)來(lái)開(kāi)發(fā)運(yùn)行Spring應(yīng)用程序擦盾。你可以使用它來(lái)運(yùn)行Groovy腳本嘲驾,甚至編寫(xiě)自定義命令。

5.2 SpringBoot Starters

Spring boot中的starter概念是非常重要的機(jī)制迹卢,能夠拋棄以前繁雜的配置辽故,統(tǒng)一集成進(jìn)starter,應(yīng)用者只需要引入starter jar包腐碱,spring boot就能自動(dòng)掃描到要加載的信息誊垢。

starter讓我們擺脫了各種依賴庫(kù)的處理,需要配置各種信息的困擾喻杈。Spring Boot會(huì)自動(dòng)通過(guò)classpath路徑下的類發(fā)現(xiàn)需要的Bean彤枢,并織入bean。

例如筒饰,如果你想使用Spring和用JPA訪問(wèn)數(shù)據(jù)庫(kù)缴啡,你只要依賴 spring-boot-starter-data-jpa 即可。

目前瓷们,github上spring-boot項(xiàng)目的最新的starter列表spring-boot/spring-boot-starters如下:

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

(源代碼目錄執(zhí)行shell:l|awk '{print $9}'业栅, l|awk '{print $9}'|grep -c 'starter')

共52個(gè)。每個(gè)starter工程里面的pom描述有相應(yīng)的介紹谬晕。具體的說(shuō)明碘裕,參考官網(wǎng)文檔[1]。關(guān)于這些starters的使用例子攒钳,可以參考spring-boot/spring-boot-samples

比如說(shuō)帮孔,spring-boot-starter是:

Core starter, including auto-configuration support, logging and YAML

這是Spring Boot的核心啟動(dòng)器,包含了自動(dòng)配置不撑、日志和YAML文兢。它的項(xiàng)目依賴圖如下:

可以看出,這些starter只是配置焕檬,真正做自動(dòng)化配置的代碼的是在spring-boot-autoconfigure里面姆坚。同時(shí)spring-boot-autoconfigure依賴spring-boot工程,這個(gè)spring-boot工程是SpringBoot的核心实愚。

SpringBoot會(huì)基于你的classpath中的jar包闷串,試圖猜測(cè)和配置您可能需要的bean。

例如躏吊,如果你的classpath中有tomcat-embedded.jar,你可能會(huì)想要一個(gè)TomcatEmbeddedServletContainerFactory Bean (SpringBoot通過(guò)獲取EmbeddedServletContainerFactory來(lái)啟動(dòng)對(duì)應(yīng)的web服務(wù)器琼梆。常用的兩個(gè)實(shí)現(xiàn)類是TomcatEmbeddedServletContainerFactory和JettyEmbeddedServletContainerFactory)。

其他的所有基于Spring Boot的starter都依賴這個(gè)spring-boot-starter。比如說(shuō)spring-boot-starter-actuator的依賴樹(shù),如下圖:

5.3 @EnableAutoConfiguration自動(dòng)配置原理

通過(guò)@EnableAutoConfiguration啟用Spring應(yīng)用程序上下文的自動(dòng)配置蕉鸳,這個(gè)注解會(huì)導(dǎo)入一個(gè)EnableAutoConfigurationImportSelector的類,而這個(gè)類會(huì)去讀取一個(gè)spring.factories下key為EnableAutoConfiguration對(duì)應(yīng)的全限定名的值。

這個(gè)spring.factories里面配置的那些類忍法,主要作用是告訴Spring Boot這個(gè)stareter所需要加載的那些xxxAutoConfiguration類,也就是你真正的要自動(dòng)注冊(cè)的那些bean或功能榕吼。然后饿序,我們實(shí)現(xiàn)一個(gè)spring.factories指定的類,標(biāo)上@Configuration注解羹蚣,一個(gè)starter就定義完了原探。

如果想從自己的starter種讀取應(yīng)用的starter工程的配置,只需要在入口類上加上如下注解即可:

@EnableConfigurationProperties(MyProperties.class)

讀取spring.factories文件的實(shí)現(xiàn)

是通過(guò)org.springframework.core.io.support.SpringFactoriesLoader實(shí)現(xiàn)顽素。

SpringFactoriesLoader的實(shí)現(xiàn)類似于SPI(Service Provider Interface咽弦,在java.util.ServiceLoader的文檔里有比較詳細(xì)的介紹。java SPI提供一種服務(wù)發(fā)現(xiàn)機(jī)制胁出,為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制型型。有點(diǎn)類似IOC的思想,就是將裝配的控制權(quán)移到程序之外全蝶,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要[3])闹蒜。

SpringFactoriesLoader會(huì)加載classpath下所有JAR文件里面的META-INF/spring.factories文件。

其中加載spring.factories文件的代碼在loadFactoryNames方法里:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

....

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            List<String> result = new ArrayList<>();
            while (urls.hasMoreElements()) {
                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 ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

通過(guò)org.springframework.boot.autoconfigure.AutoConfigurationImportSelector里面的getCandidateConfigurations方法抑淫,獲取到候選類的名字List<String>绷落。該方法代碼如下:

    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;
    }

其中,getSpringFactoriesLoaderFactoryClass()方法直接返回的是EnableAutoConfiguration.class, 代碼如下:

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

所以始苇,getCandidateConfigurations方法里面的這段代碼:

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

會(huì)過(guò)濾出key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名對(duì)應(yīng)的值砌烁。全限定名都使用如下命名方法:

包名.外部類名
包名.外部類名$內(nèi)部類名

e.g:

org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

SpringBoot中的META-INF/spring.factories(完整路徑:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中關(guān)于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.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,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.ReactiveMongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.ReactiveMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAnnotationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

當(dāng)然了,這些AutoConfiguration不是所有都會(huì)加載的催式,會(huì)根據(jù)AutoConfiguration上的@ConditionalOnClass等條件函喉,再進(jìn)一步判斷是否加載。我們下文通過(guò)FreeMarkerAutoConfiguration實(shí)例來(lái)分析整個(gè)自動(dòng)配置的過(guò)程蓄氧。

5.4 FreeMarkerAutoConfiguration自動(dòng)配置的實(shí)例分析

我們首先看spring-boot-starter-freemarker工程函似,目錄結(jié)構(gòu)如下:

.
├── pom.xml
├── spring-boot-starter-freemarker.iml
└── src
    └── main
        └── resources
            └── META-INF
                └── spring.provides

4 directories, 3 files

我們可以看出,這個(gè)工程沒(méi)有任何Java代碼喉童,只有兩個(gè)文件:pom.xml跟spring.provides撇寞。starter本身在你的應(yīng)用程序中實(shí)際上是空的顿天。

其中,
spring.provides文件

provides: freemarker,spring-context-support

主要是給這個(gè)starter起個(gè)好區(qū)分的名字蔑担。

Spring Boot 通過(guò)starter對(duì)項(xiàng)目的依賴進(jìn)行統(tǒng)一管理. starter利用了maven的傳遞依賴解析機(jī)制,把常用庫(kù)聚合在一起, 組成了針對(duì)特定功能而定制的依賴starter牌废。

我們可以使用IDEA提供的maven依賴圖分析的功能(如下圖),得到spring-boot-starter-freemarker依賴的module啤握。

IDEA提供的maven依賴圖分析
spring-boot-starter-freemarker依賴的module

從上面的依賴圖鸟缕,我們可以清晰看出其間依賴關(guān)系。

當(dāng)Spring Boot Application中自動(dòng)配置EnableAutoConfiguration的相關(guān)類執(zhí)行完畢之后排抬,Spring Boot會(huì)進(jìn)一步解析對(duì)應(yīng)類的配置信息懂从。如果我們配置了spring-boot-starter-freemarker ,maven就會(huì)通過(guò)這個(gè)starter所依賴的spring-boot-autoconfigure蹲蒲,自動(dòng)傳遞到spring-boot-autoconfigure工程中番甩。

我們來(lái)簡(jiǎn)單分析一下spring-boot-autoconfigure工程的架構(gòu)。

其中届搁,F(xiàn)reeMarker的自動(dòng)配置類是org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration缘薛。

下面我們來(lái)簡(jiǎn)要分析一下FreeMarkerAutoConfiguration這個(gè)類。

在FreeMarkerAutoConfiguration類上面有四行注解:

@Configuration
@ConditionalOnClass({ freemarker.template.Configuration.class,
        FreeMarkerConfigurationFactory.class })
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(FreeMarkerProperties.class)
public class FreeMarkerAutoConfiguration {
    ...
}

其中卡睦,
(1)@Configuration宴胧,是org.springframework.context.annotation包里面的注解。這么說(shuō)吧表锻,用@Configuration注解該類恕齐,等價(jià) 與XML中配置beans;用@Bean標(biāo)注方法等價(jià)于XML中配置bean浩嫌。

(2)@ConditionalOnClass檐迟,org.springframework.boot.autoconfigure.condition包里面的注解。意思是當(dāng)類路徑下有指定的類的條件下码耐,才會(huì)去注冊(cè)被標(biāo)注的類為一個(gè)bean追迟。在上面的代碼中的意思就是,當(dāng)類路徑中有freemarker.template.Configuration.class,FreeMarkerConfigurationFactory.class兩個(gè)類的時(shí)候骚腥,才會(huì)實(shí)例化FreeMarkerAutoConfiguration這個(gè)Bean敦间。

(3)@AutoConfigureAfter,org.springframework.boot.autoconfigure包里面的注解束铭。這個(gè)通過(guò)注解的名字意思就可以知道廓块,當(dāng)WebMvcAutoConfiguration.class這個(gè)類實(shí)例化完畢,才能實(shí)例化FreeMarkerAutoConfiguration(有個(gè)先后順序)契沫。SpringBoot使用@ AutoConfigureBefore带猴、@AutoConfigureAfter注解來(lái)定義這些配置類的載入順序。

(4)@EnableConfigurationProperties懈万,表示啟動(dòng)對(duì)FreeMarkerProperties.class的內(nèi)嵌配置支持拴清,自動(dòng)將FreeMarkerProperties注冊(cè)為一個(gè)bean靶病。這個(gè)FreeMarkerProperties類里面就是關(guān)于FreeMarker屬性的配置:

@ConfigurationProperties(prefix = "spring.freemarker")
public class FreeMarkerProperties extends AbstractTemplateViewResolverProperties {

    public static final String DEFAULT_TEMPLATE_LOADER_PATH = "classpath:/templates/";

    public static final String DEFAULT_PREFIX = "";

    public static final String DEFAULT_SUFFIX = ".ftl";

    /**
     * Well-known FreeMarker keys which will be passed to FreeMarker's Configuration.
     */
    private Map<String, String> settings = new HashMap<>();

    /**
     * Comma-separated list of template paths.
     */
    private String[] templateLoaderPath = new String[] { DEFAULT_TEMPLATE_LOADER_PATH };

    /**
     * Prefer file system access for template loading. File system access enables hot
     * detection of template changes.
     */
    private boolean preferFileSystemAccess = true;

    public FreeMarkerProperties() {
        super(DEFAULT_PREFIX, DEFAULT_SUFFIX);
    }

    public Map<String, String> getSettings() {
        return this.settings;
    }

    public void setSettings(Map<String, String> settings) {
        this.settings = settings;
    }

    public String[] getTemplateLoaderPath() {
        return this.templateLoaderPath;
    }

    public boolean isPreferFileSystemAccess() {
        return this.preferFileSystemAccess;
    }

    public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
        this.preferFileSystemAccess = preferFileSystemAccess;
    }

    public void setTemplateLoaderPath(String... templateLoaderPaths) {
        this.templateLoaderPath = templateLoaderPaths;
    }

}

綜上,當(dāng)(1)(2)兩個(gè)條件滿足時(shí)口予,才會(huì)繼續(xù)(3)(4)的動(dòng)作娄周,同時(shí)注冊(cè)FreeMarkerAutoConfiguration這個(gè)Bean。該類的結(jié)構(gòu)如下圖:

我們來(lái)看其內(nèi)部類FreeMarkerWebConfiguration的代碼:

    @Configuration
    @ConditionalOnClass(Servlet.class)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    public static class FreeMarkerWebConfiguration extends FreeMarkerConfiguration {

        @Bean
        @ConditionalOnMissingBean(FreeMarkerConfig.class)
        public FreeMarkerConfigurer freeMarkerConfigurer() {
            FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
            applyProperties(configurer);
            return configurer;
        }

        @Bean
        public freemarker.template.Configuration freeMarkerConfiguration(
                FreeMarkerConfig configurer) {
            return configurer.getConfiguration();
        }

        @Bean
        @ConditionalOnMissingBean(name = "freeMarkerViewResolver")
        @ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true)
        public FreeMarkerViewResolver freeMarkerViewResolver() {
            FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
            this.properties.applyToViewResolver(resolver);
            return resolver;
        }

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnEnabledResourceChain
        public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
            return new ResourceUrlEncodingFilter();
        }

    }

其中沪停,
(1)@ConditionalOnWebApplication(type = Type.SERVLET)煤辨, 是當(dāng)該應(yīng)用是基于Servlet的Web應(yīng)用時(shí)。

(2)@ConditionalOnMissingBean(name = "freeMarkerViewResolver")木张,是當(dāng)Spring容器中不存在freeMarkerViewResolver的Bean時(shí)众辨。

(3)@ConditionalOnProperty(name = "spring.freemarker.enabled", matchIfMissing = true),指定的spring.freemarker.enabled屬性是否有舷礼。如果沒(méi)有(IfMissing)泻轰,設(shè)為true。

當(dāng)(1)(2)(3)三個(gè)條件都滿足且轨,則注冊(cè)freeMarkerViewResolver這個(gè)Bean。

我們也可以自定義我們自己的my-starter虚婿,以及實(shí)現(xiàn)對(duì)應(yīng)的@MyEnableAutoConfiguration旋奢。SpringBoot有很多第三方starter,其自動(dòng)配置的原理基本都是這樣然痊,比如mybatis-spring-boot-starter的MybatisAutoConfiguration至朗,閱讀源碼https://github.com/mybatis/spring-boot-starter[4]

上面文字描述了這么多剧浸,再用一張形象生動(dòng)的圖來(lái)說(shuō)明[5]:

SpringBoot Autoconfigure 工作原理圖

5.5 spring.factories與定義應(yīng)用程序的初始化行為

上面說(shuō)了這么多锹引,講的都是讀取properties文件中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的全限定名對(duì)應(yīng)的值。SpringBoot內(nèi)部還有許多其他的key用于過(guò)濾得到需要加載的類唆香。

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider

這些key仍然是定義在spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories文件中嫌变。

還有對(duì)應(yīng)的用于測(cè)試的自動(dòng)配置,在
spring-boot/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories文件中定義躬它。

另外腾啥,我們使用spring.factories里還可以定制應(yīng)用程序的初始化行為。這樣我們就可以在應(yīng)用程序載入前操縱Spring的應(yīng)用程序上下文ApplicationContext冯吓。

例如倘待,可以使用ConfigurableApplicationContext類的addApplicationListener()方法,在應(yīng)用上下文ApplicationContext中創(chuàng)建監(jiān)聽(tīng)器组贺。

自動(dòng)配置運(yùn)行日志報(bào)告功能就是這么實(shí)現(xiàn)的凸舵。我們來(lái)看在spring.factories中,Initializers一段的配置:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

其中失尖,AutoConfigurationReportLoggingInitializer監(jiān)聽(tīng)到系統(tǒng)事件時(shí)啊奄,比如上下文刷新ContextRefreshedEvent或應(yīng)用程序啟動(dòng)故障ApplicationFailedEvent之類的事件渐苏,Spring Boot可以做一些事情。這里說(shuō)的代碼在AutoConfigurationReportLoggingInitializer.AutoConfigurationReportListener里面增热。關(guān)于支持的事件類型supportsEventType的如下:

    private class AutoConfigurationReportListener implements GenericApplicationListener {

...
        @Override
        public boolean supportsEventType(ResolvableType resolvableType) {
            Class<?> type = resolvableType.getRawClass();
            if (type == null) {
                return false;
            }
            return ContextRefreshedEvent.class.isAssignableFrom(type)
                    || ApplicationFailedEvent.class.isAssignableFrom(type);
        }

        @Override
        public boolean supportsSourceType(Class<?> sourceType) {
            return true;
        }

        @Override
        public void onApplicationEvent(ApplicationEvent event) {
    AutoConfigurationReportLoggingInitializer.this.onApplicationEvent(event);
        }

    }

要以調(diào)試模式啟動(dòng)應(yīng)用程序整以,可以使用-Ddebug標(biāo)識(shí),或者在application.properties文件這添加屬性debug= true峻仇。這樣公黑,當(dāng)我們以調(diào)試模式啟動(dòng)應(yīng)用程序時(shí),SpringBoot就可以幫助我們創(chuàng)建自動(dòng)配置的運(yùn)行報(bào)告摄咆。對(duì)于每個(gè)自動(dòng)配置凡蚜,通過(guò)報(bào)告我們可以看到它啟動(dòng)或失敗的原因。 這個(gè)報(bào)告內(nèi)容格式大致如下:

=========================
AUTO-CONFIGURATION REPORT
=========================


Positive matches:
-----------------

   DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)

   DataSourceAutoConfiguration#dataSourceInitializer matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; SearchStrategy: all) did not find any beans (OnBeanCondition)

   DataSourceAutoConfiguration.PooledDataSourceConfiguration matched:
      - AnyNestedCondition 2 matched 0 did not; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.PooledDataSourceAvailable PooledDataSource found supported DataSource; NestedCondition on DataSourceAutoConfiguration.PooledDataSourceCondition.ExplicitType @ConditionalOnProperty (spring.datasource.type) matched (DataSourceAutoConfiguration.PooledDataSourceCondition)
      - @ConditionalOnMissingBean (types: javax.sql.DataSource,javax.sql.XADataSource; SearchStrategy: all) did not find any beans (OnBeanCondition)

   ...

Exclusions:
-----------

    None


Unconditional classes:
----------------------

    org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration

    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration


除了SpringBoot官方提供的starter外吭从,還有社區(qū)貢獻(xiàn)的很多常用的第三方starter朝蜘,列表可參考[2]。

另外涩金,國(guó)內(nèi)很多公司使用RPC框架dubbo谱醇,關(guān)于SpringBoot集成dubbo,可參考:https://github.com/linux-china/spring-boot-dubbo步做。

參考資料:

1.http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-starter
2.https://github.com/spring-projects/spring-boot/tree/master/spring-boot-starters
3.http://www.cnblogs.com/javaee6/p/3714719.html
4.https://github.com/mybatis/spring-boot-starter
5.https://afoo.me/posts/2015-07-09-how-spring-boot-works.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末副渴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子全度,更是在濱河造成了極大的恐慌煮剧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件将鸵,死亡現(xiàn)場(chǎng)離奇詭異勉盅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)顶掉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)草娜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人一喘,你說(shuō)我怎么就攤上這事驱还。” “怎么了凸克?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵议蟆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我萎战,道長(zhǎng)咐容,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任蚂维,我火速辦了婚禮戳粒,結(jié)果婚禮上路狮,老公的妹妹穿的比我還像新娘。我一直安慰自己蔚约,他們只是感情好奄妨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著苹祟,像睡著了一般砸抛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上树枫,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天直焙,我揣著相機(jī)與錄音,去河邊找鬼砂轻。 笑死奔誓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搔涝。 我是一名探鬼主播厨喂,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼庄呈!你這毒婦竟也來(lái)了杯聚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抒痒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颁褂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體故响,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年颁独,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彩届。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誓酒,死狀恐怖樟蠕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靠柑,我是刑警寧澤寨辩,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站歼冰,受9級(jí)特大地震影響靡狞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隔嫡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一甸怕、第九天 我趴在偏房一處隱蔽的房頂上張望甘穿。 院中可真熱鬧,春花似錦梢杭、人聲如沸温兼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)募判。三九已至,卻和暖如春吝羞,著一層夾襖步出監(jiān)牢的瞬間兰伤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工钧排, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敦腔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓恨溜,卻偏偏與公主長(zhǎng)得像符衔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糟袁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容