springboot源码分析4-springboot之SpringFactoriesLoader使用

简介: 摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用。

摘要:本文我们重点分析一下Spring框架中的SpringFactoriesLoader类以及META-INF/spring.factories的使用。在详细分析之前,我们可以思考一个问题?在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?其一:我们可以规范不同的系统调用,也就是传递一个系统标识;其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理;其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一个功能的实现,A实现类与B实现类的逻辑一点也不一样,但是目标是一样的,这个时候使用策略类是毋庸置疑的?上述的问题我们是在API层面进行处理的?那万一有一天让我们自己设计一套框架,然后让别人直接使用?我们该如何处理上述的这个问题呢?这个可能就涉及到SPI的一些规范了跟技巧了,比如同一套API可能有很多实现类,这个时候我们该如何内置一系列实现类供框架使用呢?或者让用户也可以自定义这些API的实现类,相互之间协作运转。带着这些问题我们看一下Spring框架中的SpringFactoriesLoader以及META-INF/spring.factories的使用。

1.1 属性配置

首先,我们来看一下属性的配置方式,在传统的开发模式中(无springboot),属性文件的格式无外乎就是两种,第一种是XML,第二种是key、value形式(properties文件)。当然springboot引入了yaml方式。这里我们重点看一下XML以及properties的定义以及获取方式。

1.1.1 properties方式

1.1.1.1. 单个属性配置

首先,我们新建一个shareniu-single.factories文件,该文件的目录结构如下图所示:

                     

shareniu-single.factories的内容如下:

shareniu=http://www.shareniu.com/

1.1.1.2. 多个属性配置

单个属性的定义比较简单,就是key、value形式即可。对于同一个属性有多个值的定义格式如下:

com.example.demo.ch3.IShareniu=\

com.example.demo.ch3.ShareniuA,\

com.example.demo.ch3.ShareniuB

 

1.1.1.3. properties读取工具类

上述的属性定义完毕之后,我们写一个工具类进行测试,在这里我们直接调用了PropertiesLoaderUtils类中的方法。实例代码如下:

1 public class PropertiesLoaderUtilsTest {

2  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/shareniu-single.factories";

3  public static void main(String[] args) throws IOException {

4  Properties properties = PropertiesLoaderUtils.loadAllProperties(FACTORIES_RESOURCE_LOCATION,null);

5  System.out.println(properties);

6  }

7 }

上述的代码直接调用了PropertiesLoaderUtils类中的loadAllProperties方法,PropertiesLoaderUtils的全路径名称为:org.springframework.core.io.support.PropertiesLoaderUtils。该类位于spring-core-5.0.0.RC3.jar包中。

运行上面的代码,程序的输出如下:

{shareniu=http://www.shareniu.com/, com.example.demo.ch3.IShareniu=com.example.demo.ch3.ShareniuA,com.example.demo.ch3.ShareniuB}

果真我们自定义的属性都可以完美的获取到。

关于PropertiesLoaderUtils.loadAllProperties的核心代码如下:

1 public static Properties loadAllProperties(String resourceName, @Nullable ClassLoader classLoader) throws IOException {

2  ClassLoader classLoaderToUse = classLoader;

3  if (classLoaderToUse == null) {

4  classLoaderToUse = ClassUtils.getDefaultClassLoader();

5  }

6  Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :

7  ClassLoader.getSystemResources(resourceName));

8  Properties props = new Properties();

9  while (urls.hasMoreElements()) {

10  URL url = urls.nextElement();

11  URLConnection con = url.openConnection();

12  ResourceUtils.useCachesIfNecessary(con);

13  InputStream is = con.getInputStream();

14  try {

15  if (resourceName.endsWith(XML_FILE_EXTENSION)) {

16  props.loadFromXML(is);

17  }

18  else {

19  props.load(is);

20  }

21  }

22  finally {

23  is.close();

24  }

25  }

26  return props;

27  }

loadAllProperties方法,首先会根据类加载器去获取指定的资源(也就是我们调用的时候,传递的resourceName参数值)。然后判断资源的后缀是否为xml,如果后缀是xml则使用xml方式加载资源,否则都是用Properties方式进行资源的加载。

注意:虽然上述的代码我们指定的资源名称是:META-INF/shareniu-single.factories,但是上述的类加载器不仅扫描我们项目的META-INF/shareniu-single.factories,还会扫描当前类加载所加载的jar包中的META-INF/shareniu-single.factories文件。

1.1.2 xml方式

了解了上述代码的处理逻辑之后,我们看一下xml方式如何定义,shareniu.xml定义内容如下:

1 <?xml version="1.0" encoding="UTF-8"?>  

2 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">    

3 <properties>   

4     <comment>shareniu xml配置文件</comment>  

5     <entry key="username">shareniu</entry>  

6     <entry key="url">http://www.shareniu.com/</entry>   

7 </properties>

运行上述的属性工具类,控制台的输出信息如下:

{url=http://www.shareniu.com/, username=shareniu}

1.2 SpringFactoriesLoader使用

了解了XML以及properties的定义以及获取方式之后,接下来学习SpringFactoriesLoader类就简单的多了。

首先,看一下SpringFactoriesLoader类定义的方法如下所示:

                     

 

1. loadFactoryNames:加载指定的factoryClass并进行实例化。

2. loadSpringFactories:加载指定的factoryClass。

3. instantiateFactory:对指定的factoryClass进行实例化。

    通过上文可知:loadFactoryNames方法内部直接调用loadSpringFactories方法,loadSpringFactories方法则会调用instantiateFactory方法。

    loadSpringFactories方法内部会加载META-INF/spring.factories文件,这里加载的文件不仅包含项目中的,还包换我们项目环境所依赖的jar包中的META-INF/spring.factories文件。

1.现在,我们写一个简单的测试类,加载spring.factories文件,实例代码如下:

spring.factories文件的内容如下所示:

com.example.demo.ch3.IShareniu=\

com.example.demo.ch3.ShareniuA,\

com.example.demo.ch3.ShareniuB

   其中:IShareniu为接口,ShareniuA以及ShareniuB实现了IShareniu接口。结构如下图所示:

                           

2.自定义测试类并调用SpringFactoriesLoader类中的相关方法,如下所示:

1 public class DemoApplication {

2  public static void main(String[] args) {

3  List<String> loadFactoryNames = SpringFactoriesLoader.loadFactoryNames(IShareniu.class, null);

4  System.out.println(loadFactoryNames);

5  }

6 }

    自行上述代码,程序的输出信息如下:

[com.example.demo.ch3.ShareniuA, com.example.demo.ch3.ShareniuB]

通过上述的代码可知,我们确实完成了自身项目中META-INF/spring.factories文件的属性读取。

那我们能否能够通过Spring框架实例化这些类呢?答案是肯定的?实例代码如下:

1 List<IShareniu> loadFactories = SpringFactoriesLoader.loadFactories(IShareniu.class, null);

2  System.out.println(loadFactories);

   自行上述代码,程序的输出信息如下:

[com.example.demo.ch3.ShareniuA@53fd30, com.example.demo.ch3.ShareniuB@cbc42f]

loadFactories方法返回的已经是实例化完毕的对象了。

1.3 SpringFactoriesLoader原理

接下来,我们看一下SpringFactoriesLoader类中的loadFactories方法,如下所示:

1 public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {

2  Assert.notNull(factoryClass, "'factoryClass' must not be null");

3  ClassLoader classLoaderToUse = classLoader;

4  if (classLoaderToUse == null) {

5  classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();

6  }

7  List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);

8  if (logger.isTraceEnabled()) {

9  logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);

10  }

11  List<T> result = new ArrayList<>(factoryNames.size());

12  for (String factoryName : factoryNames) {

13  result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));

14  }

15  AnnotationAwareOrderComparator.sort(result);

16  return result;

17  }

loadFactories方法首先获取类加载器,然后调用loadFactoryNames方法获取所有的制定资源的名称集合、其次调用instantiateFactory方法实例化这些资源类并将其添加到result集合中。最后调用AnnotationAwareOrderComparator.sort方法进行集合的排序。

1.3.1 loadFactoryNames方法

loadFactoryNames方法核心代码如下:

18 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

19 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

20  MultiValueMap<String, String> result = cache.get(classLoader);

21  if (result != null)

22  return result;

23  try {

24  Enumeration<URL> urls = (classLoader != null ?

25  classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

26  ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

27  result = new LinkedMultiValueMap<>();

28  while (urls.hasMoreElements()) {

29  URL url = urls.nextElement();

30  UrlResource resource = new UrlResource(url);

31  Properties properties = PropertiesLoaderUtils.loadProperties(resource);

32  for (Map.Entry<?, ?> entry : properties.entrySet()) {

33  List<String> factoryClassNames = Arrays.asList(

34  StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));

35  result.addAll((String) entry.getKey(), factoryClassNames);

36  }

37  }

38  cache.put(classLoader, result);

39  return result;

40  }

41  catch (IOException ex) {

42  }

43  }

loadSpringFactories方法直接加载所有的META-INF/spring.factories文件内容,其内部还是调用PropertiesLoaderUtils.loadProperties方法进行处理。该方法前面我们也详细的演示了,再次不再累赘。

唯一需要了解的是,这个地方使用了缓存策略。

1.3.2 instantiateFactory方法

instantiateFactory方法的核心逻辑如下:

1  private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {

2  try {

3         Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);

4  if (!factoryClass.isAssignableFrom(instanceClass)) {

5                throw new IllegalArgumentException(

6  }

7  return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();

8  }

9  catch (Throwable ex) {

10  throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);

11  }

12  }

直接调用了ClassUtils.forName方法,然后调用ReflectionUtils.accessibleConstructor方法进行实例对象进行对象的实例化工作,原来这里直接使用了反射技术进行对象的实例化工作。原来如此。

至此,XML以及properties的定义以及获取方式,SpringFactoriesLoader类的使用以及原理已经讲解完毕。


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 

   

作者:分享牛
         
本博客中未标明转载的文章归作者 分享牛所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


相关文章
|
前端开发 Java 应用服务中间件
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
457 0
《SpringBoot启动流程七》:源码分析SpringBoot如何内嵌并启动Tomcat服务器的?
|
Java Spring 容器
《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)
《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)
429 0
《SpringBoot启动流程六》:SpringBoot自动装配时做条件装配的原理(万字图文源码分析)(含@ConditionalOnClass原理)
|
Java Spring
《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)
《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)
206 0
《SpringBoot启动流程五》:你真的知道SpringBoot自动装配原理吗(两万字图文源码分析)
|
Java 应用服务中间件 Spring
《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段
《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段
267 0
《SpringBoot启动流程四》:图文带你debug源码分析SpringApplication运行阶段和运行后阶段
|
监控 安全 Java
《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)
《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)
335 0
《SpringBoot启动流程三》:两万+字图文带你debug源码分析SpringApplication准备阶段(含配置文件加载时机、日志系统初始化时机)
|
存储 缓存 前端开发
《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段
《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段
174 0
《SpringBoot启动流程二》:七千字源码分析SpringApplication构造阶段
|
Java 程序员 开发者
自定义spring boot starter三部曲之三:源码分析spring.factories加载过程
分析Spring和Spring boot源码,了解spring.factories自动加载原理
336 0
自定义spring boot starter三部曲之三:源码分析spring.factories加载过程
|
NoSQL Java Redis
SpringBoot自动化配置源码分析
SpringBoot 的自动化配置让我们的开发彻底远离了 Spring 繁琐的各种配置,让我们专注于开发,但是SpringBoot 的自动化配置是怎么实现的呢?下面为你揭开 SpringBoot 自动化配置的神秘面纱。
128 0
SpringBoot自动化配置源码分析
|
设计模式 Java 网络架构
SpringBoot请求映射源码分析(没看过源码的小白也能懂,比针尖还细)
SpringBoot请求映射源码分析(没看过源码的小白也能懂,比针尖还细)
314 0
SpringBoot请求映射源码分析(没看过源码的小白也能懂,比针尖还细)
|
安全 Java
Springboot 别名管理源码分析
# 别名的注册 SimpleAliasRegistry维护了一个映射别名到真实名称的aliasMap这里使用的是线程安全的ConcurrentHashMap,即在多线程的情况下也可以对同一个bean安全地进行别名的增删改查。同时也说明了**一个别名只能对应一个真名,而一个真名没有对应多个别名**。由于SimpleAliasRegistry是对项目全局别名的管理,**任意别名都必须是全局唯一的**。
215 0