[Spring ~松耦合的设计神器]`SPI`

2023-12-16 17:31:22

Java SPI(Service Provider Interface)是一种Java的服务提供者接口机制。它允许在运行时动态加载实现服务接口的类。

基本概念

SPI 机制的基本思想是,定义一个服务接口,多个不同的实现类可以实现这个接口。然后,在classpath 路径下的META-INF/services目录中创建一个以服务接口的全限定名命名的文件,文件内容为实现类的全限定名。这样,当程序运行时,可以通过ServiceLoader工具类来加载所有实现了该服务接口的类。

使用Java SPI的好处是,可以在不修改源代码的情况下,通过配置更换实现类,实现程序的可扩展性。而不需要在代码中显式地引用实现类,从而实现了松耦合的设计。

SPI机制在Java中被广泛应用,例如JDBC中的Driver接口和Servlet容器中的Servlet接口等。通过SPI机制,可以在不同的场景中灵活地替换实现类,提供更多的选择和定制化的功能。同时,SPI机制也增加了程序的灵活性和可维护性。

最简单的实例

我们现在需要使用一个内容搜索接口,搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索,甚至是 rpc 搜索。

基本步骤

  1. 定义接口
  2. META-INF/services 目录下,新建接口全限定名(如果是内部接口或内部类需要使用 $)的文件,然后在文件里面加上实现类。
  3. 使用 ServiceLoader加载接口,进行使用
public class SpiApplication {

    public interface Search {
        List<String> searchDoc(String keyword);
    }

    public static class FileSearch implements Search{
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("文件搜索 "+keyword);
            return null;
        }
    }
    public static class DatabaseSearch implements Search{
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("数据搜索 "+keyword);
            return null;
        }
    }

    public static class RpcSearch implements Search{
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("rpc搜索 "+keyword);
            return null;
        }
    }


    public static void main(String[] args) {
        ServiceLoader<Search> s = ServiceLoader.load(Search.class);
        Iterator<Search> iterator = s.iterator();
        while (iterator.hasNext()) {
            Search search =  iterator.next();
            search.searchDoc("hello world");
        }
    }
}

image.png
可以看到输出三行搜索结果,这就是因为 ServiceLoader.load(Search.class) 在加载某接口时,会去 META-INF/services 下找接口的全限定名文件,再根据里面的内容加载相应的实现类。
这就是 spi 的思想,接口的实现由 provider 实现,provider 只用在提交的 jar 包里的 META-INF/services 下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。

使用 jar 包通过 spi动态实现接口功能

新增 jar包,打包后加载到其他项目运行。

public interface Search {
    List<String> searchDoc(String keyword);
}

//-
public class DefaultSearch implements Search{

    @Override
    public List<String> searchDoc(String keyword) {
        System.out.println("default search");
        return null;
    }
}

//-
public class MainApplicaction {

    public static void main(String[] args) {
        ServiceLoader<Search> s = ServiceLoader.load(Search.class);
        Iterator<Search> iterator = s.iterator();
        while (iterator.hasNext()) {
            Search search =  iterator.next();
            search.searchDoc("hello world");
        }
        System.out.println(">>>> jar main end");
    }


}

image.png
然后 mvn install到本地,把 jar包导入到其他项目
image.png

package com.example.spi;

import com.example.MainApplicaction;
import com.example.Search;

import java.util.List;

public class SpiApplication {



    public static class FileSearch implements Search {
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("文件搜索 "+keyword);
            return null;
        }
    }
    public static class DatabaseSearch implements Search{
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("数据搜索 "+keyword);
            return null;
        }
    }

    public static class RpcSearch implements Search{
        @Override
        public List<String> searchDoc(String keyword) {
            System.out.println("rpc搜索 "+keyword);
            return null;
        }
    }


    public static void main(String[] args) {
        MainApplicaction.main(new String[0]);
    }
}

运行结果

文件搜索 hello world
数据搜索 hello world
rpc搜索 hello world
default search
>>>> jar main end

由此可以看到本项目的 META-INF/services的加载顺序在 jar包前面。

文章来源:https://blog.csdn.net/qq_45704048/article/details/135034338
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。