SPI,即Service Provider Interface,是一种服务发现机制,它允许第三方为应用程序提供插件式的扩展。SPI的核心优势在于其解耦性,使得应用程序可以在运行时动态地加载模块或插件,而无需在编译时硬编码这些实现。这种机制为Java应用程序提供了一种解耦的方式来扩展功能,具有以下显著好处:
- 松耦合性:SPI机制使得应用程序与服务提供者之间实现了解耦。应用程序可以在运行时动态加载实现类,而无需在编译时将实现类硬编码到代码中。这种松耦合性使得应用程序更加灵活,易于扩展和适应变化。
- 扩展性:通过SPI,应用程序可以为同一个接口定义多个实现类。这意味着开发者可以根据实际业务需求,启用框架的扩展功能或替换框架的组件,从而实现更加个性化的定制。
- 易于使用:使用SPI,应用程序只需要定义接口并指定实现类的类名,即可轻松地使用新的服务提供者。这种简单的使用方式降低了开发者的学习成本,提高了开发效率。
SPI在Spring中的使用
在Spring框架中,尤其是Spring Boot,服务发现与SPI机制有些类似,但提供了更高级和自动化的功能。Spring Boot通过其依赖注入和自动配置机制,简化了服务发现的过程,并提供了更多的灵活性和可扩展性。
- 自动装配和依赖注入:Spring Boot使用自动装配和依赖注入的机制来将服务实现注入到需要使用它们的地方。这意味着开发者无需手动创建和配置服务实例,Spring容器会自动完成这些工作。
- 简化配置:与Java SPI需要手动编辑配置文件不同,Spring Boot通过注解和约定大于配置的原则,大大简化了配置过程。开发者只需使用适当的注解标记实现类,Spring容器就会自动将它们注册为bean,并在需要时进行装配。
- 条件化装配:Spring Boot还提供了条件化装配的机制,允许根据特定的条件来加载和装配服务实现。这使得开发者可以更加灵活地控制服务的创建和使用。
下面根据具体的业务场景和代码来说明如何解耦和消除IF的
假设场景: 在一个相同的业务下,a地区需要实现政策A, b地区需要实现政策B, c地区实现政策C ,注意:政策A,B,C之间大体相同但是细节不一致.同时不确定后续是否会新增地区以及对应政策,或者现有地区的政策是否会发生变化…
在这种场景下,如果我们不使用SPI机制,我们不得写很多 if
来判断不同的地区, 以及可能出现的同地区的多个政策,更甚的情况下我们可能不得不修改现有的代码以便兼容最新的政策,最后的结果就是屎山.
如果使用SPI机制呢? 直接上代码(MVC架构,SpringBoot)
controller 模拟前端请求, 没有重点,调用service
package com.huej.springboot3st.controller;
import com.huej.springboot3st.service.TestService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
private final TestService testService;
public TestController(TestService testService) {
this.testService = testService;
}
/**
* 调用controller->service->policy
* @param param 参数
* @return 返回值
*/
@GetMapping("test/{param}")
public String test(@PathVariable String param){
return testService.test(param);
}
}
serviceImpl 作为分发角色, 使用spring的构造注入注入所有能提供的服务的实现类
package com.huej.springboot3st.service.impl;
import com.huej.springboot3st.service.TestService;
import com.huej.springboot3st.service.policy.PolicyXXXX;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class TestServiceImpl implements TestService {
private final Map<String, PolicyXXXX> subPolicyMap = new HashMap<>();
/**
* 通过构造方法注入所有的PolicyXXXXImpls
* @param PolicyXXXXImpls
*/
public TestServiceImpl(List<PolicyXXXX> PolicyXXXXImpls) {
for (PolicyXXXX policyXXXXImpl : PolicyXXXXImpls) {
if (subPolicyMap.containsKey(policyXXXXImpl.getBusinessNo())) {
// log.error("存在交易编号相同的BO,请检查. 编号:" + ldrgDrgSetlBO.getBusinessNo());
continue;
}
subPolicyMap.put(policyXXXXImpl.getBusinessNo(), policyXXXXImpl);
}
}
/**
* 由于是根据交易编号来区分不同的业务, 所以这里的param就是交易编号, 例如: A, B, C
* 不用使用IF判断 具体业务又谁来实现 , 而是又我们的实现类判断我能提供什么样子服务
*/
@Override
public String test(String param) {
PolicyXXXX policyXXXX = subPolicyMap.get(param);
if (policyXXXX == null) {
// log.error("不存在交易编号为" + param + "的BO,请检查.");
return null;
}
return policyXXXX.doSomething();
}
}
PolicyXXXX 接口:定义所有的公共逻辑 , 作为TestServiceImpl也只调用了这个接口 TestServiceImpl不关注 具体的实现
PolicyAbstract 抽象类: 政策A,B,C的公共逻辑可以放在这里,同时具体的实现如PolicyC 可以重写对应的方法
PolicyA,PolicyB.PolicyC 继承PolicyAbstract 实现PolicyXXXX , 作为具体的业务实现
package com.huej.springboot3st.service.policy;
public interface PolicyXXXX {
/**
* 返回具体的业务编号, 用于区分不同的业务, 实现类能提供什么样子的业务编号, 由实现类自己决定
* @return 返回值
*/
String getBusinessNo();
/**
* 具体的业务逻辑
* @return 返回值
*/
String doSomething();
}
package com.huej.springboot3st.service.policy.impl;
public abstract class PolicyAbstract {
/**
* 提取出政策A, B, C的公共逻辑
* @return 返回值
*/
public String common(){
return "公共的逻辑-";
}
}
package com.huej.springboot3st.service.policy.impl;
import com.huej.springboot3st.service.policy.PolicyXXXX;
import org.springframework.stereotype.Service;
@Service
public class PolicyA extends PolicyAbstract implements PolicyXXXX {
/**
* 我能提供A政策的实现
* @return
*/
@Override
public String getBusinessNo() {
return "A";
}
/**
* 具体的业务逻辑
* @return
*/
@Override
public String doSomething() {
return common()+"业务A的逻辑";
}
}
package com.huej.springboot3st.service.policy.impl;
import com.huej.springboot3st.service.policy.PolicyXXXX;
import org.springframework.stereotype.Service;
@Service
public class PolicyB extends PolicyAbstract implements PolicyXXXX {
/**
* 我能提供B政策的实现
* @return
*/
@Override
public String getBusinessNo() {
return "B";
}
/**
* 具体的业务逻辑
* @return
*/
@Override
public String doSomething() {
return common()+"业务B的逻辑";
}
}
package com.huej.springboot3st.service.policy.impl;
import com.huej.springboot3st.service.policy.PolicyXXXX;
import org.springframework.stereotype.Service;
@Service
public class PolicyC extends PolicyAbstract implements PolicyXXXX {
/**
* 我能提供C政策的实现
* @return
*/
@Override
public String getBusinessNo() {
return "C";
}
/**
* 具体的业务逻辑
* @return
*/
@Override
public String doSomething() {
return common()+"业务C的逻辑";
}
/**
* 如果C政策需要修改公共逻辑, 可以重写父类的公共逻辑
* @return
*/
@Override
public String common(){
return "修改后的公共的逻辑(C)";
}
}
最终的效果
如此我们能够在不使用if语句的前提下,实现三个政策的自动分发。而且,这种设计的扩展性极佳:每当我们需要添加新的政策时,只需简单地创建一个新的类(比如PolicyD如下代码),而无需修改其他任何现有代码。这样,系统就能自动地识别并分发新政策,从而达到了很高的解耦水平。这种设计不仅简化了代码结构,还大大提高了系统的可维护性和灵活性
package com.huej.springboot3st.service.policy.impl;
import com.huej.springboot3st.service.policy.PolicyXXXX;
import org.springframework.stereotype.Service;
@Service
public class PolicyD extends PolicyAbstract implements PolicyXXXX {
/**
* 我能提供B政策的实现
* @return
*/
@Override
public String getBusinessNo() {
return "D";
}
/**
* 具体的业务逻辑
* @return
*/
@Override
public String doSomething() {
return common()+"业务D的逻辑";
}
}