1. 程式人生 > >SpringBoot-自動配置原始碼解析

SpringBoot-自動配置原始碼解析

接著上一篇部落格《 SpringBoot-快速搭建WEB工程》提出的需要分析的三個方面:我們來深入的探究SpringBoot是如何在沒有一個配置檔案的情況下為我們啟動好一個完整的WEB工程的,首先我們從@SpringBootApplication 開始這裡的分析會剖出一些次要的資訊沿著主幹走,所以可能會有一些略過的地方。以下原始碼擷取自spring-boot-1.4.0.RELEASE

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration @ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)) public @interface SpringBootApplication {}

可以看到這是一個複合註解:其中@Target,@Retention,@Documented,@Inherited這四個註解不做過多的解釋。
@ComponentScan這個是Spring很常用的註解也不做解釋。
下面來看一看@SpringBootConfiguration:

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

這個註解,有作用的也是@Configuration:這是標註當前類為:JavaConfig類
現在就來看看最後的一個註解@EnableAutoConfiguration

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

這個註解上@Import(EnableAutoConfigurationImportSelector.class)代表引入其它的Spring的JavaConfig接著進入EnableAutoConfigurationImportSelector.class
關注一下以下的方法:

@Override
public String[] selectImports(AnnotationMetadata metadata) {
    if (!isEnabled(metadata)) {
        return NO_IMPORTS;
    }
    try {
        AnnotationAttributes attributes = getAttributes(metadata);
        List<String> configurations = getCandidateConfigurations(metadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(metadata, attributes);
        configurations.removeAll(exclusions);
        configurations = sort(configurations);
        recordWithConditionEvaluationReport(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
        throw new IllegalStateException(ex);
    }
}

進入:List configurations = getCandidateConfigurations(metadata,attributes);

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

在進入:List configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
程式碼如下:

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<String>();
        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_RESOURCE_LOCATION + "]", ex);
    }
}
public static final String FACTORIES_RESOURCE_LOCATION = 
"META-INF/spring.factories";

在上面的程式碼可以看到自動配置器會跟根據傳入的factoryClass.getName()到spring.factories的檔案中找到相應的key,從而載入裡面的類但我們開啟spring-boot-autoconfigure-1.4.0.RELEASE.jar裡面的spring.factories可以發現很多key,那麼這裡是怎麼樣的一個載入的流程呢這裡只把程式碼貼出來不進和講解,下一篇部落格會對SpringBoot啟動的整個流程進入深入的分析《SpringBoot-啟動流程分析》.

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, ex);
        throw new IllegalStateException(ex);
    }
}

這篇文章只是說明自動配置功能,所以這裡只指明自動配置是的載入是發生在refreshContext(context);這一句。
由於篇幅原因這裡截取了一小部分,完整的請到spring-boot-autoconfigure-1.4.0.RELEASE.jar包裡檢視spring.factories檔案。

# 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.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
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.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.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
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.data.redis.RedisAutoConfiguration為例檢視程式碼如下:

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
    @Configuration
    @ConditionalOnClass(GenericObjectPool.class)
    protected static class RedisConnectionConfiguration {
        @Bean
        @ConditionalOnMissingBean(RedisConnectionFactory.class)
        public JedisConnectionFactory redisConnectionFactory()
                throws UnknownHostException {
            return applyProperties(createJedisConnectionFactory());
        }
    }
    @Configuration
    protected static class RedisConfiguration {
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                        throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
        @Bean
        @ConditionalOnMissingBean(StringRedisTemplate.class)
        public StringRedisTemplate stringRedisTemplate(
                RedisConnectionFactory redisConnectionFactory)
                        throws UnknownHostException {
            StringRedisTemplate template = new StringRedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }

    }
}

把類簡化一下基本上就可以看出這就是一個Spring的註解版的配置
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })這個註解的意思是:當存在JedisConnection.class, RedisOperations.class, Jedis.class三個類時才解析RedisAutoConfiguration配置類,否則不解析這一個配置類
@ConditionalOnMissingBean(name = “redisTemplate”)這個註解的意思是如果容器中不存在name指定的bean則建立bean注入,否則不執行
內部程式碼可以看出裡面又定義了兩個帶@Configuration註解的配置類,這兩個配置類會向SpringIOC容器注入可能3個bean:
首先當類路徑下存在(GenericObjectPool.class)時則注入JedisConnectionFactory 的例項如果Spring容器中不存在name = “redisTemplate”的實體,則建立RedisTemplate和StringRedisTemplate例項注入容器,這樣在Spring的專案中,就可以用在任意的Spring管理的bean中註冊用RedisTemplate和StringRedisTemplate的例項來對redis進入操作了。
通過以上分析的過程我們可以發現只要一個基於SpringBoot專案的類路徑下存在JedisConnection.class, RedisOperations.class, Jedis.class就可以觸發自動化配置,意思說我們只要在maven的專案中依賴了spring-data-redis-1.7.2.RELEASE.jar和C:jedis-2.8.2.jar就可以觸發自動配置,但這樣不是每整合一個功能都要去分析裡其自動化配置類,那就代不到開箱即用的效果了。所以Spring-boot為我提供了統一的starter可以直接配置好相關觸發自動配置的所有的類的依賴集如redis的start如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

這裡是擷取的spring-boot-starter-data-redis的原始碼中pom.xml檔案中所有依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
</dependencies>

因為maven依賴的傳遞性,我們只要依賴starter就可以看在類路徑下配置好所有的觸發自動配置的所有類,實現開箱即用的功能。
這裡只是大概的說明了一下SpringBoot的Starter的用法,後面在說明自制自已的starter時還會做深入的說明。