springboot SPI加载机制说明

SpringSPI的加载机制相信大家一定不会陌生,它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。但是,它和@Bean注解实现的Bean实例化有什么区别呢?今天我们就来说明下SpringSPI的实现原理。

Spring Factories 的实现原理

Spring SPI的实现依赖于SpringFactoriesLoader类的方法来实现
具体的实现类SpringFactoriesLoader方法如下:

/**
	 * 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
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	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 {
			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 properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

通过代码我们可以看到,加载spring.factories文件的方法是loadSpringFactories(@Nullable ClassLoader classLoader),它底层是通过PropertiesLoaderUtils来获取文件里的具体值并返回Properties对象。

这时,我们再debug网上看的时候,就会发现

/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) 
		
// 这里就是spring.factories文件的加载入口
		getSpringFactoriesInstances(ApplicationContextInitializer.class)
		);
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

参见上面代码,我们会发现,其实加载的入口是在Springboot的启动类入口方法里。
也就是说,springboot的加载还未实例化bean的情况下,就已经开始加载spring.factories文件了。

这也佐证了一点,spring.factories的加载顺序是先于@Bean的实例化的。

所以大家畅想下,我们经常依赖的 starter 组件,是不是会找到spring.factories文件,根据上面的原理,可以推理知道,为什么我只要依赖了 starter 组件就可以自动加载其中的服务了。

SPI(Service Provider Interface)是Java提供的一种服务发现机制,它允许第三方服务提供者在不需要修改代码的情况下,向程序动态地添加服务实现。 在Spring Boot中,SPI可以用来加载和卸载插件。Spring Boot提供了一个自动配置类`SpringFactoriesLoader`,它可以自动加载classpath下的`META-INF/spring.factories`文件,并根据其中定义的`EnableAutoConfiguration`类进行自动配置。 要实现SPI加载和卸载,可以按照以下步骤进行: 1. 定义接口和接口实现类 定义一个接口,例如: ```java public interface Plugin { void doSomething(); } ``` 定义一个或多个实现类,例如: ```java public class PluginA implements Plugin { @Override public void doSomething() { System.out.println("Plugin A is doing something."); } } public class PluginB implements Plugin { @Override public void doSomething() { System.out.println("Plugin B is doing something."); } } ``` 2. 创建`META-INF/services`目录 在classpath下创建`META-INF/services`目录。 3. 创建服务提供者配置文件 在`META-INF/services`目录下创建一个以接口全限定名为文件名的文件,例如: ``` com.example.Plugin ``` 在文件中写入实现类的全限定名,例如: ``` com.example.PluginA com.example.PluginB ``` 4. 加载插件 使用`ServiceLoader`类加载插件,例如: ```java ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class); for (Plugin plugin : plugins) { plugin.doSomething(); } ``` 5. 卸载插件 可以将实现类从服务提供者配置文件中删除,再重新加载插件即可完成卸载。或者在程序中对实现类进行过滤,例如: ```java ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class); for (Plugin plugin : plugins) { if (plugin instanceof PluginA) { continue; } plugin.doSomething(); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值