spring的spi

时间:2022-07-25
本文章向大家介绍spring的spi,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

spi是什么?

SPI(service provider interface)机制是JDK内置的一种服务发现机制,可以动态的发现服务,即服务提供商,它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。目前这种大部分都利用SPI的机制进行服务提供,比如:dubbo、spring、JDBC、等;

spi解决了什么问题?

由于classLoader加载类的时候采用是【双亲委托模式】,意思是:首先委托父类去加载器获取,若父类加载器存在则直接返回,若加载器无法完成此加载任务,自己才去加载。该加载存在的弊端就是上层的类加载永远无法加载下层的类加载器所加载的类,所以通过spi解决了该问题。

spi是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔spi实现了动态加载,插件化,

弊端:

资源浪费:由于 spi 是通过循环加载实现类,会导致所有的类全部一起加载!

有哪些SPI机制?

jcl-over-slf4j-1.7.5.jar

dubbo

mysql-connector-java-5.1.35.jar

等等...

原因

简单的说,类加载器中的双亲委派模型的工作原理是对于类的加载,先交给父类加载器完成,如果父类加载器无法完成加载,子类加载器才进行加载。JDK中的一些基础类接口,例如 JDBC,如果按照双亲委派模型,JDBC只能用启动类加载器完成驱动的加载,但是驱动的实现是在用户目录的 jar 包实现,所以启动类加载器就无法加载用户的目录的驱动类了(下面可以从代码实例了解一下)

ServiceLoader 的源码中的介绍如下

代码下载:https://gitee.com/hong99/spring/issues/I1N1DF

代码实现

jdk spi实现

位置图

/**
 *
 * 功能描述:接口
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/17 15:13
 */
public interface Robot {

    void sayHello();
}
package com.hong.spring.spi.service.impl;

import com.hong.spring.spi.service.Robot;
/**
 *
 * 功能描述: 大黄峰
 *
 * @param: 
 * @return: 
 * @auther: csh
 * @date: 2020/8/17 15:14
 */
public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}
package com.hong.spring.spi.service.impl;

import com.hong.spring.spi.service.Robot;
/**
 *
 * 功能描述: 擎天柱
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/17 15:14
 */
public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

META-INF/services/com.hong.spring.spi.service.Robot

com.hong.spring.spi.service.impl.Bumblebee
com.hong.spring.spi.service.impl.OptimusPrime

spi_test.JdkSpiTest

package spi_test;

import com.hong.spring.spi.service.Robot;
import sun.misc.Service;

import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * @Auther: csh
 * @Date: 2020/8/17 14:53
 * @Description:jdk的spi测试
 */
public class JdkSpiTest {
    /**
     *
     * 功能描述:测试spi
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/8/17 15:16
     */
    public static void main(String[] args) {
        Iterator <Robot> providers = Service.providers(Robot.class);
        ServiceLoader <Robot> load = ServiceLoader.load(Robot.class);
        while (providers.hasNext()){
            Robot next = providers.next();
            next.sayHello();
        }
        System.out.println("==============================");
        Iterator <Robot> iterator = load.iterator();
        while (iterator.hasNext()){
            Robot next = iterator.next();
            next.sayHello();
        }

    }
}

结果

Hello, I am Bumblebee.
Hello, I am Optimus Prime.
==============================
Hello, I am Bumblebee.
Hello, I am Optimus Prime.

spring spi实现

spring的spi是通过SpringFactoriesLoader类loadFactories方法来加载的,包目录是:org.springframework.core.io.support

文件格式是:配置放在 META-INF/spring.factories中;

public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
   Assert.notNull(factoryClass, "'factoryClass' must not be null");
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
   if (logger.isTraceEnabled()) {
      logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
   }
   List<T> result = new ArrayList<T>(factoryNames.size());
   for (String factoryName : factoryNames) {
      result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
   }
   AnnotationAwareOrderComparator.sort(result);
   return result;
}

代码下载:https://gitee.com/hong99/spring/issues/I1N1DF

最后:

不管是jdk 的spi或者 spring spi都很好遵循了开闭原则,即对修改关闭,对拓展开放,其实看了很多开源的框架后都发现,很多框架都是遵循原则,并且在里面应用了大量的设计模式。而spi 的思想应用广泛就是应用了策略模式,不管是jdk/jdbc/spring/dubbo等框架都有用到,只是所标注的名字不同而且,实现原理大致相同。spi主要是解決是为了解决因为类上层加载器无法去加载下层加载器这个问题,当然也存在一个弊端就是扫描了所有类实现...,

参考文章:

https://www.cnblogs.com/helloz/p/10961026.html

jdK spi:https://blog.csdn.net/rzpy_qifengxiaoyue/article/details/107977448

双亲委托:https://www.cnblogs.com/hiyujie/p/wo-xueJava1ClassLoader-yu-shuang-qin-wei-tuo-mo-sh.html

https://www.cnblogs.com/hiyujie/p/wo-xueJava1ClassLoader-yu-shuang-qin-wei-tuo-mo-sh.html

https://learnku.com/articles/45733

dubbo官方:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html