6

1. 简介

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

号外:当面试管问你SpringBoot和Spring区别时?只回答简化了配置,内置了tomcat等可以吗? 远远不行,最重要的还是自动化配置。所以这篇文章就出现了。

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

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


模范青蛙
65 声望15 粉丝

大道无情,唯变化永恒