前言
偶然看到一篇微信公众号文章的分享 https://mp.weixin.qq.com/s/11VKseROGVcJHPCJ8xQ3XA,感觉挺有意思,在这里记录下。
为什么感觉有意思呢?是因为它通过spring 提供的ServiceLocatorFactoryBean 来实现服务定位模式,将服务的提供者和具体实现进行了解耦,你可以使用简单工厂模式来实现,但是不够友好,原因在于你用工厂模式的时候需要在工厂类里面维护创建具体实现类的创建,非真正意义的上的解耦,而本文要介绍的就能做到这一点,如下是示例。
示例:依据不同的文件类型来模拟解析文件
一、代码示例
1、我需要定义一个解析工厂,没错他就是接口,没有具体实现
// 解析工厂
public interface ParserFactory {
Parser getParser(ContentType contentType);
}
// 文件类型枚举
public enum ContentType {
JSON,
CSV
}
2、我需要一个解析接口,用于不同类型的文件解析
public interface Parser {
List parse(Reader r);
}
3、模拟实现两个解析类,CSV/JSON
@Component("CSV")
public class CSVParser implements Parser {
@Override
public List parse(Reader r) {
System.out.println("parser csv file");
return null;
}
}
@Component("JSON")
public class JSONParser implements Parser {
@Override
public List parse(Reader r) {
System.out.println("parser json file");
return null;
}
}
4、以上都具备了,那我要咋么用呢?这时需要有个客户端类帮助我们获取解析器类型
@Service
public class Client {
@Autowired(required = false)
private ParserFactory parserFactory;
public List getAll(ContentType contentType) {
// 关键点,直接根据类型获取
return parserFactory
.getParser(contentType)
.parse(null); // 传入不同的文件读取Reader
}
}
有么有注意到,这里的parserFactory 我只定义了接口,并没有实现,它是怎么注入的?
5、通过ServiceLocatorFactoryBean注入ParserFactory
@Configuration
public class ParserConfig {
@Bean("parserFactory")
public FactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
// 设置服务定位接口
factoryBean.setServiceLocatorInterface(ParserFactory.class);
return factoryBean;
}
}
6、现在这些接口以及client都有了,我要怎么用呢?很简单,如下
@RestController
@RequestMapping("/test")
public class MyController{
@Autowired
private Client client;
@Operation(summary = "测试接口调用")
@GetMapping("/{type}")
public String test(@PathVariable String type) throws Exception {
client.getAll(ContentType.valueOf(type.toUpperCase()));
return "ok";
}
}
到这里,你可以发起请求用于测试:curl -X GET “http://localhost:8080/test/csv”
二、原理探究
1.ServiceLocatorFactoryBean 是什么?ParserFactory它只是一个接口什么也没干,为什么就能找到Parser的具体实现类呢?
这时因为有个内部类实现 ServiceLocatorInvocationHandler 它利用了我们提供的ContentType,也即为bean的name来获取具体的实现类的,
private Object invokeServiceLocatorMethod(Method method, Object[] args) throws Exception {
Class<?> serviceLocatorMethodReturnType = this.getServiceLocatorMethodReturnType(method);
try {
// 获取我们传入的bean name
String beanName = this.tryGetBeanName(args);
Assert.state(ServiceLocatorFactoryBean.this.beanFactory != null, "No BeanFactory available");
// 如果 bean name不为空则通过beanFactory去加载指定名称的bean实例
return StringUtils.hasLength(beanName) ? ServiceLocatorFactoryBean.this.beanFactory.getBean(beanName, serviceLocatorMethodReturnType) : ServiceLocatorFactoryBean.this.beanFactory.getBean(serviceLocatorMethodReturnType);
} catch (BeansException var5) {
if (ServiceLocatorFactoryBean.this.serviceLocatorExceptionConstructor != null) {
throw ServiceLocatorFactoryBean.this.createServiceLocatorException(ServiceLocatorFactoryBean.this.serviceLocatorExceptionConstructor, var5);
} else {
throw var5;
}
}
}
比如我们这里为JSONParser 起的名字叫 JSON,就是通过它去加载 JSONParser 然后执行具体的代码。
@Component("JSON")
public class JSONParser implements Parser {
@Override
public List parse(Reader r) {
System.out.println("parser json file");
return null;
}
}
如下图所示:
2. ServiceLocatorFactoryBean的作用
其实ServiceLocatorFactoryBean 用于查找 Bean 的。我们提供的工厂类交给他通过bean name来获取具体实现的单例bean,从而实现业务和定义的解耦,非常省事。
总结
服务定位模式Service Locator Pattern来解决,它帮助我们消除紧耦合实现及其依赖性,并提出将服务与其具体类解耦。
参考:https://www.cnblogs.com/qiushuiyu-108/p/17140900.html