java之SPI机制与Spring SPI扩展

java之SPI机制

前言:本文根据其他文章和文档理解整理,非原创,首先对各位作者表示感谢

java中API的含义与其他语言的API含义类似,提供功能性API接口供开发人员使用。

java中的SPI机制是java语言特有的,SPI机制用于框架开发人员根据一个接口规范按照需要实现不同的功能,以扩展框架的功能。

以java.sql.Driver接口为例,我们的spring-boot-web项目调用java.sql.Driver接口提供的方法(也就是在调用API接口)。

当我们使用不同的数据库时,各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑,客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可,客户端会通过SPI机制自动加载对应的jdbc服务。Mysql的则是com.mysql.jdbc.Drive,Oracle则是oracle.jdbc.driver.OracleDriver。

优点:SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。

另外,它弥补了类加载双亲委派模型的局限、做了很好的补充。

jdk自带的SPI机制

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个不带参数的构造方法;
示例代码

步骤1、定义一组接口 (假设是org.foo.demo.Animal),并写出接口的一个或多个实现,(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。

public interface Animal {
    void shout();
}
public class Cat implements Animal {
    @Override
    public void shout() {
        System.out.println("miao miao");
    }
}
public class Dog implements Animal {
    @Override
    public void shout() {
        System.out.println("wang wang");
    }
}

步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.Animal文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。

文件位置

- src
    -main
        -resources
            - META-INF
                - services
                    - org.foo.demo.Animal
复制代码

文件内容

org.foo.demo.animal.Dog
org.foo.demo.animal.Cat
复制代码

步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Animal> shouts = ServiceLoader.load(Animal.class);
        for (Animal s : shouts) {
            s.shout();
        }
    }
}

代码输出:

wang wang
miao miao

链接:https://juejin.cn/post/6844903679431016456
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上述定义的接口实现类没有实例成员变量,当我们需要spi加载一个有实例成员变量的实现类时该怎么办,如下所示

public interface Animal {
    void shout();
}
public class Cat implements Animal {
    private String name;
    public Cat(String name) {
        this.name=name;
    }
    @Override
    public void shout() {
        System.out.println(this.name+":miao miao");
    }
}

如果简单的SPI加载,必然会报错,没有对应的构造器。

这就需要借助AnimalFactory接口,定义一个create方法去生成想要的类,

// 改造一下Cat和Dog的构造方法
public class Cat implements Animal {
    private String name;
    public Cat(Map<Class<?>, String> catalogWithName) {
        this.name = catalogWithName.get(Cat.class);
    }
    @Override
    public void shout() {
        System.out.println(this.name + ":miao miao");
    }
}
public class Dog implements Animal {
    private final String name;
    public Dog(Map<Class<?>, String> catalogWithName) {
        this.name = catalogWithName.get(Dog.class);
    }
    @Override
    public void shout() {
        System.out.println(this.name+":wang wang");
    }
}
// 工厂类生成Cat和Dog
public interface AnimalFactory {
    Animal create(Map<Class<?>, String> catalogWithName);
}

public class CatFactory implements AnimalFactory {

    public Cat create(Map<Class<?>, String> catalogWithName) {
        return new Cat(catalogWithName);
    }
}

public class DogFactory implements AnimalFactory {

    public Dog create(Map<Class<?>, String> catalogWithName) {
        return new Dog(catalogWithName);
    }
}

使用如下:

  • src
    -main
    -resources
    - META-INF
    - services
    - org.foo.demo.AnimalFactory

文件内容为:

org.foo.demo.animal.CatFactory
org.foo.demo.animal.DogFactory

demo里面获得想要的Cat和Dog

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<AnimalFactory> factories = ServiceLoader.load(AnimalFactory.class);
        Map<Class<?>, String> catalogWithName = new HashMap<>();
        catalogWithName.put(Dog.class, "da huang");
        catalogWithName.put(Cat.class, "hello kitty");
        for (AnimalFactory factory : factories) {
            Animal animal = factory.create(catalogWithName);
            animal.shout();
        }
    }
}
// 输出
// hello kitty:miao miao
// da huang:wang wang
    
springboot 中的SPI扩展机制
  • 在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,找到标识为EnableAutoConfiguration的配置类,解析properties文件,然后将其中定义的bean注入到Spring容器。

  • 需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

  • 跟Java的SPI更多的是为了面向接口编程和克服双亲委派局限不同,Spring的这种SPI可能更多的是体现一种框架的可扩展性:在springboot工程中我们都知道,默认是会加载主类所在目录及其所有子目录下的自动注入bean的,比如主类在com.esa.stack,则com.esa.stack.controller,com.esa.stack.service等等都会加载并注入;但如果第三方开发的jar包、大概率情况下目录是跟工程目录不同的,比如esaStack公司的合作伙伴lb公司开发了一个组件用的是com.lb.*`,这个组件的类就没法自动的注入到spring,而通过上面讲的SPI机制就可以解决这个问题。

  • 详细讲解参看这位大佬的文档:

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值