Spring定义Bean类(四) 自动装配 spring.factories

本文介绍Spring中关于自动装配META-INF/spring.factories文件的使用

  • 使用方式
  • 实现原理
  • 内容延伸
  • 总结

使用方式

在SpringBoot中,有一种自动装配的过程,是通过在文件classpath:/META-INF/spring.factories 中添加配置org.springframework.boot.autoconfigure.EnableAutoConfiguration 的方式来配置Bean,下面配置自定义Bean com.ethan.diveinspringboot.bean.HelloWorld 类,

package com.ethan.diveinspringboot.bean;

public class HelloWorld {

    public void test() {
        System.out.println("hello world");
    }

}

resources 资源文件夹下新建文件META-INF/spring.factories 文件,内容如下:

# 自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ethan.diveinspringboot.bean.HelloWorld

添加测试类,并在配置类上添加注解@EnableAutoConfiguration

@EnableAutoConfiguration
public class EnableAutoConfigurationBootstrap {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        HelloWorld helloWorld = context.getBean(HelloWorld.class);
        helloWorld.test();
        // 关闭上下文
        context.close();

    }
}

测试结论得出com.ethan.diveinspringboot.bean.HelloWorld 类被成功注册成为Bean。

实现原理

关于自动装配META-INF/spring.factories 配置文件的实现原理,可以从两个方面入手

  1. META-INF/spring.factories 目录
  2. @EnableAutoConfiguration 的使用

META-INF/spring.factories文件

先看META-INF/spring.factories 文件是从哪里开始使用的,通过查找,发现在类org.springframework.core.io.support.SpringFactoriesLoader#FACTORIES_RESOURCE_LOCATION 中定义

/**
 * General purpose factory loading mechanism for internal use within the framework.
 *
 * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
 * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
 * may be present in multiple JAR files in the classpath. The {@code spring.factories}
 * file must be in {@link Properties} format, where the key is the fully qualified
 * name of the interface or abstract class, and the value is a comma-separated list of
 * implementation class names. For example:
 *
 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
 *
 * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
 * and {@code MyServiceImpl2} are two implementations.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.2
 */
public abstract class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	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<>();
}

以上是SpringFactoriesLoader 抽象类的部分代码,首先定义资源文件META-INF/spring.factories ,最后一个属性cache 类型为Map<ClassLoader, MultiValueMap<String, String>>ClassLoader 代表当前加载类的加载器,MultiValueMap<String, String>> 表示这个ClassLoader 加载器加载的类的集合。而MultiValueMap 集合的value 可以存储多个值,而非一个值,所以cache 的存储的结构应该为:

Map<类加载器,多值集合<键,多值集合>>, 这样的结构

cache 属性在方法org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories 中被初始化

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 从缓存中获取类加载加载的键值集合
    MultiValueMap<String, String> result = cache.get(classLoader);
    // 若缓存cache中存在,则直接返回
    if (result != null) {
        return result;
    }

    try {
        // 获取所有资源文件META-INF/spring.factories中
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        // 初始化多值集合
        result = new LinkedMultiValueMap<>();
        // 遍历资源文件
        while (urls.hasMoreElements()) {
            // 获取资源文件地址
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 将资源文件META-INF/spring.factories 文件读取成为Properties类
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            // 读取Properties实例,将相同键的集合,放入result中
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                List<String> factoryClassNames = Arrays.asList(
                    StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        // 将读取的文件,放入到cache中,并返回结果
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

根据注释内容,完整读取了META-INF/spring.factories 中的内容。

EnableAutoConfiguration 注解的使用

在Spring Enable注解装配中,我们曾分析过@Enable* 的实现原理,那么我们直接看EnableAutoConfiguration 功能实现的类org.springframework.boot.autoconfigure.AutoConfigurationImportSelector , AutoConfigurationImportSelector 依赖于方法AutoConfigurationImportSelector#selectImports 实现,方法如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取候选配置类,将准备加载成为Bean的类添加到configurations集合中
    List<String> configurations = getCandidateConfigurations(annotationMetadata,
                                                             attributes);
    // 移除重复的类
    configurations = removeDuplicates(configurations);
    // 根据类的配置,获取需要排除的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 根据AutoConfigurationImportFilter 过滤类,过滤掉无需加载的类
    configurations = filter(configurations, autoConfigurationMetadata);
    // 触发自动导入事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回待加载的Bean的字符串数组
    return StringUtils.toStringArray(configurations);
}

从第9行代码,获取配置类字符串集合,进一步分析getCandidateConfigurations 方法

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations,
                    "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

这里看第3行代码,可以关联到上小结提到的SpringFactoriesLoader 类,类SpringFactoriesLoader#loadFactoryNames 方法定义如下:

/**
  * Load the fully qualified class names of factory implementations of the
  * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
  * class loader.
  * @param factoryClass the interface or abstract class representing the factory
  * @param classLoader the ClassLoader to use for loading resources; can be
  * {@code null} to use the default
  * @see #loadFactories
  * @throws IllegalArgumentException if an error occurs while loading factory names
  */
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

此方法将获取所有spring.factories 文件中配置的键为factoryClass.getName 的集合。

回到AutoConfigurationImportSelector#getCandidateConfigurations 方法,可以看出,这个方法将从spring.factories 文件中获取所有的键为org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值集合,然后通过后期的判断,加载成为Spring容器的Bean。

小结:META-INF/spring.factories 文件在SpringFactoriesLoader 类中定义,可以加载所有spring.factories 文件的键为org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值为Spring容器管理的Bean。这种方式,模仿了Java SPI技术。

内容延伸

spring-boot-autoconfigure-x.x.x.RELEASE.jar 中的spring.factories 中有各种key的使用。

# 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

# Auto Configuration Import Filters
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,\
.......

# 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

这些Key,都在不同的场景中使用,比如org.springframework.context.ApplicationContextInitializer 键的所有值,必须为接口org.springframework.context.ApplicationContextInitializer 的实现,并且在SpringApplication 初始化时,调用ApplicationContextInitializer#initialize(C applicationContext) 方法。这种方式,模仿了Java SPI技术。

总结

  1. 可以通过在META-INF/spring.factories文件中添加org.springframework.boot.autoconfigure.EnableAutoConfiguration 键,添加多个类的全名,然后在启动类上添加@EnableAutoConfiguration 注解来注册Bean类。
  2. META-INF/spring.factories 文件在SpringFactoriesLoader 中定义,内容被加载到cache 属性中。
  3. @EnableAutoConfiguration 注解的实现,调用SpringFactoriesLoader#loadSpringFactories 方法读取键为org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有值。
  4. SpringFactoriesLoader 类原理,仿照JAVA SPI技术实现。

版权声明:本文为w13485673086原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。