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

来源:互联网 发布:在线相片制作软件 编辑:程序博客网 时间:2024/06/06 08:45

摘要:本文我们重点分析一下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
    
本博客中未标明转载的文章归作者分享牛所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


阅读全文
0 0
原创粉丝点击