【案例代码】spi.zip
简述
SPI全称Service Provider Interface(服务提供者接口)是一种服务发现机制。为了被第三方实现或者扩展的API。它可以用于实现框架扩展,或者组件替换。
SPI 机制本质是将 接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载文件中的实现类,这样运行时可以动态的为接口替换实现类
简单一点来说,就是你在 META-INF/services 下面定义个文件,然后通过一个特殊的类加载器,启动的时候加载你定义文件中的类,这样就能扩展原有框架的功能
【借用一下网上图片】
案例
创建MyRequest接口
package com.itmck.springbootdorisk.spi;public interface MyRequest {String request(String param);}
创建MyRequestImpl实现MyRequest接口
package com.itmck.springbootdorisk.spi;import java.util.ServiceLoader;public class MyRequestImpl implements MyRequest {@Overridepublic String request(String param) {return "hello :" + param;}public static void main(String[] args) {ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);load.forEach(dto-> {String mck = dto.request("mck");System.out.println(mck);});}}
resource下面创建 META-INF/services 并创建接口名为 com.itmck.springbootdorisk.spi.MyRequest的文件,内容com.itmck.springbootdorisk.spi.MyRequestImpl
创建测试类
public class MyTest{public static void main(String[] args) {ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);load.forEach(dto-> {String mck = dto.request("mck");System.out.println(mck);});}}
结果 hello :mck
场景
sharding-jdbc 本身支持 AES 和 MD5 两种加密方式。但是,如果客户端不想用内置的两种加密,偏偏想用 RSA 算法呢?难道每加一种算法,sharding-jdbc 就要发个版本么?
sharding-jdbc 可不会这么干,首先提供出 Encryptor 加密接口,并引入 SPI 的机制,做到服务接口与服务实现分离的效果。如果客户端想要使用新的加密算法,只需要在客户端项目 META-INF/services 目录下定义接口的全限定名称文件,并在文件内写上加密实现类的全限定名
模拟
首先我们知道如果要让我们自己去做一个加密的程序,我们能做得加密是有限的,加入客户端想要使用新的加密手册,这时候怎么办呢?那何不直接交给客户一个SPI接口,用户只需要实现他就可以使用自己想要的效果。
创建springboot-do-risk服务端,先实现两种策略。
package com.itmck.springbootdorisk.spi;public interface MyRequest {/*** 用于返回接口实现的类型** @return*/String getRequestType();/*** 用于进行加密请求参数** @param param* @return*/String request(String param);}
模拟加密
package com.itmck.springbootdorisk.spi;public class MyRequestImpl implements MyRequest {@Overridepublic String getRequestType() {return "mck";}@Overridepublic String request(String param) {return "hello :" + param;}}
package com.itmck.springbootdorisk.spi;public class MyRequestImpl2 implements MyRequest {@Overridepublic String getRequestType() {return "mck2";}@Overridepublic String request(String param) {return "hello2 :" + param;}}
创建加密工厂
package com.itmck.springbootdorisk.factory;import com.itmck.springbootdorisk.spi.MyRequest;import org.springframework.beans.factory.InitializingBean;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import java.util.HashMap;import java.util.Map;import java.util.ServiceLoader;@Componentpublic class MyRequestFactory implements InitializingBean {@Value("${mck.request.type:mck}")private String requestType;private final Map<String, MyRequest> myRequestMap = new HashMap<>();@Overridepublic void afterPropertiesSet() throws Exception {//通过spi的发现机制进行服务的发现,将服务存放在map中ServiceLoader<MyRequest> load = ServiceLoader.load(MyRequest.class);load.forEach(myRequest -> myRequestMap.put(myRequest.getRequestType(), myRequest));System.out.println(myRequestMap);}public MyRequest getMyRequest() {return myRequestMap.getOrDefault(requestType, null);}}
resources下创建META-INF/services文件夹,并创建文件com.itmck.springbootdorisk.spi.MyRequest
com.itmck.springbootdorisk.spi.MyRequestImplcom.itmck.springbootdorisk.spi.MyRequestImpl2
注意:springboot项目打包到本地记得修改如下
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><mainClass>com.itmck.springbootdorisk.SpringbootDoRiskApplication</mainClass><skip>true</skip></configuration></plugin></plugins></build>
此时执行打包install到本地仓库即可
创建客户端并引入pomy依赖
<dependency><groupId>com.itmck</groupId><artifactId>springboot-do-risk</artifactId><version>0.0.1</version></dependency>
创建实现继承MyRequest
package com.example.demo11.spi;import com.itmck.springbootdorisk.spi.MyRequest;public class Mp implements MyRequest {@Overridepublic String getRequestType() {return "mp";}@Overridepublic String request(String param) {return "mp :"+param;}}
resources下创建META-INF/services文件夹,并创建文件com.itmck.springbootdorisk.spi.MyRequest
com.example.demo11.spi.Mp
application.properties中配置
mck.request.type=mp
主启动类
package com.example.demo11;import com.itmck.springbootdorisk.factory.MyRequestFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;@SpringBootApplicationpublic class Demo11Application {@Beanpublic MyRequestFactory myRequestFactory(){return new MyRequestFactory();}public static void main(String[] args) {SpringApplication.run(Demo11Application.class, args);}}
测试类
package com.example.demo11;import com.itmck.springbootdorisk.factory.MyRequestFactory;import com.itmck.springbootdorisk.spi.MyRequest;import org.junit.jupiter.api.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import java.util.ServiceLoader;@RunWith(SpringRunner.class)@SpringBootTestclass Demo11ApplicationTests {@Autowiredprivate MyRequestFactory myRequestFactory;@Testpublic void contextLoads() {MyRequest myRequest = myRequestFactory.getMyRequest();System.out.println(myRequest.request("ez"));}}
运行结果 mp :ez
通过上述最终达到客户端扩展的效果
