spring的SpringFactoriesLoader是spring框架内部工具类,在 Spring boot 应用启动的过程中,这个类的工作很重要, 启动逻辑使用该类从classpath上所有jar包中找出各自的 META-INF/spring.factories 属性文件,并分析出其中定义的工厂类。这些工厂类进而被启动逻辑使用,应用于进一步初始化工作。其使用的方式和java的spi基本类似,我们可以先学习java的spi从而进一步学习SpringFactoriesLoader。
一、java spi
1、什么是Spi
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。
2、Spi的应用场景
SPI (Service Provider Interface)
是调用方
来制定接口规范,提供给外部来实现,调用方在调用时则
选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
3、Spi的简单demo
(1)、定义一个接口
package com.linzhe.springbootdemo.analysis.d1;
public interface Upload {
void upload();
}
(2)、接口实现类
package com.linzhe.springbootdemo.analysis.d1;
public class BduUploadImpl implements Upload {
@Override
public void upload(){
System.out.println("上传到百度云");
}
}
public class AliUploadImpl implements Upload {
@Override
public void upload() {
System.out.println("上传到阿里oss");
}
}
(3)、META-INF/services下创建接口全限定名文件
META-INF/services/com.linzhe.springbootdemo.analysis.d1.Upload
com.linzhe.springbootdemo.analysis.d1.AliUploadImpl
com.linzhe.springbootdemo.analysis.d1.BduUploadImpl
(4)、测试类
public class spidemo {
public static void main(String[] args) {
ServiceLoader<Upload> uploads = ServiceLoader.load(Upload.class);
for (Upload upload :uploads){
upload.upload();
}
}
}
(4)输出
上传到阿里oss
上传到百度云
Process finished with exit code 0
4、Spi源码分析
public final class ServiceLoader<S> implements Iterable<S> {
//扫描目录前缀
private static final String PREFIX = "META-INF/services/";
// 被加载的类或接口
private final Class<S> service;
// 用于定位、加载和实例化实现方实现的类的类加载器
private final ClassLoader loader;
// 上下文对象
private final AccessControlContext acc;
// 按照实例化的顺序缓存已经实例化的类
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 懒加载迭代器
private java.util.ServiceLoader.LazyIterator lookupIterator;
// 私有内部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
String nextName = null;
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//....
reload();
}
public void reload() {
// 清空providers
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
//...
private boolean hasNextService() {
if (configs == null) {
try {
//获取目录下所有的类
//"META-INF/services/"+service的全限定类名
String fullName = PREFIX + service.getName();
if (loader == null)
//加载 META-INF/services/"+service里的所有全限定类名
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
//...
}
//....
}
}
private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
//..
}
//..
}
}
}
上面的代码只贴出了部分关键的实现,下面贴出比较直观的spi加载的主要流程供参考:
二、SpringFactoriesLoader
1、什么是SpringFactoriesLoader
Spring的SpringFactoriesLoader工厂的加载机制类似java提供的SPI机制一样,是Spring提供的一种加载方式。只需要在classpath路径下新建一个文件META-INF/spring.factories,并在里面按照Properties格式填写好接口和实现类即可通过SpringFactoriesLoader来实例化相应的Bean。其中key可以是接口、注解、或者抽象类的全限定名。value为相应的实现类的全限定名,当存在多个实现类时,用“,”进行分割,换行用""。
2、SpringFactoriesLoader的应用场景
基本上和spi一样。在springboot中最经典的表现为springboot的自动配置处理类 AutoConfigurationImportSelector
的getCandidateConfigurations方法中获取所有以org.springframework.boot.autoconfigure.EnableAutoConfiguration
为key的value值。
3、简单demo
接口和实现类用上面spi的就可以了。
1、创建META-INF/spring.factories文件
文件内容:
com.linzhe.springbootdemo.analysis.d1.Upload=
com.linzhe.springbootdemo.analysis.d1.BduUploadImpl,
com.linzhe.springbootdemo.analysis.d1.AliUploadImpl
2、demo类
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
System.out.println("SpringFactoriesLoader加载类");
// 获取实现类的全限定类名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(Upload.class, SpringFactoriesLoaderDemo.class.getClassLoader()));
//遍历
for (String name : names){
// 反射获取对象
Class<?> instanceClass = ClassUtils.forName(name, SpringFactoriesLoaderDemo.class.getClassLoader());
Constructor<?> constructor = instanceClass.getDeclaredConstructor();
Upload instance = (Upload) BeanUtils.instantiateClass(constructor);
// 调用upload方法
instance.upload();
}
}
4、源码分析
General purpose factory loading mechanism for internal use within the framework.
SpringFactoriesLoader loads and inst antiates factories of a given type from "META-INF/spring.factories" files which may be present in multiple JAR files in the classpath. The spring.factories file must be in 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:
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
where example.MyService is the name of the interface, and MyServiceImpl1 and MyServiceImpl2 are two implementations.
中文翻译:
spring框架内部使用的通用工厂加载机制。
SpringFactoriesLoader从“META-INF”加载并实例化给定类型的工厂/spring工厂“类路径中可能存在于多个JAR文件中的文件。这个spring工厂文件必须采用属性格式,其中键是接口或抽象类的完全限定名,值是以逗号分隔的实现类名列表。
public final class SpringFactoriesLoader {
//目录前缀
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//存放类加载器:全限定名称的类
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
..................
// 根据类名称获取对应的全限类名
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) {
// 获取classLoader
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 获取"META-INF/spring.factories"文件(resource/META-INF/spring.factories)
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 获取限定类名,封装成classLoader: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());
}
}
}
// 存入缓存的map中
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
}
下面是流程图
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。