您的位置:首页 > 编程语言 > Java开发

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

2017-11-27 12:31 1046 查看
摘要:本文我们重点分析一下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    本博客中未标明转载的文章归作者分享牛所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: