二、spring.factories文件

1、spring.factories介绍

spring.factories 是 Spring 框架中的一个关键配置文件,通常位于类路径下的META-INF目录中。它的主要功能是提供一种自动装配机制,用于在应用启动时自动加载指定的类。通过spring.factories文件,开发者可以将特定的配置类、监听器、过滤器等组件注册到Spring上下文中。

文件格式

  • 通常以键值对的形式表示
  • 键为接口或抽象类的全限定名
  • 值为实现类的全限定名,多个类可以用逗号分隔

示例

  • 使用\表示续行符,用来将长行分成多行写
# 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MyAutoConfiguration1,\
com.example.config.MyAutoConfiguration2

# 自定义监听器
org.springframework.context.ApplicationListener=\
com.example.listener.MyApplicationListener

2、读取spring.factories文件

// 2. 读取spring.factories文件
// 2.1 查找并实例化BootstrapRegistryInitializer类型的工厂类
this.bootstrapRegistryInitializers = new ArrayList<>(
		getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 2.2 查找上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 2.3 查找应用监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  • 用于从所有依赖的JAR文件的META-INF/spring.factories文件中加载指定类型的多个工厂类名称
  • 通过Class.forName反射获取实例化多个对象,并根据对象上@Order注解或Ordered接口排序
// SpringApplication类方法
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	// 第二个参数为构造器的参数类型集合
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = getClassLoader();
	// 用于从所有依赖的 JAR 文件的 META-INF/spring.factories 文件中加载指定类型的工厂类名称
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 根据权限定类名字符串,Class.forName反射实例化对象
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 它能够根据@Order注解和Ordered接口的优先级来对对象进行排序,从而决定对象的执行顺序
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

2.1、加载factories文件原理

// SpringFactoriesLoader类方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
	}
	String factoryTypeName = factoryType.getName();
	// 加载spring.factories文件,获取Map,通过key获取多个实现
	return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

// 读取spring.factories文件的缓存结果
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

// 从 `META-INF/spring.factories` 资源中加载并缓存 Spring 工厂。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 首先检查缓存中是否已经存在该 classLoader 对应的工厂列表
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
        return result; // 如果缓存中存在,则直接返回
    }

    // 初始化一个空的 Map 用于存储结果
    result = new HashMap<>();
    try {
        // 获取资源位置(META-INF/spring.factories)的所有 URL
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            // 遍历每个 URL 并从资源文件加载属性
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);

            // 从当前资源文件加载属性
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);

            // 遍历每个属性条目(工厂类型及其实现)
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 键为工厂类型名称
                String factoryTypeName = ((String) entry.getKey()).trim();

                // 值为逗号分隔的工厂实现名称字符串
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());

                // 将每个工厂实现添加到结果 Map 中对应的工厂类型下
                for (String factoryImplementationName : factoryImplementationNames) {
                    // 去除空格并添加每个工厂实现名称到该工厂类型的列表中
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // 确保所有列表都是不可修改的且包含唯一的元素
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));

        // 将结果缓存到缓存中以便下次复用
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        // 如果加载资源时遇到 IOException,抛出 IllegalArgumentException 异常
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result; // 返回加载的结果
}

PropertiesLoaderUtils.loadProperties(resource)资源加载属性原理

springBoot源码解析:SpringApplication构造方法(二) _java

  • 从LineReader中加载键值对,并存储在当前对象中
// 从 LineReader 中加载键值对,并存储在当前对象中
private void load0(LineReader lr) throws IOException {
    // 用于字符转换的缓冲区
    char[] convtBuf = new char[1024];
    int limit;
    int keyLen;
    int valueStart;
    char c;
    boolean hasSep;
    boolean precedingBackslash;

    // 逐行读取,直到没有更多行
    while ((limit = lr.readLine()) >= 0) {
        c = 0;
        keyLen = 0;
        valueStart = limit;
        hasSep = false;

        // precedingBackslash 表示当前字符前是否有反斜杠
        precedingBackslash = false;

        // 遍历当前行的每个字符,识别键的长度(keyLen)和值的起始位置(valueStart)
        while (keyLen < limit) {
            c = lr.lineBuf[keyLen];
            // 检查字符是否为键值分隔符(= 或 :),并且没有被转义
            if ((c == '=' || c == ':') && !precedingBackslash) {
                valueStart = keyLen + 1;
                hasSep = true; // 标记有分隔符
                break;
            } 
            // 如果字符为空格、制表符或换页符,并且没有被转义,则认为是值的开始
            else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
                valueStart = keyLen + 1;
                break;
            }
            // 检查是否是反斜杠,并处理转义状态
            if (c == '\\') {
                precedingBackslash = !precedingBackslash;
            } else {
                precedingBackslash = false;
            }
            keyLen++;
        }

        // 跳过值前面的空格、制表符和换页符
        while (valueStart < limit) {
            c = lr.lineBuf[valueStart];
            if (c != ' ' && c != '\t' && c != '\f') {
                // 如果没有分隔符,并且遇到 '=' 或 ':',则标记为有分隔符
                if (!hasSep && (c == '=' || c == ':')) {
                    hasSep = true;
                } else {
                    break;
                }
            }
            valueStart++;
        }

        // 使用 loadConvert 方法将键和值部分的字符转换为字符串
        String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
        String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);

        // 将键值对存入当前对象的属性中
        put(key, value);
    }
}

2.2、三个spring.factories文件路径

  • spring-boot-2.7.18.jar

springBoot源码解析:SpringApplication构造方法(二) _spring_02

  • spring-boot-autoconfigure-2.7.18.jar

springBoot源码解析:SpringApplication构造方法(二) _加载_03

  • spring-beans-5.3.31.jar

springBoot源码解析:SpringApplication构造方法(二) _java_04

查询引导注册组件初始化器、上下文初始化器、应用监听器就是从以上三个spring.factories文件中获取BootstrapRegistryInitializerApplicationContextInitializerApplicationListener这三个接口的实现类。

2.3、引导注册组件初始化器BootstrapRegistryInitializer

BootstrapRegistryInitializer在ApplicationContext创建之前对注册表进行配置,并注册一些启动时的关键组件。它主要应用于SpringCloud的场景中,用来初始化那些在应用上下文加载之前需要配置的组件,比如配置中心服务注册和发现等。

从以上三个jar的spring.factories文件没有获取到初始化器,表示这里也没用到它。

2.4、上下文初始化器ApplicationContextInitializer

ApplicationContextInitializer主要作用是在Spring应用上下文 (ApplicationContext) 刷新之前进行自定义的初始化操作。它允许开发者在应用上下文完全启动和加载所有Bean定义之前进行特定的配置和设置。

从以上三个jar的spring.factories文件获取到7个上下文初始化器,前5个来自spring-boot-2.7.18.jar,最后2个来自spring-boot-autoconfigure-2.7.18.jar。

2.5、应用监听器ApplicationListener

ApplicationListener作用是监听Spring框架中内置的各种事件(如上下文刷新事件、上下文关闭事件等),也可以监听自定义的事件。基于事件触发执行逻辑,常用于对生命周期事件的监听以及自定义事件的处理,帮助实现松耦合的事件驱动架构。