乐趣区

SpringBoot源码自动配置原理

1. 简介

本篇文章主要是针对上一篇文章:启动原理 的补充,在上一篇文章的 @SpringBootApplication 注解分析中,对于 @EnableAutoConfiguration 的阐述意犹未尽,但限于篇幅与文章主题规划,就拿到这里做详细说明了。
重要声明:本系列 SpringBoot 源码的分析都是针对 2.0.1.RELEASE 版本!!!

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 容器管理起来。

2.2.1 解惑

大家平时看博客之类相关文章可能发现大多数在讲解这部分的内容时使用的还是 @Import(EnableAutoConfigurationImportSelector.class),这是版本升级导致的,EnableAutoConfigurationImportSelector 继承自 AutoConfigurationImportSelector, 从 spring boot 1.5 以后,EnableAutoConfigurationImportSelector 已经不再被建议使用,而是推荐使用 AutoConfigurationImportSelector。因此咱们也顺应版本发展之潮流,开启源码解析新篇章。

2.2.2 导火线

上一篇文章中简要提到了 @EnableAutoConfiguration 可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建的 IoC 容器中。本篇文件就以此为入口详细分析其内部运作原理。

2.2.3 原理

Springboot 应用启动过程中使用 Spring 的工具类 ConfigurationClassParser(这个工具类信息量比较大,将在后续文章中详细阐述)分析 @Configuration 注解的配置类(分析过程主要是递归分析配置类的注解 @Import),产生一组 ConfigurationClass 对象。如果发现注解中存在 @Import,就创建一个相应的 ImportSelector 对象,并调用这个对象的 selectImports(AnnotationMetadata annotationMetadata)方法, 上面例子中 AutoConfigurationImportSelector 的导入 @Import(AutoConfigurationImportSelector.class) 就属于这种情况。所以 ConfigurationClassParser 会实例化一个 AutoConfigurationImportSelector 对象并调用它的 selectImports() 方法。既然现在聚光灯打在了 AutoConfigurationImportSelector 脸上,那就顺势端出它的源码看一下吧。

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
下面分别对三类接口简要介绍。

  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,因为它是最顶层的类加载器。也就是说,双亲委派模型并不能解决这个问题。

线程上下文类加载器 (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 的类加载原理,文章中若存在分析不当之处还请各位读者不吝指出,若这篇文章对你有用还请分享给更多的人。

退出移动版