【小家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技术


相关文章
|
2月前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
125 1
|
2月前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
53 0
|
10天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
529 6
|
8天前
|
Java Spring 容器
Spring使用异步注解@Async正确姿势
Spring使用异步注解@Async正确姿势,异步任务,spring boot
|
7天前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean
|
7天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
21天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
2月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
2月前
|
消息中间件 Java RocketMQ
微服务架构师的福音:深度解析Spring Cloud RocketMQ,打造高可靠消息驱动系统的不二之选!
【8月更文挑战第29天】Spring Cloud RocketMQ结合了Spring Cloud生态与RocketMQ消息中间件的优势,简化了RocketMQ在微服务中的集成,使开发者能更专注业务逻辑。通过配置依赖和连接信息,可轻松搭建消息生产和消费流程,支持消息过滤、转换及分布式事务等功能,确保微服务间解耦的同时,提升了系统的稳定性和效率。掌握其应用,有助于构建复杂分布式系统。
38 0
|
2月前
|
监控 安全 Java
【开发者必备】Spring Boot中自定义注解与处理器的神奇魔力:一键解锁代码新高度!
【8月更文挑战第29天】本文介绍如何在Spring Boot中利用自定义注解与处理器增强应用功能。通过定义如`@CustomProcessor`注解并结合`BeanPostProcessor`实现特定逻辑处理,如业务逻辑封装、配置管理及元数据分析等,从而提升代码整洁度与可维护性。文章详细展示了从注解定义、处理器编写到实际应用的具体步骤,并提供了实战案例,帮助开发者更好地理解和运用这一强大特性,以实现代码的高效组织与优化。
56 0
下一篇
无影云桌面