栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java之SPI机制与Spring SPI扩展

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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 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, 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, 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, String> catalogWithName);
}

public class CatFactory implements AnimalFactory {

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

public class DogFactory implements AnimalFactory {

    public Dog create(Map, 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 factories = ServiceLoader.load(AnimalFactory.class);
        Map, 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机制就可以解决这个问题。

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/693703.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号