Spring Boot自動裝配詳解

《Spring Boot Hello World》中介紹了一個簡單的spring boot例子,體驗了spring boot中的諸多特性,其中的自動配置特性極大的簡化了程序開發中的工做(不用寫一行XML)。本文咱們就來看一下spring boot是如何作到自動配置的。
首先闡明,spring boot的自動配置是基於spring framework提供的特性實現的,因此在本文中,咱們先介紹spring framework的相關特性,在瞭解了這些基礎知識後,咱們再來看spring boot的自動配置是如何實現的。html

基於Java代碼對Spring進行配置

在以往使用spring framework進行程序開發時,相信你們也只是使用XML搭配註解的方式對spring容器進行配置,例如在XML文件中使用<context:component-scan base-package="**"/>指定spring須要掃描package的根路徑。
除了使用XML對spring進行配置,還能夠使用Java代碼執行徹底相同的配置。下面咱們詳細看一下如何使用Java代碼對spring容器進行配置,詳細內容可參考這裏
使用Java代碼進行spring配置,有兩個核心註解@Configuration@Beanjava

@Configuration
public class AppConfig {

    @Bean
    public SampleService sampleService() {
        return new SampleServiceImpl();
    }
}

@Bean註解用於修飾方法,方法的返回值會做爲一個bean裝載到spring容器中。bean的id就是方法的名字。
@Configuration註解用於修飾一個類,它代表這個類的做用是用來對spring容器進行配置的。
上面的Java代碼至關於下面的XML配置:web

<beans>
    <bean id="sampleService" class="com.**.SampleServiceImpl"/>
</beans>

使用AnnotationConfigApplicationContext類構建一個spring容器,從容器中取出對應的bean的測試代碼以下:redis

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    SampleService myService = ctx.getBean("sampleService" ,SampleService.class);
    myService.doService();
}

Java代碼配置ComponentScan

使用@ComponentScan註解指定須要掃描package的根路徑:spring

@Configuration
@ComponentScan(basePackages = "com.**.service.impl")
public class AppConfig {

}

上面的Java代碼至關於下面的XML配置:json

<beans>
    <context:component-scan base-package="com.**.service.impl"/>
</beans>

此外,AnnotationConfigApplicationContext類還提供了scan方法用於指定要掃描的包路徑。咱們能夠刪除AppConfig類上的@ComponentScan註解,在構造spring容器時使用下面代碼:segmentfault

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.**.service.impl");
    ctx.refresh();
    SampleService myService = ctx.getBean("sampleService" ,SampleService.class);
    myService.doService();
}

使用@Import組合多個配置

將全部的spring配置所有放在同一個類中確定是不合適的,這會致使那個配置類很是複雜。一般會建立多個配置類,再借助@Import將多個配置類組合成一個。@Import的功能相似於XML中的<import/>api

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

上面的代碼分別建立了兩個配置類ConfigA和ConfigB,它們分別定義了a和b兩個Bean。在ConfigB上使用@Import註解導入ConfigA的配置,此時應用代碼若是加載ConfigB的配置,就自動也加載了ConfigA的配置。以下代碼所示:app

public static void main(String[] args) {
    // 只加載ConfigB一個配置類,但同時也包含了ConfigA的配置
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);

    System.out.println(a);
    System.out.println(b);
}

@Import還能夠同時導入多個配置類。當有多個配置類須要同時導入時,示意代碼以下:elasticsearch

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }
}

條件註解@Conditional

@Conditional註解根據某一個條件是否成立來判斷是否構建Bean。藉助Condition接口能夠表示一個特定條件。例以下面代碼實現了一個條件,固然這個條件始終成立:

public class SampleCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 若是條件成立返回true, 反之返回false
        return true;
    }
}

有了表達條件的類SampleCondition,接下來咱們就能夠經過@Conditional註解對建立bean的函數進行配置:

請輸入代碼@Configuration
public class ConditionConfig {

    // 只有當知足SampleCondition指定的條件時,參會構造id時sampleBean這個bean。固然這裏的條件始終成立
    @Conditional(SampleCondition.class)
    @Bean
    public SampleBean sampleBean() {
        return new SampleBean();
    }
}

因爲SampleCondition的matches方法返回true,表示建立bean的條件成立,因此sampleBean會被建立。若是matches返回false,sampleBean就不會被構建。
在spring boot中,根據這個原理提供了不少@ConditionOnXXX的註解,這些註解都在包org.springframework.boot.autoconfigure.condition下面。例如比較常見的@ConditionalOnClass註解,這個註解的判斷邏輯是隻有指定的某個類在classpath上存在時,判斷條件才成立。@ConditionalOnClass的具體代碼以下:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

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

    String[] name() default {};
}

@ConditionalOnClass具體的判斷邏輯可參看OnClassCondition類。

@SpringBootApplication註解

介紹完前面這些基礎的知識後,咱們來看Spring Boot是如何實現自動裝配的。
《Spring Boot官方文檔第14章》推薦在程序的main class上使用註解@SpringBootApplication對Spring應用進行自動配置,咱們就從分析這個註解開始。下面是@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、@EnableAutoConfiguration和@ComponentScan三個註解構成。
@SpringBootConfiguration代表被標註的類提供了Spring Boot應用的配置,其實這個註解與@Configuration註解的功能相似。
@ComponentScan指定須要掃描package的路徑,@SpringBootApplication也提供了相應屬性,指定須要掃描哪些package或不掃描哪些package。《Spring Boot官方文檔》建議將應用的main class放置於整個工程的根路徑,並用@SpringBootApplication註解修飾main class,這樣整個項目的子package就都會被自動掃描包含。建議的工程結構以下所示,其中Application就是應用的main class。

com
 +- example
     +- myapplication
         +- Application.java
         |
         +- customer
         |   +- Customer.java
         |   +- CustomerController.java
         |   +- CustomerService.java
         |   +- CustomerRepository.java
         |
         +- order
             +- Order.java
             +- OrderController.java
             +- OrderService.java
             +- OrderRepository.java

@EnableAutoConfiguration是這裏最重要的註解,它實現了對Spring Boot應用自動裝配的功能。@EnableAutoConfiguration是利用SpringFactoriesLoader機制加載自動裝配配置的,它的配置數據在META-INF/spring.factories中,咱們打開spring-boot-autoconfigure jar中的該文件,發現EnableAutoConfiguration對應着N多XXXAutoConfiguration配置類,咱們截取幾個重要的配置類以下(已經刪除了不少):

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
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.CloudServiceConnectorsAutoConfiguration,\
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.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
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.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
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.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.jsonb.JsonbAutoConfiguration,\
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.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
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,\

能夠看到Spring Boot提供了N多XXXAutoConfiguration類,有Spring Framework的、Web的、redis的、JDBC的等等。
咱們從其中選擇HttpEncodingAutoConfiguration這個類來看下它是如何實現自動配置的:

@Configuration
@EnableConfigurationProperties(HttpProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled",
        matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

    private final HttpProperties.Encoding properties;

    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    @Bean
    @ConditionalOnMissingBean
    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;
    }

上面代碼表示,只有在知足以下條件時,纔會注入characterEncodingFilter這個bean:

  1. 只有在WebApplication的狀況
  2. classpath上必須存在CharacterEncodingFilter類
  3. 配置文件中配置了spring.http.encoding.enabled爲true或者沒有配置
  4. Spring容器中不存在類型爲CharacterEncodingFilter的bean

總結

Spring Boot自動裝配的原理並非很是複雜,其實背後的主要原理就是條件註解。
當咱們使用@EnableAutoConfiguration註解激活自動裝配時,實質對應着不少XXXAutoConfiguration類在執行裝配工做,這些XXXAutoConfiguration類是在spring-boot-autoconfigure jar中的META-INF/spring.factories文件中配置好的,@EnableAutoConfiguration經過SpringFactoriesLoader機制建立XXXAutoConfiguration這些bean。XXXAutoConfiguration的bean會依次執行並判斷是否須要建立對應的bean注入到Spring容器中。
在每一個XXXAutoConfiguration類中,都會利用多種類型的條件註解@ConditionOnXXX對當前的應用環境作判斷,如應用程序是否爲Web應用、classpath路徑上是否包含對應的類、Spring容器中是否已經包含了對應類型的bean。若是判斷條件都成立,XXXAutoConfiguration就會認爲須要向Spring容器中注入這個bean,不然就忽略。

相關文章
相關標籤/搜索