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

摘要:本文我们重点分析一下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相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。 

   

作者:分享牛
    
出处:http://blog.csdn.net/qq_30739519

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

时间: 2024-10-25 08:09:06

springboot源码分析4-springboot之SpringFactoriesLoader使用的相关文章

springboot源码分析11-ApplicationContextInitializer原理

摘要:springboot源码分析10-ApplicationContextInitializer使用一文中,我们详细地讲解了ApplicationContextInitializer的三种使用方式,本文我们重点看一下为何这三种方式都可以使用,也就是框架是如何处理的.包括内置的ContextIdApplicationContextInitializer.DelegatingApplicationContextInitializer. 1.1. 用户手动添加ApplicationContextIn

springboot源码分析6-springboot之PropertySource类初探

摘要:本小节重点梳理一下PropertySource类的相关结构以及职责,本文的学习前提是学习了springboot源码分析5-springboot之命令行参数以及原理一文. 在springboot源码分析5-springboot之命令行参数以及原理一文中,我们看到了实例化Source类的时候,会去先实例化其父类SimpleCommandLinePropertySource.SimpleCommandLinePropertySource类的构造函数中直接解析了命令行参数以及值,然后返回封装好的C

springboot源码分析3-springboot之banner类架构以及原理

继续上文的<<springboot源码分析2-springboot 之banner定制以及原理章节>>进行讲解,上一节我们详细详解了banner的三种输出模式.banner的输出模式设置.banner类的架构.SpringApplicationBannerPrinter类.ImageBanner以及TextBanner的处理方式.本小节我们来重点讲解一下各种banner处理类的相关实现逻辑以及设计意图和职责. 1.1 SpringBootBanner类 SpringBootBann

springboot源码分析10-ApplicationContextInitializer使用

摘要:spring中ApplicationContextInitializer接口是在ConfigurableApplicationContext刷新之前初始化ConfigurableApplicationContext的回调接口.当spring框架内部执行 ConfigurableApplicationContext#refresh() 方法的时候回去回调. 1.1. 实现方式一 首先,我们需要自定义一个类并且实现ApplicationContextInitializer接口.示例代码如下:

springboot源码分析7-环境属性构造过程(上)

使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties.xml.yml.yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可.同理application.xml.applica

springboot源码分析2-springboot 之banner定制以及原理

1. springboot源码分析2-springboot 之banner定制以及原理 springboot在启动的时候,默认会在控制台输出默认的banner.也就是我们经常所说的图案,输出的图案如下所示:   .   ____          _            __ _ _  /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \  \\/  ___)| |_)| | | |

springboot源码分析14-ApplicationContextInitializer原理Springboot中PropertySource注解多环境支持以及原理

摘要:Springboot中PropertySource注解的使用一文中,详细讲解了PropertySource注解的使用,通过PropertySource注解去加载指定的资源文件.然后将加载的属性注入到指定的配置类,@value以及@ConfigurationProperties的使用.但是也遗留一个问题,PropertySource注解貌似是不支持多种环境的动态切换?这个问题该如何解决呢?我们需要从源码中看看他到底是否支持. 首先,我们开始回顾一下上节课说的PropertySource注解的

springboot源码分析14-事件发布机制以及应用监听器

摘要:事件驱动模型,也就是我们经常提到用到的观察者模式.当然也可以将其理解为发布-订阅模型.具体的实现要素有如下几个方面. 1.首先是一对多的关系,一是目标对象,多则是观察者对象.比如报社是一个,而订报者是多个. 2.当目标对象的行为发生变化的时候,多个观察者对象会级联触发并做出相应的处理.换言之,目标对象的行为发生变化的时候,只需要通知一下所有的观察者对象(订阅过的)即可.具体的各个观察者怎么去处理,使用什么方式去处理,并不是目标对象所需要考虑的范畴.也就是说目标与观察者的实现是低耦合.目标对

springboot源码分析9-random的使用以及原理

摘要:springboot框架为我们提供了很多的便利,其中有一个非常有意思的功能,那就是可以通过变量的方式来配置一个随机数random,然后使用random随机出各式各样数值.本位重点讲解一下random的使用以及框架内部的实现机制. 1.1. Springboot中random的使用 首先我们定义一个配置类,如下所示: 1 @Component 2 public class Config { 3  @Value("${random.value}") 4  private String