springboot SPI 实现自动配置

本文介绍Java中类加载器资源查找机制,包括ClassLoader的两种资源查找方式及其实现原理。同时,深入探讨SpringFactoriesLoader如何扫描资源并缓存结果,以及@EnableAutoConfiguration注解如何实现自动配置。
1. Java类加载器资源查找的过程

ClassLoader 类中资源查找有2种

  • 一种是 classLoader 对象的实例方法,在 classsLoader 对象的目录范围内查找
    // ClassLoader.java
    
    public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            // parent == null, 表示已经找到最顶级 BootStrapClassLoader 加载器
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);
    
        return new CompoundEnumeration<>(tmp);
    }
    
  • 另一种是 ClassLoader 类的静态方法,在 AppClassLoader 的范围,即 classPath 下进行查找
    // ClassLoader.java
    
    public static URL getSystemResource(String name) {
        ClassLoader system = getSystemClassLoader(); // 返回 AppClassLoader
        // parent == null, 表示已经找到最顶级 BootStrapClassLoader 加载器
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);
    }
    

两种查找资源的方法,都是先递归委托给 parent 的类加载器查找,最后在自己的加载范围内查找。和类加载时的双亲委派一样。

2. SpringFactoriesLoader 扫描资源

SpringFactoriesLoader 会使用 classLoader 对象扫描其可见域下的 spring.properties 实现类,并缓存所有 classLoader 对象和 Map<接口名, List[实现类名]> 的映射关系,其他类加载时,直接通过缓存获取接口的实现类名集合,并用 loadFactories() 反射实例化实现类集合

public final class SpringFactoriesLoader {
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	// interface MultiValueMap<K, V> extends Map<K, List<V>>
	// 全局缓存,key: classLoader 对象   
	//         value: Map <String,List<String>>  (key: 接口名, value: 实现类名的list )
	private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
 	// 
	public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// 找到接口名下配置的所有实现类类名
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
			// 对 class 反射实例化出对象
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
	
	// 找到接口名下配置的所有实现类类名, 返回 List<String>
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		// 调用 private 方法
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	/**
	  * 加载 classLoader 对象扫描域下,所有 spring.factpries 文件中配置的 <接口名,List[实现类名]>,放入 cache 中,
	  * 下次从 cache 中直接通过 classLoader 对象取 <接口名,List[实现类名]>
	  */
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			// ClassLoader 两种扫描资源的方法
			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);
				// 解析 properties 文件的每一行,加入 Map<String,List<String>> 中
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		// ...  
	}
	/**
	 * (1)用 classLoader 加载实现类
	 * (2)并校验实现类和配置的接口是否是实现关系
	 * (3)根据实现类名反射实例化对象,
	 */
	private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
		try {
			// (1)用 classLoader 加载实现类
			Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
			// (2)并校验实现类和配置的接口是否是实现关系
			if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
				throw new IllegalArgumentException(
						"Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
			}
			// (3)根据实现类名反射实例化对象,
			return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
		}
		// ...
	}
}
3. 实现自动配置的 @EnableAutoConfiguration 注解

@EnableAutoConfiguration 注解如下:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

AutoConfigurationImportSelector.class 该类的 selectImports() 方法使用 SpringFactoriesLoader 扫描并实例化 EnableAutoConfiguration.class 的实现类对象,所以 spring.factories 文件中配置的都是 EnableAutoConfiguration.class 的实现类。如 mybatis starter 的文件内容:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
			getBeanClassLoader());
	return configurations;
}
### 关于 Spring Boot 自动配置和起步依赖的面试题 #### 什么是 Spring Boot 的自动配置? Spring Boot 的自动配置是其核心特性之一,旨在简化开发过程。该机制允许应用程序在启动时自动配置大量常用的框架和组件,从而减少开发者手动编写配置的需求[^1]。 ```java @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } ``` 这段代码展示了如何创建一个简单的 Spring Boot 应用程序,在此过程中会触发自动配置功能。 #### 如何实现自动配置? 当 Spring Boot 启动时,会扫描项目中的 JAR 文件来查找包含 `spring.factories` 文件的资源路径下的类。这些文件定义了哪些自动配置类应该被加载以及它们的应用条件。通过使用 `@Conditional` 注解家族(如 `@ConditionalOnClass`, `@ConditionalOnMissingBean`),可以根据特定环境或存在与否的情况决定是否激活某些 Bean 或者整个配置类[^2]。 #### 什么叫做起步依赖 (Starter)? 起步依赖是指一组预先打包好的库集合,用于帮助开发者更方便地引入所需的技术栈到自己的项目当中去。每一个 starter 实际上就是一个 Maven/Gradle 描述符,里面声明了一组协调版本号的一致性依赖列表。这样做的好处是可以让使用者不必关心各个第三方库之间的兼容性和最佳实践组合问题。 例如: - **Web 开发**: 使用 `spring-boot-starter-web` 可以轻松获得 RESTful Web Services 所需的一切支持; - **数据访问层**: 对应不同的数据库可以选择相应的 starter,比如 MySQL 数据源可以通过添加 `spring-boot-starter-data-jpa` 加 `mysql:mysql-connector-java` 来完成设置; #### 示例:自定义自动配置并将其作为新的 Starter 发布 假设要构建一个新的自动化工具链——MyCustomTool,并希望它能像官方提供的 starters 那样易于集成,则需要做如下工作: 1. 创建名为 my-custom-tool-spring-boot-autoconfigure 的模块; 2. 在 resources/META-INF 下新建 spring.factories 文件; 3. 将新编写的 AutoConfiguration 类注册进去; 4. 编写具体的业务逻辑和服务提供者接口(SPI); 5. 测试验证一切正常之后发布至中央仓库供他人引用. ```xml <!-- pom.xml --> <dependency> <groupId>com.example</groupId> <artifactId>my-custom-tool-spring-boot-starter</artifactId> <version>${project.version}</version> </dependency> ``` 以上就是有关 Spring Boot 中自动配置与起步依赖的相关知识点介绍及其实际应用场景举例说明。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值