【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)(下)

简介: 【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)(下)

SpringFactoriesLoader


Spring最为一个最为流行的开源框架,必然就应该有属于它自己的SPI实现。而SpringFactoriesLoader就是它用来实现SPI的法宝。它在Spring Framework中鲜有应用,但是SpringBoot中被广泛的使用,它的自动配置和这息息相关,因此在讲解boot的自动化配置章节的时候,还会提到它。


上面已经对SPI进行了概念介绍,以及对JDK的标注实现:ServiceLoad进行了描述解释。

我们发现很多框架等都有自己对SPI的实现,

比如tomcat的实:WebappServiceLoader它用于比如容器启动时加载所有的ServletContainerInitializer实现类,从而驱动Spring容器的启动。


Spring的实现:SpringFactoriesLoader它用于SpringBoot中的自动化配置起到了关键甚至决定性作用~


注意:SpringFactoriesLoader的不同之处在于,它内部都是key-value的形式,这是和前两种SPI不一样的地方


使用Demo


是骡子是马,拉出来溜溜就可以了。

SpringFactoriesLoader默认加载的路径和文件为:类路径下META-INF/spring.factories


com.fsx.serviceloader.IService=com.fsx.serviceloader.HDFSService,com.fsx.serviceloader.LocalService
// 若有非常多个需要换行 可以这么写
// 前面是否顶头没关系(Spring在4.x版本修复了这个bug)
com.fsx.serviceloader.IService=\
    com.fsx.serviceloader.HDFSService,\
    com.fsx.serviceloader.LocalService

我们这么测试一下即可:


    public static void main(String[] args) throws IOException {
        List<IService> services = SpringFactoriesLoader.loadFactories(IService.class, Main.class.getClassLoader());
        List<String> list = SpringFactoriesLoader.loadFactoryNames(IService.class, Main.class.getClassLoader());
        System.out.println(list); //[com.fsx.serviceloader.HDFSService, com.fsx.serviceloader.LocalService]
        System.out.println(services); //[com.fsx.serviceloader.HDFSService@794cb805, com.fsx.serviceloader.LocalService@4b5a5ed1]
    }


完美work。


使用小细节:


  • spring.factories内容的key不只能是接口,也可以是抽象类、具体的类。但是有个原则:=后面必须是key的实现类(子类)
  • key还可以是注解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一个注解
  • 文件的格式需要保证正确,否则会返回[](不会报错)
  • =右边必须不是抽象类,必须能够实例化。且有空的构造函数~
  • loadFactories依赖方法loadFactoryNames。loadFactoryNames方法只拿全类名,loadFactories拿到全类名后会立马实例化
  • 此处特别注意:loadFactories实例化完成所有实例后,会调用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的,这个特点特别的重要。

SpringFactoriesLoader的版本小变化:Spring4.x的时候该类是个抽象类,且没有缓存。Spring5.x后它变成了final类,并且加入了Map<ClassLoader, MultiValueMap<String, String>> cache缓存


原理简单剖析


因为Spring的这个配置文件和上面的不一样,它的名字是固定的spring.factories,里面的内容是key-value形式,因此一个文件里可以定义N多个键值对。我认为它比源生JDK的SPI是更加灵活些的~


它主要暴露了两个方法:loadFactories和loadFactoryNames


// @since 3.2
public final class SpringFactoriesLoader {
  ...
  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  ...
  // 核心方法如下:
  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
      return result;
    }
    try {
      // 读取到资源文件,遍历
      Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 此处使用的是URLResource把这个资源读进来~~~
        UrlResource resource = new UrlResource(url);
        // 可以看到,最终它使用的还是PropertiesLoaderUtils,只能使键值对的形式哦~~~ 当然xml也是被支持的
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
          String factoryClassName = ((String) entry.getKey()).trim();
          // 使用逗号,分隔成数组,遍历   名称就出来了~~~
          for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
            result.add(factoryClassName, factoryName.trim());
          }
        }
      }
      cache.put(classLoader, result);
      return result;
    } catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
          FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
  }
}


原理其实蛮简单的,主要是SPI这种设计思想,对开闭原则来说还是非常的重要的。因为有了Spring的SPI,所以SpringBoot的自动配置也就自然而然了


Spring中的应用举例


若你不是Boot环境,Spring Framwork中自己应用极少。此处找到一个Spring内部使用者:CachedIntrospectionResults:它是一个内建的还是很有用的一个类,主要和缓存JavaBean有关,还有BeanInfoFactory。它有这么一句使用:


  /** Stores the BeanInfoFactory instances. */
  private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
      BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
// 配置文件内容为
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
// 也就是说`BeanInfoFactory`的实现类默认使用的`ExtendedBeanInfoFactory`



关于JavaBean、内省、BeanInfoFactory、BeanInfo等等基础,各位可以自行学习了解一下~


SPI解决的问题场景描述


在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?


  • 其一:我们可以规范不同的系统调用,也就是传递一个系统标识;
  • 其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理;
  • 其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一个功能的实现,A实现类与B实现类的逻辑一点也不一样,但是目标是一样的,这个时候使用策略类是毋庸置疑的?


从上面三种实现方案来看,高下立判。当然任何事情都不是绝对的,如果你要求速度,第一种方案可谓是最为快速的,但是是最难维护的


上述的问题我们是在API层面进行处理的。

那万一有一天让我们自己设计一套框架,然后让别人直接使用(比如Spring,比如tomcat)?我们该如何处理上述的这个问题呢?答:SPI技术


相关文章
|
15天前
|
人工智能 前端开发 Java
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
236 0
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
133 62
|
1月前
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
57 14
|
2月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
252 12
基于开源框架Spring AI Alibaba快速构建Java应用
|
1月前
|
XML 前端开发 安全
Spring MVC:深入理解与应用实践
Spring MVC是Spring框架提供的一个用于构建Web应用程序的Model-View-Controller(MVC)实现。它通过分离业务逻辑、数据、显示来组织代码,使得Web应用程序的开发变得更加简洁和高效。本文将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring MVC,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
81 2
|
2月前
|
JSON 安全 算法
Spring Boot 应用如何实现 JWT 认证?
Spring Boot 应用如何实现 JWT 认证?
86 8
|
2月前
|
消息中间件 Java Kafka
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
Spring Boot 与 Apache Kafka 集成详解:构建高效消息驱动应用
59 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
131 2
|
2月前
|
Java Docker 微服务
利用Docker容器化部署Spring Boot应用
利用Docker容器化部署Spring Boot应用
55 0