SpringBoot源碼-自動配置原理

1. 簡介

本篇文章主要是針對上一篇文章:啓動原理 的補充,在上一篇文章的@SpringBootApplication註解分析中,對於@EnableAutoConfiguration的闡述意猶未盡,但限於篇幅與文章主題規劃,就拿到這裏作詳細說明了。java

號外:當面試管問你SpringBoot和Spring區別時?只回答簡化了配置,內置了tomcat等能夠嗎? 遠遠不行,最重要的仍是自動化配置。因此這篇文章就出現了。react

重要聲明:本系列SpringBoot源碼的分析都是針對2.0.1.RELEASE版本!!!web

2. 註解解析

2.1 註解回顧

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

2.2 解析

上一篇文章中已經對大部分註解作了簡要介紹,鑑於對@Import(AutoConfigurationImportSelector.class)重要性地位的尊重,這裏單獨拿出來詳細介紹一下。面試

在這個註解中,最重要的是它導入了一個類AutoConfigurationImportSelector,它是一個ImportSelector接口的實現類,而ImportSelector接口中的selectImports方法所返回的類將被Spring容器管理起來。redis

2.2.1 解惑

你們平時看博客之類相關文章可能發現大多數在講解這部分的內容時使用的仍是@Import(EnableAutoConfigurationImportSelector.class),這是版本升級致使的,EnableAutoConfigurationImportSelector繼承自AutoConfigurationImportSelector,從 spring boot 1.5 之後,EnableAutoConfigurationImportSelector已經再也不被建議使用,而是推薦使用 AutoConfigurationImportSelector。所以我們也順應版本發展之潮流,開啓源碼解析新篇章。spring

爲了便於各位讀者理解,小編偷來了一張圖片,圖片中主題流程是沒有問題的,只是版本有些落後,你們將就着看一下吧。
圖片描述sql

2.2.2 導火線

上一篇文章中簡要提到了@EnableAutoConfiguration能夠幫助SpringBoot應用將全部符合條件的@Configuration配置都加載到當前SpringBoot建立的IoC容器中。本篇文件就以此爲入口詳細分析其內部運做原理。mongodb

2.2.3 原理

Springboot應用啓動過程當中使用Spring的工具類ConfigurationClassParser(這個工具類信息量比較大,將在後續文章中詳細闡述)分析@Configuration註解的配置類(分析過程主要是遞歸分析配置類的註解@Import),產生一組ConfigurationClass對象。若是發現註解中存在@Import,就建立一個相應的ImportSelector對象, 並調用這個對象的selectImports(AnnotationMetadata annotationMetadata)方法, 上面例子中AutoConfigurationImportSelector的導入@Import(AutoConfigurationImportSelector.class) 就屬於這種狀況。因此ConfigurationClassParser會實例化一個 AutoConfigurationImportSelector對象並調用它的 selectImports() 方法。既然如今聚光燈打在了AutoConfigurationImportSelector臉上,那就順勢端出它的源碼看一下吧。apache

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered {

    private static final String[] NO_IMPORTS = {};

    private static final Log logger = LogFactory
            .getLog(AutoConfigurationImportSelector.class);

    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

    private ConfigurableListableBeanFactory beanFactory;

    private Environment environment;

    private ClassLoader beanClassLoader;

    private ResourceLoader resourceLoader;

看源碼能夠發現AutoConfigurationImportSelector類實現的接口主要分三類:DeferredImportSelector接口、...Aware接口、Ordered
下面分別對三類接口簡要介紹。json

  1. DeferredImportSelector接口:DeferredImportSelector是spring-context下的annotation定義
    public interface DeferredImportSelector extends ImportSelector

擴展:DeferredImportSelector是ImportSelector的一個擴展,ImportSelector的主要做用是收集須要導入的配置類,若是該接口的實現類同時實現EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware或者ResourceLoaderAware,那麼在調用其selectImports方法以前先調用上述接口中對應的方法,若是須要在全部的@Configuration處理完再導入時,能夠實現DeferredImportSelector接口。再看AutoConfigurationImportSelector類,它不光實現了DeferredImportSelector接口,同時還實現EnvironmentAware, BeanFactoryAware ,BeanClassLoaderAware,ResourceLoaderAware接口,因此在AutoConfigurationImportSelector對象的selectImports方法執行前,全部須要的資源都已經獲取到了(就是這個成員變量beanFactory、environment、beanClassLoader、resourceLoader)。

  1. ...Aware接口:Spring的依賴注入的最大亮點就是你全部的Bean對Spring容器的存在是沒有意識的。即你能夠將你的容器替換成別的容器,例如Goggle Guice,這時Bean之間的耦合度很低。可是在實際的項目中,咱們不可避免的要用到Spring容器自己的功能資源,這時候Bean必需要意識到Spring容器的存在,才能調用Spring所提供的資源,這就是所謂的Spring Aware。其實Spring Aware原本就是Spring設計用來框架內部使用的,若使用了Spring Aware,你的Bean將會和Spring框架耦合。

    • BeanFactoryAware:得到當前bean factory,這樣能夠調用容器的服務
    • ResourceLoaderAware:是一個標記接口,用於經過ApplicationContext上下文注入ResourceLoader。
    • EnvironmentAware:加載配置文件
    • BeanClassLoaderAware:得到資源加載器,能夠得到外部資源文件

全部的Aware都優先於selectImports方法執行。

  1. Ordered接口:用來對selectImports的執行順序排序

2.2.4 核心方法 selectImports

Springboot應用啓動過程當中使用ConfigurationClassParser分析配置類時,若是發現註解中存在@Import(ImportSelector)的狀況,就會建立一個相應的ImportSelector對象,並調用其方法 public String[] selectImports(AnnotationMetadata annotationMetadata)。該方法將全部須要導入的組件,以全類名的方式返回,這些組件就會被添加到容器中。按照springboot的編碼格式,springboot源碼中全部形如XXXAutoConfiguration的配置類都是自動配置類,無需其餘操做便可注入IOC容器。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //判斷 enableautoconfiguration註解有沒有開啓,默認開啓(是否進行自動裝配)
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //第一步:加載配置文件META-INF/spring-autoconfigure-metadata.properties,從中獲取全部支持自動配置的信息
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        //獲取EnableAutoConfiguration的屬性,也就是exclue和excludeName的內容
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
         //第二步:讀取META-INF/spring.factories下的EnableAutoConfiguration的配置
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        //去除重複的配置類,若咱們本身寫的starter 可能存在重複的
        configurations = removeDuplicates(configurations);
        //若是項目中某些自動配置類,咱們不但願其自動配置,咱們能夠經過EnableAutoConfiguration的exclude或excludeName屬性進行配置,或者也能夠在配置文件裏經過配置項「spring.autoconfigure.exclude」進行配置。
        //找到不但願自動配置的配置類(根據EnableAutoConfiguration註解的一個exclusions屬性)
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        //校驗排除類(exclusions指定的類必須是自動配置類,不然拋出異常)
        checkExcludedClasses(configurations, exclusions);
        //刪除全部不但願自動配置的配置類
        configurations.removeAll(exclusions);
        //第三步:根據OnClassCondition(註解中配置的當存在某類才生效)註解過濾掉一些條件沒有知足的
        configurations = filter(configurations, autoConfigurationMetadata);// 如今已經找到全部須要被應用的候選配置類
         //第四步:廣播AutoConfigurationImportEvents事件
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
    }
2.2.4.1 第一步解析

其實就是用類加載器去加載:META-INF/spring-autoconfigure-metadata.properties(Spring-boot-autoconfigure-2.0.1.RELEASE.jar)文件中定義的配置,返回PropertiesAutoConfigurationMetadata(實現了AutoConfigurationMetadata接口,封裝了屬性的get set方法),SpringBoot使用一個Annotation的處理器來收集一些自動裝配的條件,那麼這些條件能夠在META-INF/spring-autoconfigure-metadata.properties進行配置。SpringBoot會將收集好的@Configuration進行一次過濾進而剔除不知足條件的配置類。

protected static final String PATH = "META-INF/"
            + "spring-autoconfigure-metadata.properties";
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, PATH);
    }

    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
        //ClassLoader.getResource()方法找到具備給定名稱的資源。資源是一些數據(圖像,音頻,文本等),返回URL對象讀取資源。該方法就是獲取該目錄下的配置數據spring-autoconfigure-metadata.properties
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(path)
                    : ClassLoader.getSystemResources(path));
            Properties properties = new Properties();
            while (urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils
                        .loadProperties(new UrlResource(urls.nextElement())));
            }
            return loadMetadata(properties);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException(
                    "Unable to load @ConditionalOnClass location [" + path + "]", ex);
        }
    }

spring-autoconfigure-metadata.properties文件格式以下:
自動配置的類全名.條件=值
這個值就是key中全類名所依賴的條件,若是類路徑下不存在這個Java類,那麼這個自動配置的全類名對應的bean將會被過濾掉。

META-INF/spring-autoconfigure-metadata.properties內容以下:

#Thu Apr 05 11:37:28 UTC 2018
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,org.springframework.data.cassandra.core.ReactiveCassandraTemplate,reactor.core.publisher.Flux
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration=
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration=
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.jms.artemis.ArtemisXAConnectionFactoryConfiguration=
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration=
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.CouchbaseBucket,com.couchbase.client.java.Cluster
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitTemplate,com.rabbitmq.client.Channel
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureOrder=-2147483648
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration=
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.Bucket,org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository,reactor.core.publisher.Flux
org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration.Configuration=
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration=
org.springframework.boot.autoconfigure.jms.artemis.ArtemisConnectionFactoryConfiguration.Configuration=
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration.Configuration=
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration=
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration=
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnClass=org.springframework.batch.core.launch.JobLauncher,javax.sql.DataSource,org.springframework.jdbc.core.JdbcOperations
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration=
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration.ConditionalOnClass=org.elasticsearch.client.Client,org.springframework.data.elasticsearch.core.ElasticsearchTemplate
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration=
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.session.HazelcastSessionConfiguration.Configuration=
org.springframework.boot.autoconfigure.session.JdbcSessionConfiguration=
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration.ConditionalOnClass=org.jooq.DSLContext
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration.ConditionalOnClass=org.springframework.ldap.core.ContextSource
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseConfigurerAdapterConfiguration.ConditionalOnClass=org.springframework.data.couchbase.config.CouchbaseConfigurer
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.freemarker.FreeMarkerServletWebConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration=
org.springframework.boot.autoconfigure.session.JdbcSessionConfiguration.Configuration=
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.freemarker.FreeMarkerNonWebConfiguration=
org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration=
org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration.Configuration=
org.springframework.boot.autoconfigure.session.NoOpSessionConfiguration=
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration=
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration=
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.transaction.jta.AtomikosJtaConfiguration=
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQConnectionFactoryConfiguration.Configuration=
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration=
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration=
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration=
org.springframework.boot.autoconfigure.session.MongoSessionConfiguration.Configuration=
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.http.JsonbHttpMessageConvertersConfiguration.ConditionalOnClass=javax.json.bind.Jsonb
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration=
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration=
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration.Configuration=
org.springframework.boot.autoconfigure.jms.artemis.ArtemisXAConnectionFactoryConfiguration.Configuration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration.ConditionalOnClass=org.springframework.session.Session
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration.ConditionalOnClass=org.springframework.data.redis.core.RedisOperations
org.springframework.boot.autoconfigure.transaction.jta.BitronixJtaConfiguration.ConditionalOnClass=org.springframework.transaction.jta.JtaTransactionManager,bitronix.tm.jndi.BitronixContext
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.ConditionalOnClass=org.flywaydb.core.Flyway
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration=
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
org.springframework.boot.autoconfigure.freemarker.FreeMarkerServletWebConfiguration=
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration.Configuration=
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.couchbase.SpringBootCouchbaseReactiveDataConfiguration.Configuration=
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration.ConditionalOnClass=com.hazelcast.core.HazelcastInstance
org.springframework.boot.autoconfigure.session.RedisSessionConfiguration=
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration.Configuration=
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration=
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration.ConditionalOnClass=org.h2.server.web.WebServlet
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseConfigurerAdapterConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration=
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration.ConditionalOnClass=org.springframework.jmx.export.MBeanExporter
org.springframework.boot.autoconfigure.mustache.MustacheReactiveWebConfiguration=
org.springframework.boot.autoconfigure.session.MongoSessionConfiguration.ConditionalOnClass=org.springframework.data.mongodb.core.MongoOperations,org.springframework.session.data.mongo.MongoOperationsSessionRepositor
。。。地處省略N個字。。。
2.2.4.2 第二步解析

第二步中的getCandidateConfigurations()用來獲取默認支持的自動配置類名列表,Spring Boot在啓動的時候,使用內部工具類SpringFactoriesLoader,查找classpath上全部jar包中的META-INFspring.factories,找出其中key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的屬性定義的工廠類名稱,將這些值做爲自動配置類導入到容器中,自動配置類就生效了。

spring-boot-autoconfigure-2.0.1.RELEASE.jar這個包幫咱們引入了jdbc、kafka、logging、mail、mongo等包如圖所示。不少包須要咱們引入相應jar後自動配置才生效。因此就有了後來的過濾操做。
圖片描述

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {//這2個入參沒用到,多是後期會有擴展吧
        
        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類實際獲取到的信息。該方法實際是獲取了# Auto Configure自動配置模塊的全部類(並不是全部的類都引入了相應的jar,因此在後續的操做中會進行過濾)。

插曲:使用SpringBoot的朋友應該都知道SpringBoot由衆多Starter組成(一系列的自動化配置的starter插件),SpringBoot之因此流行,也是由於Spring starter。Spring starter是SpringBoot的核心,能夠理解爲一個可拔插式的插件,正是這些starter使得使用某個功能的開發者不須要關注各類依賴庫的處理,不須要具體的配置信息,由Spring Boot自動經過classpath路徑下的類發現須要的Bean,並織入相應的Bean。例如,你想使用Reids插件,那麼可使用spring-boot-starter-redis;若是想使用MongoDB,可使用spring-boot-starter-data-mongodb。

Starter的命名規範:官方對Starter項目的jar包定義的 artifactId 是有要求的,固然也能夠不遵照。Spring官方Starter一般命名爲spring-boot-starter-{name}如:spring-boot-starter-redis,Spring官方建議非官方的starter命名應遵照{name}-spring-boot-starter的格式。

亮點來了:朋友們,看下面的這個文件,是否是能夠找到這些starter呢?不錯,SpringBoot全部的Starter都在spring.factories這裏配置了,固然在SpringBoot項目中咱們也能夠建立自定義SpringBoot Starter來達成目的。關於自定義Starter的代碼與詳解在網上一抓一大把並且不少介紹的都不錯,我這裏就再也不重複造輪子了。

spring.factories文件以下:

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

# 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

# SpringBoot默認配置的filter
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

# 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.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
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.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
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.RedisReactiveAutoConfiguration,\
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.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.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.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.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration,\
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.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
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.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

# 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,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# 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

當獲取到的這些信息後並無直接將他們返回,而是執行了下面的篩選操做。
緣由:從spring.factories獲取到的Auto Configure信息都是支持自動配置的類,這些類的使用都是有依賴條件的,若是某個類的這些依賴條件不存在,那這個類也就不必加載了,應當排除。而這些依賴條件是在第一步的spring-autoconfigure-metadata.properties文件中配置好的。

2.2.4.3 第三步解析

過濾的方式基本上是判斷現有系統是否引入了某個組件,(系統是否使用哪一個組件是在pom定義的時候就肯定的),若是有的話則進行相關配置。好比ServletWebServerFactoryAutoConfiguration,會在ServletRequest.class等條件存在的狀況下進行配置,而EmbeddedTomcat會在Servlet.class, Tomcat.class存在的狀況下建立TomcatServletWebServerFactory。

過濾條件

SpringBoot爲咱們提供了一批實用的XxxCondition,查看了他們的源碼後發現,他們都實現了spring提供的Condition接口,而後編寫對應的annotation。咱們在使用他們的時候,只須要在須要的地方寫上這些annotation就行了。這些註解都在SpringBoot提供的jar包中
package org.springframework.boot.autoconfigure.condition。

講到這裏你們可能會存在疑惑,既然咱們在spring-autoconfigure-metadata.properties中配置了從spring.factories獲取到的全部Auto Configure中類的依賴條件,爲何還要擴展出這麼多的condition?

由於要判斷所配置的類(Auto Configure中的類)是否能夠被加載是一個很是繁瑣的邏輯,若是由某個中央控制系統來處理的話,必然會形成代碼耦合和複雜性驟增,因此SpringBoot最終使用了一向的作法——將判斷是否加載的權限下放給了各個須要進行自動配置的需求方自己,這樣在SpringBoot中擴展了不少condition。

AutoConfigurationImportFilter 是一個接口,OnClassCondition纔是它的實現類,主要功能就是過濾掉第二步加載的類中不知足條件的類,SpringBoot經常使用的條件依賴註解有:

@ConditionalOnClass : 某個class位於類路徑上,纔會實例化這個Bean。
@ConditionalOnMissingClass : classpath中不存在該類時起效 
@ConditionalOnBean : DI容器中存在該類型Bean時起效 
@ConditionalOnMissingBean : DI容器中不存在該類型Bean時起效 
@ConditionalOnSingleCandidate : DI容器中該類型Bean只有一個或@Primary的只有一個時起效 
@ConditionalOnExpression : SpEL表達式結果爲true時 
@ConditionalOnProperty : 參數設置或者值一致時起效 
@ConditionalOnResource : 指定的文件存在時起效 
@ConditionalOnJndi : 指定的JNDI存在時起效 
@ConditionalOnJava : 指定的Java版本存在時起效 
@ConditionalOnWebApplication : Web應用環境下起效 
@ConditionalOnNotWebApplication : 非Web應用環境下起效

以上註解都是元註解@Conditional演變而來的,過濾掉一些沒有知足條件的class。

至此有必要總結一下判斷是否要加載某個類的兩種方式:

  1. 根據spring-autoconfigure-metadata.properties中的依賴配置。
  2. 要加載的類上的condition註解。如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示須要在類路徑中存在SqlSessionFactory.class、SqlSessionFactoryBean.class這兩個類才能完成自動註冊。

下面就是過濾功能的實現:

private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        // 記錄候選配置類是否須要被排除,skip爲true表示須要被排除,所有初始化爲false,不須要被排除
        boolean[] skip = new boolean[candidates.length];
        // 記錄候選配置類中是否有任何一個候選配置類被忽略,初始化爲false
        boolean skipped = false;
        // 獲取AutoConfigurationImportFilter並逐個應用過濾
        for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 對過濾器注入其須要Aware的信息
            invokeAwareMethods(filter);
            // 使用此過濾器檢查候選配置類跟autoConfigurationMetadata的匹配狀況
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);
            for (int i = 0; i < match.length; i++) {
                if (!match[i]) {
                // 若是有某個候選配置類不符合當前過濾器,將其標記爲須要被排除,
                // 而且將 skipped設置爲true,表示發現了某個候選配置類須要被排除
                    skip[i] = true;
                    skipped = true;
                }
            }
        }
        if (!skipped) {
        // 若是全部的候選配置類都不須要被排除,則直接返回外部參數提供的候選配置類集合
            return configurations;
        }
        // 邏輯走到這裏由於skipped爲true,代表上面的的過濾器應用邏輯中發現了某些候選配置類,須要被排除,這裏排除那些須要被排除的候選配置類,將那些不須要被排除的候選配置類組成一個新的集合返回給調用者
        List<String> result = new ArrayList<>(candidates.length);
        for (int i = 0; i < candidates.length; i++) {
            if (!skip[i]) {//匹配-》不跳過-》添加進result
                result.add(candidates[i]);
            }
        }
        if (logger.isTraceEnabled()) {
            int numberFiltered = configurations.size() - result.size();
            logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                    + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                    + " ms");
        }
        return new ArrayList<>(result);
    }
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
                this.beanClassLoader);
}
2.2.4.4 第四步解析

當AutoConfigurationImportSelector過濾完成後會自動加載類路徑下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的實現類,並觸發fireAutoConfigurationImportEvents事件。當fireAutoConfigurationImportEvents事件被觸發時,打印出已經註冊到spring上下文中的@Configuration註解的類,打印出被阻止註冊到spring上下文中的@Configuration註解類。

private void fireAutoConfigurationImportEvents(List<String> configurations,
            Set<String> exclusions) {
            // 經過SpringFactoriesLoader.loadFactories()方法獲取全部實現 
   //  AutoConfigurationImportListener的實例化對象
        List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
        if (!listeners.isEmpty()) {
        //  生成一個Even事件,給listener發送
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                    configurations, exclusions);
            for (AutoConfigurationImportListener listener : listeners) {
                // 若是實現了或者繼承了一些Aware,則設置相應的值。
                invokeAwareMethods(listener);
                // 給AutoConfigurationImportListener監聽器發送事件
                listener.onAutoConfigurationImportEvent(event);
            }
        }
    }

    protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class,
                this.beanClassLoader);
    }

3 擴展篇

細心的朋友可能已經發現了,這哥們兒SpringFactoriesLoader在我看的這幾行源碼中的出場率實在是過高了,搞得小編再僞裝忽略它的存在感就有點不近人情了。下面就來吧啦吧啦它吧
SpringFactoriesLoader是 Spring 工廠加載機制的核心底層實現類,即 Spring Factories Loader,核心邏輯是使用 SpringFactoriesLoader 加載由用戶實現的類,並配置在約定好的META-INF/spring.factories 路徑下(spring.factories文件可能存在於工程類路徑下或者 jar 包以內,因此會存在多個該文件),該機制能夠爲框架上下文動態的增長擴展。該機制相似於 Java SPI,給用戶提供可擴展的鉤子,從而達到對框架的自定義擴展功能。spring-factories是一個典型的java properties文件,只不過Key和Value都是Java類型的完整類名,好比:
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=org.springframework.boot.autoconfigure.condition.OnClassCondition
對於@EnableAutoConfiguration來講,SpringFactoriesLoader的用途稍微有些不一樣,在當前@EnableAutoConfiguration場景中,它更多的是提供了一種配置查找的功能,即根據@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration做爲查找的Key,得到對應的一組@Configuration類(逗號分隔)。

3.1 JVM類加載機制

說到SpringFactoriesLoader就有必要提一下JVM預約義的三種類加載器了:

  1. 啓動(Bootstrap)類加載器:引導類加載器是用 本地代碼實現的類加載器,它負責將 <JAVA_HOME>/lib下面的核心類庫 或 -Xbootclasspath選項指定的jar包等 虛擬機識別的類庫 加載到內存中。因爲引導類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用,因此 不容許直接經過引用進行操做。
  2. 擴展(Extension)類加載器:擴展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的,它負責將 <JAVA_HOME >/lib/ext或者由系統變量-Djava.ext.dir指定位置中的類庫 加載到內存中。開發者能夠直接使用標準擴展類加載器。
  3. 系統(System)類加載器:系統類加載器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的,它負責將 用戶類路徑(java -classpath或-Djava.class.path變量所指的目錄,即當前類所在路徑及其引用的第三方類庫的路徑,如第四節中的問題6所述)下的類庫 加載到內存中。開發者能夠直接使用系統類加載器。

除了以上列舉的三種類加載器,還有一種比較特殊的類型就是線程上下文類加載器,下面會講到,這裏很少說了。

雙親委派模型:JVM在加載類時默認採用的是雙親委派機制。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸 (本質上就是loadClass函數的遞歸調用)。所以,全部的加載請求最終都應該傳送到頂層的啓動類加載器中。若是父類加載器能夠完成這個類加載請求,就成功返回;只有當父類加載器沒法完成此加載請求時,子加載器纔會嘗試本身去加載。查看ClassLoader的源碼,對雙親委派模型會有更直觀的認識:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
           // 首先,檢查該類是否已經被加載,若是從JVM緩存中找到該類,則直接返回
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                // 遵循雙親委派的模型,首先會經過遞歸從父加載器開始找,
                 // 直到父類加載器是BootstrapClassLoader爲止
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 若是還找不到,嘗試經過findClass方法去尋找
                    // findClass是留給開發者本身實現的,也就是說
                    // 自定義類加載器時,重寫此方法便可
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

事實上,大多數狀況下,越基礎的類由越上層的加載器進行加載,這些基礎類之因此稱爲「基礎」,是由於它們老是做爲被用戶代碼調用的API(固然也存在基礎類回調用戶代碼的情形)。

雙親委派模型並不能解決全部的類加載器問題,好比,Java 提供了不少服務提供者接口(Service Provider Interface,SPI),容許第三方爲這些接口提供實現。常見的 SPI 有 JDBC、JNDI、JAXP 等,這些SPI的接口由核心類庫提供,卻由第三方實現,這樣就存在一個問題:SPI 的接口是 Java 核心庫的一部分,是由BootstrapClassLoader加載的;SPI實現的Java類通常是由AppClassLoader來加載的。BootstrapClassLoader是沒法找到 SPI 的實現類的,由於它只加載Java的核心庫。它也不能代理給AppClassLoader,由於它是最頂層的類加載器。也就是說,雙親委派模型並不能解決這個問題。

掃盲:SPI 全稱爲 Service Provider Interface,是JDK內置的服務發現機制(一種動態替換髮現的機制)。它經過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件裏所定義的類。這種機制爲不少框架擴展提供了可能,好比在Spring、Dubbo、JDBC中都使用到了SPI機制。固然不一樣的框架的實現方式會略有不一樣,可是原理都是同樣的:都是使用的反射機制。Spring中使用的類是SpringFactoriesLoader,在org.springframework.core.io.support包中,文件放在 META-INF/spring.factories。

線程上下文類加載器(ContextClassLoader)正好解決了這個問題。從名稱上看,可能會誤解爲它是一種新的類加載器,實際上,它僅僅是Thread類的一個變量而已,能夠經過setContextClassLoader(ClassLoader cl)和getContextClassLoader()來設置和獲取該對象。若是不作任何的設置,Java應用的線程的上下文類加載器默認就是AppClassLoader。在覈心類庫使用SPI接口時,傳遞的類加載器使用線程上下文類加載器,就能夠成功的加載到SPI實現的類。線程上下文類加載器在不少SPI的實現中都會用到。但在JDBC中,你可能會看到一種更直接的實現方式,好比,JDBC驅動管理java.sql.Driver中的loadInitialDrivers()方法中,你能夠直接看到JDK是如何加載驅動的:

for (String aDriver : driversList) {
    try {
        // 直接使用AppClassLoader
        Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
    } catch (Exception ex) {
        println("DriverManager.Initialize: load failed: " + ex);
    }
}

這裏講解線程上下文類加載器,主要是讓你們在看到Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()時不會一臉懵逼,這二者除了在許多底層框架中取得的ClassLoader可能會有所不一樣外,其餘大多數業務場景下都是同樣的,你們只要知道它是爲了解決什麼問題而存在的便可。

類加載器除了加載class外,還有一個很是重要功能,就是加載資源,它能夠從jar包中讀取任何資源文件,好比,ClassLoader.getResources(String name)方法就是用於讀取jar包中的資源文件,其代碼以下:

public Enumeration<URL> getResources(String name) throws IOException {
    Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
    if (parent != null) {
        tmp[0] = parent.getResources(name);
    } else {
        tmp[0] = getBootstrapResources(name);
    }
    tmp[1] = findResources(name);
    return new CompoundEnumeration<>(tmp);
}

是否是以爲有點眼熟,不錯,它的邏輯其實跟類加載的邏輯是同樣的,首先判斷父類加載器是否爲空,不爲空則委託父類加載器執行資源查找任務,直到BootstrapClassLoader,最後才輪到本身查找。而不一樣的類加載器負責掃描不一樣路徑下的jar包,就如同加載class同樣,最後會掃描全部的jar包,找到符合條件的資源文件。

類加載器的findResources(name)方法會遍歷其負責加載的全部jar包,找到jar包中名稱爲name的資源文件,這裏的資源能夠是任何文件,甚至是.class文件,好比下面的示例,用於尋找Array.class文件:

public static void main(String[] args) throws Exception{
    String name = "java/sql/Array.class";// Array.class的完整路徑
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        System.out.println(url.toString());
    }
}

運行後能夠獲得以下結果:
$JAVA_HOME/jre/lib/rt.jar!/java/sql/Array.class根據資源文件的URL,能夠構造相應的文件來讀取資源內容。

3.2 分析SpringFactoriesLoader

有了前面關於ClassLoader的知識,再來理解下面的代碼。

public abstract class SpringFactoriesLoader {

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

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        //根據當前接口類的全限定名做爲key,從loadFactoryNames從文件中獲取到全部實現類的全限定名
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<>(factoryNames.size());
        //實例化全部實現類,並保存到 result 中返回。
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

// spring.factories文件的格式爲:key=value1,value2,value3,從全部的jar包中找到META-INF/spring.factories文件,而後從文件中解析出key=factoryClass類名稱的全部value值
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
        // 取得資源文件的URL,熟悉嗎?上面的尋找Array.class文件
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
           //一Key多值 Map,適合上文提到的一個接口多個實現類的情形。
            result = new LinkedMultiValueMap<>();
            // 遍歷全部的URL
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                // 根據資源文件URL解析properties文件
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    //以逗號進行分割,獲得List的實現類全限定名集合
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                  // 組裝數據,並返回
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException(
                        "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            }
            return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
        }
    }

}

看上面的源碼可知SpringFactoriesLoader是一個抽象類,類中的靜態屬性FACTORIES_RESOURCE_LOCATION定義了其加載資源的路徑public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",此外還有三個靜態方法:

  • loadFactories:加載指定的factoryClass並進行實例化。
  • loadFactoryNames:加載指定的factoryClass的名稱集合。
  • instantiateFactory:對指定的factoryClass進行實例化。

loadFactories方法首先獲取類加載器,而後調用loadFactoryNames(EnableAutoConfiguration.class, classLoader)方法獲取全部的指定資源的名稱集合(一組@Configuration類),接着調用instantiateFactory方法經過反射實例化這些資源類並將其添加到result集合中。最後調用AnnotationAwareOrderComparator.sort方法進行集合的排序,而後注入到IOC容器中。最後容器裏就有了一系列標註了@Configuration的JavaConfig形式的配置類。

4 總結

本篇文章主要介紹了SpringBoot的自動配置原理,並擴展了JVM的類加載原理,文章中若存在分析不當之處還請各位讀者不吝指出,若這篇文章對你有用還請分享給更多的人。

相關文章
相關標籤/搜索