【案例代码】spi.zip

简述

SPI全称Service Provider Interface(服务提供者接口)是一种服务发现机制。为了被第三方实现或者扩展的API。它可以用于实现框架扩展,或者组件替换。

SPI 机制本质是将 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载文件中的实现类,这样运行时可以动态的为接口替换实现类
简单一点来说,就是你在 META-INF/services 下面定义个文件,然后通过一个特殊的类加载器,启动的时候加载你定义文件中的类,这样就能扩展原有框架的功能

【借用一下网上图片】
image.png

案例

创建MyRequest接口

  1. package com.itmck.springbootdorisk.spi;
  2. public interface MyRequest {
  3. String request(String param);
  4. }

创建MyRequestImpl实现MyRequest接口

  1. package com.itmck.springbootdorisk.spi;
  2. import java.util.ServiceLoader;
  3. public class MyRequestImpl implements MyRequest {
  4. @Override
  5. public String request(String param) {
  6. return "hello :" + param;
  7. }
  8. public static void main(String[] args) {
  9. ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);
  10. load.forEach(dto-> {
  11. String mck = dto.request("mck");
  12. System.out.println(mck);
  13. });
  14. }
  15. }

resource下面创建 META-INF/services 并创建接口名为 com.itmck.springbootdorisk.spi.MyRequest的文件,内容com.itmck.springbootdorisk.spi.MyRequestImpl

创建测试类

  1. public class MyTest{
  2. public static void main(String[] args) {
  3. ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);
  4. load.forEach(dto-> {
  5. String mck = dto.request("mck");
  6. System.out.println(mck);
  7. });
  8. }
  9. }

结果 hello :mck

场景

sharding-jdbc 本身支持 AES 和 MD5 两种加密方式。但是,如果客户端不想用内置的两种加密,偏偏想用 RSA 算法呢?难道每加一种算法,sharding-jdbc 就要发个版本么?
sharding-jdbc 可不会这么干,首先提供出 Encryptor 加密接口,并引入 SPI 的机制,做到服务接口与服务实现分离的效果。如果客户端想要使用新的加密算法,只需要在客户端项目 META-INF/services 目录下定义接口的全限定名称文件,并在文件内写上加密实现类的全限定名

模拟
首先我们知道如果要让我们自己去做一个加密的程序,我们能做得加密是有限的,加入客户端想要使用新的加密手册,这时候怎么办呢?那何不直接交给客户一个SPI接口,用户只需要实现他就可以使用自己想要的效果。

创建springboot-do-risk服务端,先实现两种策略。

  1. package com.itmck.springbootdorisk.spi;
  2. public interface MyRequest {
  3. /**
  4. * 用于返回接口实现的类型
  5. *
  6. * @return
  7. */
  8. String getRequestType();
  9. /**
  10. * 用于进行加密请求参数
  11. *
  12. * @param param
  13. * @return
  14. */
  15. String request(String param);
  16. }


模拟加密

  1. package com.itmck.springbootdorisk.spi;
  2. public class MyRequestImpl implements MyRequest {
  3. @Override
  4. public String getRequestType() {
  5. return "mck";
  6. }
  7. @Override
  8. public String request(String param) {
  9. return "hello :" + param;
  10. }
  11. }
  1. package com.itmck.springbootdorisk.spi;
  2. public class MyRequestImpl2 implements MyRequest {
  3. @Override
  4. public String getRequestType() {
  5. return "mck2";
  6. }
  7. @Override
  8. public String request(String param) {
  9. return "hello2 :" + param;
  10. }
  11. }

创建加密工厂

  1. package com.itmck.springbootdorisk.factory;
  2. import com.itmck.springbootdorisk.spi.MyRequest;
  3. import org.springframework.beans.factory.InitializingBean;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.stereotype.Component;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. import java.util.ServiceLoader;
  9. @Component
  10. public class MyRequestFactory implements InitializingBean {
  11. @Value("${mck.request.type:mck}")
  12. private String requestType;
  13. private final Map<String, MyRequest> myRequestMap = new HashMap<>();
  14. @Override
  15. public void afterPropertiesSet() throws Exception {
  16. //通过spi的发现机制进行服务的发现,将服务存放在map中
  17. ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);
  18. load.forEach(myRequest -> myRequestMap.put(myRequest.getRequestType(), myRequest));
  19. System.out.println(myRequestMap);
  20. }
  21. public MyRequest getMyRequest() {
  22. return myRequestMap.getOrDefault(requestType, null);
  23. }
  24. }

resources下创建META-INF/services文件夹,并创建文件com.itmck.springbootdorisk.spi.MyRequest

  1. com.itmck.springbootdorisk.spi.MyRequestImpl
  2. com.itmck.springbootdorisk.spi.MyRequestImpl2

注意:springboot项目打包到本地记得修改如下

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-maven-plugin</artifactId>
  6. <configuration>
  7. <mainClass>com.itmck.springbootdorisk.SpringbootDoRiskApplication</mainClass>
  8. <skip>true</skip>
  9. </configuration>
  10. </plugin>
  11. </plugins>
  12. </build>

此时执行打包install到本地仓库即可

创建客户端并引入pomy依赖

  1. <dependency>
  2. <groupId>com.itmck</groupId>
  3. <artifactId>springboot-do-risk</artifactId>
  4. <version>0.0.1</version>
  5. </dependency>

创建实现继承MyRequest

  1. package com.example.demo11.spi;
  2. import com.itmck.springbootdorisk.spi.MyRequest;
  3. public class Mp implements MyRequest {
  4. @Override
  5. public String getRequestType() {
  6. return "mp";
  7. }
  8. @Override
  9. public String request(String param) {
  10. return "mp :"+param;
  11. }
  12. }

resources下创建META-INF/services文件夹,并创建文件com.itmck.springbootdorisk.spi.MyRequest

  1. com.example.demo11.spi.Mp

application.properties中配置

  1. mck.request.type=mp


主启动类

  1. package com.example.demo11;
  2. import com.itmck.springbootdorisk.factory.MyRequestFactory;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.context.annotation.Bean;
  6. @SpringBootApplication
  7. public class Demo11Application {
  8. @Bean
  9. public MyRequestFactory myRequestFactory(){
  10. return new MyRequestFactory();
  11. }
  12. public static void main(String[] args) {
  13. SpringApplication.run(Demo11Application.class, args);
  14. }
  15. }

测试类

  1. package com.example.demo11;
  2. import com.itmck.springbootdorisk.factory.MyRequestFactory;
  3. import com.itmck.springbootdorisk.spi.MyRequest;
  4. import org.junit.jupiter.api.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. import java.util.ServiceLoader;
  10. @RunWith(SpringRunner.class)
  11. @SpringBootTest
  12. class Demo11ApplicationTests {
  13. @Autowired
  14. private MyRequestFactory myRequestFactory;
  15. @Test
  16. public void contextLoads() {
  17. MyRequest myRequest = myRequestFactory.getMyRequest();
  18. System.out.println(myRequest.request("ez"));
  19. }
  20. }

运行结果 mp :ez

通过上述最终达到客户端扩展的效果