【案例代码】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 {
@Override
public 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 {
@Override
public String getRequestType() {
return "mck";
}
@Override
public String request(String param) {
return "hello :" + param;
}
}
package com.itmck.springbootdorisk.spi;
public class MyRequestImpl2 implements MyRequest {
@Override
public String getRequestType() {
return "mck2";
}
@Override
public 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;
@Component
public class MyRequestFactory implements InitializingBean {
@Value("${mck.request.type:mck}")
private String requestType;
private final Map<String, MyRequest> myRequestMap = new HashMap<>();
@Override
public 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.MyRequestImpl
com.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 {
@Override
public String getRequestType() {
return "mp";
}
@Override
public 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;
@SpringBootApplication
public class Demo11Application {
@Bean
public 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)
@SpringBootTest
class Demo11ApplicationTests {
@Autowired
private MyRequestFactory myRequestFactory;
@Test
public void contextLoads() {
MyRequest myRequest = myRequestFactory.getMyRequest();
System.out.println(myRequest.request("ez"));
}
}
运行结果 mp :ez
通过上述最终达到客户端扩展的效果