您当前的位置: 首页 >  spring

java持续实践

暂无认证

  • 4浏览

    0关注

    746博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

SpringBoot 2.x SpringFactoriesLoader工厂加载机制解析

java持续实践 发布时间:2021-06-13 17:46:43 ,浏览量:4

文章目录
      • SpringFactoriesLoader 作用介绍
      • 源码解析
      • SpringFactoriesLoader的流程图

SpringFactoriesLoader 作用介绍

其源码注释如下

General purpose factory loading mechanism for internal use within the framework. SpringFactoriesLoader loads and instantiates 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.

翻译成中文就是如下四点特性

  1. 框架内部使用的通用工厂加载机制
  2. classpath下多个jar包META-INF/spring.factories中读取文件并初始化类
  3. 文件的内容必须是kv形式, 即必须满足Properties 格式
  4. 文件的内容key是全限定名(接口或抽象类), value是接口或抽象类的实现, 多个实现用逗号进行分割.
源码解析

SpringBoot启动分为框架运行, 框架初始化, 框架启动, 自动化装配. SpringFactoriesLoader工厂加载是在如下图的框架初始化的ApplicationContextInitializer中进行的. 在这里插入图片描述 SpringBoot启动 框架初始化即SpringApplication的构造方法如下, 系统初始化调用了setInitializers方法. 并调用了getSpringFactoriesInstances方法,

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) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

getSpringFactoriesInstances方法调用了如下的getSpringFactoriesInstances同名方法, 方法的重载

private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

上面的方法中, 调用了SpringFactoriesLoader.loadFactoryNames方法, 该方法如下:

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

该方法调用了loadSpringFactories方法如下

private static Map loadSpringFactories(@Nullable ClassLoader classLoader) {
       // 先从缓存中去拿数据, 如果拿到了不为null ,那么直接返回. 
		MultiValueMap result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration 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 factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

上面的方法中, 有如下的这行代码.

Enumeration urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

其中FACTORIES_RESOURCE_LOCATION 的值为如下 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 即classLoader类加载器会去从META-INF/spring.factories 中获取要加载的类资源

通过如下的断点可以看到, 加载到了META-INF/spring.factories 的绝对路径为file:/D:/mycode/my-springboot2-study/target/classes/META-INF/spring.factories 并且构建了Properties对象, 其对象的内容为spring.factories文件中写的键值对. 逐个遍历spring.factories文件中的key value. value值如果是用逗号分隔的, 那么就Spring提供的工具类StringUtils.commaDelimitedListToStringArray 去进行分隔, 分隔完成后, 把接口或抽象类作为key, 所有的实现类作为value, 放入result 缓存中去, 下次拿的时候, 直接从缓存中获取. 在这里插入图片描述 再次进行遍历 , 可以看到会从jar包中jar:file:/D:/developsoft/jarslib/org/springframework/boot/spring-boot/2.3.12.RELEASE/spring-boot-2.3.12.RELEASE.jar!/META-INF/spring.factories去 获取spring.factories 加载资源. 从jar包中读取如下的类资源 最终的result的值如下 , 可以看到org.springframework.context.ApplicationContextInitializer 的实现类中有我们在配置文件中写的com.thc.myspringboot2study.initializer.FirstInitializer 在这里插入图片描述 上一步return了result之后, 调用如下的这行方法 return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); 其中getOrDefault 方法为map接口的方法, 该方法的含义是第一个参数为key, 第二个参数为默认值. 如果该key在map中存在, 则返回该key对应的value, 如果不存在, 则返回一个空集合. 断点获取如下的接口, 即org.springframework.context.ApplicationContextInitializer 的key是存在的, 即它有如下的这些实现类. 返回到getSpringFactoriesInstances , 可以看到获取的names, 就是接口实现的类名. 接着在getSpringFactoriesInstances方法中调用了下面一行 createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names)方法, 该方法是通过反射去创建实例对象. createSpringFactoriesInstances 方法如下:

  1. 创建实现子类大小容量的集合
  2. 循环遍历这些实现类, 依次通过反射去创建实例
  3. 把创建的实例, 加入到集合中去.
private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set names) {
			// 创建实现子类大小容量的集合 
		List instances = new ArrayList(names.size());
		// 循环遍历这些实现类, 依次通过反射去创建实例 
		for (String name : names) {
			try {
				Class instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				// 把创建的实例, 加入到集合中去. 
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

创建完实例之后, 调用AnnotationAwareOrderComparator.sort(instances) 方法去进行排序. 可以看到它是基于OrderComparator 去排序的. 在这里插入图片描述 经历完上面的步骤之后, 再回到SpringApplication的构造方法, 调用setInitializers方法

在这里插入图片描述 setInitializers方法 如下, 把ApplicationContextInitializer的实现类 加入到了spring容器当中. 在这里插入图片描述

SpringFactoriesLoader的流程图
  • SpringBoot框架中从类路径jar包(各个jar包)中读取特定文件 spring.factories实现扩展类的载入

loadSpringFactories方法如下

private static Map loadSpringFactories(@Nullable ClassLoader classLoader) {
       // 先从缓存中去拿数据, 如果拿到了不为null ,那么直接返回. 
		MultiValueMap result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration 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 factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

其流程图如下: 图片在线浏览 https://www.processon.com/view/link/60c5e1ba0e3e7468f4cf7d6f 在这里插入图片描述

关注
打赏
1658054974
查看更多评论
立即登录/注册

微信扫码登录

0.2085s