Java SPI机制

Java SPI机制

java的spi就是一种服务提供发现机制,在一方制定好接口规范后,通过spi的机制可以它的子实现类进行服务发现,以及加载它的子实现类,通过这种机制,让我们在引入第三方库时,不用讲第三方库中的类硬编码到我们的代码中,而是通过java spi的机制来动态加载这些类

1、使用方法

  • 在classpath:resource文件夹下创建一个META-INF文件夹
  • 再在META-INF文件夹下创建一个services文件夹
  • 然后在services文件夹创建对应的文件,文件名-接口的全限定名,文件内容就是需要加载的子实现类的全限定名

2、加载方式

  • 通过ServiceLoader这个类的load()方法,传入对应的class对象进行加载,但是这里不会真正的加载,只是讲我们的文件中的类全限定名,存放到providerNames集合中
  • 他内部是一个迭代器设计模式的实现,只有当我们进行迭代时,调用next()方法时,会调用provides的next()方法迭代providerNames集合对象,获取对象内的每个全限定名,然后调用get方法获取对应的实例对象,这一步才会创建对象,而且使用 ServiceLoader 迭代的时候,会将所有的实现类都加载并实例化,以便在迭代过程中提供这些实现的实例。
    • 优点:
      • 核心思想:
        • 解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离。可以根据实际业务情况进行使用或扩展。
    • 缺点:
      • 1、获取接口的实现类的方式不灵活
        serviceLoader 只能通过 Iterator 形式遍历获取,不能根据参数获取指定的某个实现类。
      • 2、资源浪费
        serviceLoader 只能通过遍历的方式将接口的实现类全部获取、加载并实例化一遍。如果不想用某些实现类,它也被加载并实例化,造成浪费。
  • 其次他也打破了在加载我们自定义类的类加载的一个双亲委派机制,它会获取我们当前的上下文的加载器(appClassLoader),如果为空也会获取systemClassLoader进行加载,而不是层层递归,委托向上进行加载。
    • AppClassLoaderSystemClassLoader 在层级上是一样的。
    • Bootstrap ClassLoader 负责从 rt.jar 加载标准 JDK 类文件,它是 Java 中所有类加载器的父类。 Bootstrap 类加载器没有任何父类。
    • Extension ClassLoader 将类加载请求委托(delegate)给其父 Bootstrap,如果不成功,则从 jre/lib/ext 目录或 java.ext.dirs 系统属性指向的任何其他目录加载类
    • 系统或应用程序类加载器,它负责从 CLASSPATH 环境变量、-classpath 或 -cp 命令行选项、JAR 内 list 文件的类路径属性加载应用程序特定类。
      • 应用程序类加载器是 Extension ClassLoader 的子类,由 sun.misc.Launcher$AppClassLoader 类实现。
      • 除了Bootstrap 类加载器,它主要是用C 语言实现的,所有Java 类加载器都是使用java.lang.ClassLoader 实现的。

3、测试用例

public class SpiSolution {public static void main(String[] args) {ServiceLoader<Object> load = ServiceLoader.load(Object.class);Iterator<Object> iterator = load.iterator();while (iterator.hasNext()) {Object next = iterator.next();}}
}

4、load方法

默认去找上下文类加载器:

@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}

在这里插入图片描述

没有就去获取系统类加载器:

new ServiceLoader<>(Reflection.getCallerClass(), service, cl):

在这里插入图片描述

private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {Objects.requireNonNull(svc);if (VM.isBooted()) {checkCaller(caller, svc);if (cl == null) {cl = ClassLoader.getSystemClassLoader();}} else {// if we get here then it means that ServiceLoader is being used// before the VM initialization has completed. At this point then// only code in the java.base should be executing.Module callerModule = caller.getModule();Module base = Object.class.getModule();Module svcModule = svc.getModule();if (callerModule != base || svcModule != base) {fail(svc, "not accessible to " + callerModule + " during VM init");}// restricted to boot loader during startupcl = null;}this.service = svc;this.serviceName = svc.getName();this.layer = null;this.loader = cl;this.acc = (System.getSecurityManager() != null)? AccessController.getContext(): null;
}

5、创建调用iterator()对象

5.1 创建LookupIterator()

会创建一个newLookupIterator()和一个迭代器对象new Iterator<S>()

public Iterator<S> iterator() {// create lookup iterator if neededif (lookupIterator1 == null) {lookupIterator1 = newLookupIterator();}return new Iterator<S>() {// record reload countfinal int expectedReloadCount = ServiceLoader.this.reloadCount;// index into the cached providers listint index;/*** Throws ConcurrentModificationException if the list of cached* providers has been cleared by reload.*/private void checkReloadCount() {if (ServiceLoader.this.reloadCount != expectedReloadCount)throw new ConcurrentModificationException();}@Overridepublic boolean hasNext() {checkReloadCount();if (index < instantiatedProviders.size())return true;return lookupIterator1.hasNext();}@Overridepublic S next() {checkReloadCount();S next;if (index < instantiatedProviders.size()) {next = instantiatedProviders.get(index);} else {next = lookupIterator1.next().get();instantiatedProviders.add(next);}index++;return next;}};

5.2 创建LookupIterator()

LookupIterator:

  • 会创建懒加载服务迭代器:
  • Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
  • Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
private Iterator<Provider<S>> newLookupIterator() {assert layer == null || loader == null;if (layer != null) {return new LayerLookupIterator<>();} else {Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();return new Iterator<Provider<S>>() {@Overridepublic boolean hasNext() {return (first.hasNext() || second.hasNext());}@Overridepublic Provider<S> next() {if (first.hasNext()) {return first.next();} else if (second.hasNext()) {return second.next();} else {throw new NoSuchElementException();}}};}}

5.3 LazyClassPathLookupIterator()

LazyClassPathLookupIterator:去读取

在这里插入图片描述

6、迭代过程中加载

迭代过程中才会加载:

  • 迭代过程中获取next(),实际获取lookupIterator1.next().get()去创建实例对象:
public Iterator<S> iterator() {// create lookup iterator if neededif (lookupIterator1 == null) {lookupIterator1 = newLookupIterator();}return new Iterator<S>() {// record reload countfinal int expectedReloadCount = ServiceLoader.this.reloadCount;// index into the cached providers listint index;/*** Throws ConcurrentModificationException if the list of cached* providers has been cleared by reload.*/private void checkReloadCount() {if (ServiceLoader.this.reloadCount != expectedReloadCount)throw new ConcurrentModificationException();}@Overridepublic boolean hasNext() {checkReloadCount();if (index < instantiatedProviders.size())return true;return lookupIterator1.hasNext();}@Overridepublic S next() {checkReloadCount();S next;if (index < instantiatedProviders.size()) {next = instantiatedProviders.get(index);} else {next = lookupIterator1.next().get();instantiatedProviders.add(next);}index++;return next;}};
}

在这里插入图片描述

7、源码应用

7.1 JDBC

jdbc去动态拓展,要去使用其它厂商的服务,如oracle、mysql,它只需要制定一个接口的规范,由其它厂商去遵循它的规范,就可以实现动态可插拔。

7.2 SpringMVC

在 Spring MVC 中,Servlet 3.0 的 SPI(Service Provider Interface)机制可以帮助您实现零 XML 配置文件的方式,通过注解和接口的实现来自动初始化 Spring MVC 相关的配置,从而实现无需显式的 XML 配置文件。以下是在 Spring MVC 中通过 Servlet 3.0 的 SPI 实现零 XML 配置的详细步骤:

  • 在spring-web的包下,有个META-INF/services/javax.servlet.ServletContainerInitiaLizer,内容是实现类org.springframework.web.SpringServletContainerInitializer,其为servlet为我们提供的一个接口,并会在tomcat,jetty等web容器启动时调用onStartUp()方法,使用@HandlesTypes(WebApplicationInitializer.class),将所有的实现类扫描到webAppInitializerClasses集合中作为参数onStartup()方法中。
  • 创建一个new ArrayList()集合,将所有符合条件的类,比如实现了WebApplicationInitializer.class接口的类,然后通过反射创建对象,添加到集合中,然后再统一迭代ArrayList集合中的类,调用每个对象的onStartup()进行初始化
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {public SpringServletContainerInitializer() {}public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {List<WebApplicationInitializer> initializers = Collections.emptyList();Iterator var4;if (webAppInitializerClasses != null) {initializers = new ArrayList(webAppInitializerClasses.size());var4 = webAppInitializerClasses.iterator();while(var4.hasNext()) {Class<?> waiClass = (Class)var4.next();if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());} catch (Throwable var7) {throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);}}}}if (((List)initializers).isEmpty()) {servletContext.log("No Spring WebApplicationInitializer types detected on classpath");} else {servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");AnnotationAwareOrderComparator.sort((List)initializers);var4 = ((List)initializers).iterator();while(var4.hasNext()) {WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();initializer.onStartup(servletContext);}}}
}

1. 创建扩展接口和实现类:

首先,定义一个扩展接口,例如 WebApplicationInitializer

public interface WebApplicationInitializer {void onStartup(ServletContext servletContext) throws ServletException;
}

然后,创建实现了 WebApplicationInitializer 接口的类,这些类将负责初始化 Spring MVC 配置:

public class MyWebAppInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {// 在这里初始化 Spring MVC 配置}
}

2. 使用 @HandlesTypes 注解:

MyWebAppInitializer 类上使用 @HandlesTypes 注解,以便在应用启动时将所有实现了 WebApplicationInitializer 接口的类传递给容器:

@HandlesTypes(WebApplicationInitializer.class)
public class MyWebAppInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {// 在这里初始化 Spring MVC 配置}
}

3. 初始化 Spring MVC 配置:

onStartup 方法内,您可以使用 Spring 的注解来初始化 Spring MVC 配置,例如注册 DispatcherServlet、扫描控制器、视图解析器等:

public class MyWebAppInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();context.register(AppConfig.class);DispatcherServlet dispatcherServlet = new DispatcherServlet(context);ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);registration.setLoadOnStartup(1);registration.addMapping("/");}
}

4. 部署到 Servlet 3.0 容器:

将您的应用部署到支持 Servlet 3.0 的容器,例如 Tomcat 7+。

5. 实现零 XML 配置:

通过上述步骤,您的 Spring MVC 应用现在可以实现零 XML 配置。MyWebAppInitializer 类的实现会在应用启动时被容器检测到并执行,从而初始化 Spring MVC 配置,而无需显式的 XML 配置文件。

通过使用 Servlet 3.0 的 SPI 机制和 WebApplicationInitializer 接口,您可以在 Spring MVC 中实现零 XML 配置,更加便捷地进行应用初始化和配置。

7.3 Springboot的自动装配

Springboot starter的自动装配

Spring SPI与 JDK SPI 类似, 相对于 Java 的 SPI 的主要在于:

  • Spring SPI 指定配置文件为 classpath 下的 META-INF/spring.factories,所有的拓展点配置放到一个文件中。
    配置文件内容为 key-value 类型,key 为接口的全限定名, value 为 实现类的全限定名,可以为多个。

  • Spring Boot通过@EnableAutoConfiguration注解来开启自动配置功能。这个注解实际上包含了两个注解:@Configuration@Import

    • @Configuration注解表示该类是一个配置类,用于定义Bean的实例化和装配规则。
    • @Import注解用于导入其他配置类,从而将它们的配置规则合并到当前配置类中。
  • 自动装配其实是通过条件化装配、自动配置类、配置属性绑定来实现的

    • 条件化装配:Spring Boot 使用条件化注解(@Conditional)来实现自动装配。这些注解基于运行时环境的条件来决定是否需要装配某个组件。

    • 自动配置类:Spring Boot 通过在 classpath 下的 META-INF/spring.factories 文件中定义自动配置类,这些自动配置类使用了条件化注解,根据条件来装配相应的组件。

    • 配置属性绑定:自动配置类使用了配置属性(@ConfigurationProperties)来绑定应用程序的配置到相应的组件中。配置属性可以从 application.properties 或 application.yml 文件中读取。
      所以如果我们想要引入一些第三方包,就需要按照下面几步来操作:

源码分析:

  • @SpringBootApplication 是一个复合注解,包含多个注解的元注解,相当于同时添加了三个注解的效

    • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

      @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

      @ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter

  • @EnableAutoConfiguration是一个复合注解

    • 这里面用 @Import 注解导入了一个 AutoConfigurationImportSelector 类,这个AutoConfigurationImportSelector类实现了 DeferredImportSelector 接口,重写了selectImports方法,它会先判断自动配置这个注解是否开启,只有开启了就会调用getAutoConfigurationEntry方法,具体方法内会带调用getCandidateConfigurations方法,在这个方法内就回去调用一个SpringFactoriesLoaderloadFactoryNames方法去读取META-INF/spring.factories内的数据然后,获取所有符合条件的类的全限定类名,存储到List集合中返回,然后再根据一些条件化配置对集合进行筛选,比如@ConditionalOnClass:当类路径下有指定类的条件下,@ConditionalOnProperty:yml配置文件中是否进行了属性配置,去除一些不符合条件的全限定名,满足条件这些类就会被加载到 IoC 容器中

总结:

  • SpringBoot 启动时,会扫描 META-INF/spring.factories 文件,获取所有自动配置类的全限定名
  • 根据项目的依赖关系和配置信息,选择并加载相应的自动配置类.
    • 自动配置类使用 @ConditionalOnXXX 注解来进行条件装配,通过判断特定条件是否满足来确定是否进行自动装配

7.3 dubbo spi

dubbo spi和java spi不是同一种实现方式,因为他有一个很大的改进,java spi迭代的时候,会将所有的实现类都加载并实例化,不能制定就获取其中一个,而dubbo是按需加载,它只是一开始读取到了配置文件后,把这些配置文件内的类进行存储,这个过程叫做一个IOC和aop,当你使用的使用才会进行加载,

8、spi应用

实战中,我自己也写了个rpc,对所有模块进行了一个spi的统一模块管理,也是参考了dubbo,分为两个,一个是系统的spi就读取程序本身所要使用的bean,然进行加载,除此之外还有一个用户的spi,用户spi,就是说用户可以遵循我的规范来玩的化,就可以对我们程序中的一些模块的增加或者增强,进行一些拓展的行为。

9、总结

好处:

  • 可拓展,减少硬编码,从而减少一个耦合性
  • 此外动态可插拔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/89155.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

管易云和金蝶云星空接口打通对接实战

管易云和金蝶云星空接口打通对接实战 对接系统管易云 管易云是上海管易云计算软件有限公司旗下的专注提供电商企业管理软件服务的品牌&#xff0c;总部位于中国上海张江高科技产业园区。管易云旗下拥有管易云C-ERP、EC-OMS、EC-WMS、B2C/B2B/BBC/微商城开发、PDA无纸化仓储解决…

TypeScript项目中Axios的封装

目录 前言 一、axios中的常见类型 1. AxiosInstance 2. AxiosRequestConfig 3. AxiosResponse 4. AxiosError 二、axios封装步骤 三、封装后的完整代码 1. 基础封装 2. 高级封装 前言 为了实现统一的网络请求处理和管理&#xff0c;在日常开发中我们常常封装 axios&…

一个步骤跳过 Unity 启动Logo | 多平台适用 | 官方API支持

前言【Unity实战篇 】 | 一个步骤跳过 Unity Logo 界面 | 多平台适用 | 官方API支持使用方法核心 API1. RuntimeInitializeOnLoadMethodAttribute2. SplashScreen效果展示总结前言 众所周知,使用Unity引擎打包的工程在启动时都带有Unity的默认启动Logo。这个问题可以通过购买U…

Codeforces Round 893 (Div. 2)ABC

Codeforces Round 892 (Div. 2) 目录 A. United We Stand题目大意思路代码 B. Olya and Game with Arrays题目大意思路代码 C. Another Permutation Problem题目大意思路代码 A. United We Stand 题目大意 给你一个数组&#xff0c;把这个数组分成两个数组a和b&#xff0c;使…

大数据-玩转数据-Flink RedisSink

一、添加Redis Connector依赖 具体版本根据实际情况确定 <dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-redis_2.11</artifactId><version>1.1.5</version> </dependency>二、启动redis 参…

使用KETTLE工具在Oracle和Dm8之间迁移数据

oracle 代码测试数据 CREATE TABLE PRODUCT_CATEGORY ( PRODUCT_CATEGORYID NUMBER(11,0) NOT NULL , NAME VARCHAR2(255) NOT NULL ENABLE, PRIMARY KEY (PRODUCT_CATEGORYID) )INSERT ALL into PRODUCT_CATEGORY(PRODUCT_CATEGORYID, NAME)VALUES(2,国学) into PRODUCT_CATEG…

Qt开发技术:Q3D图表开发笔记:Q3DSurface三维曲面图介绍、Demo以及代码详解

前言 qt提供了q3d进行三维开发&#xff0c;虽然这个框架没有得到大量运用也不是那么成功&#xff0c;性能上也有很大的欠缺&#xff0c;但是普通的点到为止的应用展示还是可以的。   其中就包括华丽绚烂的三维图表&#xff0c;数据量不大的时候是可以使用的。   前面介绍了…

win10电脑右下角不显示电脑图标,但是能正常上网,怎么解决?

一、问题描述 win10系统更新后&#xff0c;电脑右下角不显示小电脑图标&#xff0c;但是能正常上网&#xff0c;而且用命令测试时显示的是192打头的网址。 二、解决方法 运行命令提示符窗口&#xff0c;在命令提示符中输入&#xff1a;netsh winsock reset&#xff08; 如果提示…

学习pytorch 3 tensorboard的使用

tensorboard的使用 1. 安装2. add_scalar 查看函数图形3. 查看结果4. add_image() 查看训练步骤中间结果的图片 1. 安装 pytorch conda环境 pip install tensorboard pip install opencv-python2. add_scalar 查看函数图形 常用来查看 train val loss等函数图形 from torch…

第57步 深度学习图像识别:CNN可视化(Pytorch)

基于WIN10的64位系统演示 一、写在前面 由于不少模型使用的是Pytorch&#xff0c;因此这一期补上基于Pytorch实现CNN可视化的教程和代码&#xff0c;以SqueezeNet模型为例。 二、CNN可视化实战 继续使用胸片的数据集&#xff1a;肺结核病人和健康人的胸片的识别。其中&…

Java整合Selenium录制视频

捕捉视频 有时候我们未必能够分析故障只需用日志文件或截图的帮助。有时捕获完整的执行视频帮助。让我们了解如何捕捉视频。 我们将利用Monte媒体库的执行相同。 配置 第1步&#xff1a;导航到URL下载屏幕记录JAR&#xff0c;如下图所示。 http://www.randelshofer.ch/monte…

day24-106.从中序与后序遍历序列构造二叉树

106.从中序与后序遍历序列构造二叉树 力扣题目链接(opens new window) 根据一棵树的中序遍历与后序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如&#xff0c;给出 中序遍历 inorder [9,3,15,20,7]后序遍历 postorder [9,15,7,20,3] 返回如下的二叉树&am…

如何在win10系统上使用旧版本的IE浏览器

win10系统打开IE浏览器自动变成了Edge浏览器&#xff0c;切换成IE模式时&#xff0c;IE浏览器的版本默认为IE11&#xff08;注&#xff1a;Edge浏览器只支持IE11&#xff09;&#xff0c;有些网站只能使用IE浏览器打开或者在做一些兼容性测试时&#xff0c;需要使用到不同版本的…

PLC求解弹簧质量模型微分方程数值解(RK4梯形图程序)

微分方程的数值求解,属于数学分析类课程涉及的内容。大家可以参看相关书籍对Runge-Kutta法的介绍,弹簧质量阻尼模型详细的微分方程介绍可以查看下面文章,链接如下: 弹簧质量阻尼系统前馈PID位置控制(PLC闭环仿真SCL+ST代码)_RXXW_Dor的博客-CSDN博客带前馈控制的博途PID程…

【数据结构•堆】堆排序(理论基础)

堆的定义  • 堆是一个完全二叉树   –所有叶子在同一层或者两个连续层   –最后一层的结点占据尽量左的位置  • 堆性质   –为空, 或者最小元素在根上   –两棵子树也是堆 存储方式  • 最小堆的元素保存在heap[1..hs]内   – 根在heap[1]   –K的左儿子是2k,…

在线吉他调音

先看效果&#xff08;图片没有声&#xff0c;可以下载源码看看&#xff0c;比这更好~&#xff09;&#xff1a; 再看代码&#xff08;查看更多&#xff09;&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&quo…

【Unity】VS Code 没有智能提示 Unity 中的类

正常来说&#xff0c;VS Code中会对部分输入类名进行提示&#xff0c;如下图所述 假如你从Unity 中进入 VS Code后发现没有提示相关 Unity的类&#xff0c;可能是 Unity 中 有关于 VS Code的相关Package 没有跟着 VS Code升级到最新版本。 点击Unity Windows 下拉框中的 Pac…

基于Promise.resolve实现Koa请求队列中间件

本文作者为360奇舞团前端工程师 前言 最近在做一个 AIGC 项目&#xff0c;后端基于 Koa2 实现。其中有一个需求就是调用兄弟业务线服务端 AIGC 能力生成图片。但由于目前兄弟业务线的 AIGC 项目也是处于测试阶段&#xff0c;能够提供的服务器资源有限&#xff0c;当并发请求资源…

直播带货热潮:海外网红直播对产品推广的影响与机遇

随着互联网的快速发展和社交媒体的普及&#xff0c;直播带货成为了一种风靡全球的新型营销方式。其中&#xff0c;海外网红直播作为直播带货的一种形式&#xff0c;引起了广泛的关注。海外网红以其独特的个人魅力和粉丝基础&#xff0c;成为了产品推广的强有力渠道。本文Nox聚星…

SpringBoot 异步、邮件任务

异步任务 创建一个Hello项目 创建一个类AsyncService 异步处理还是非常常用的&#xff0c;比如我们在网站上发送邮件&#xff0c;后台会去发送邮件&#xff0c;此时前台会造成响应不动&#xff0c;直到邮件发送完毕&#xff0c;响应才会成功&#xff0c;所以我们一般会采用多线…