使用Spring特性实现接口多实现类的动态调用方式
作者:EmineWang 发布时间:2022-04-11 05:41:42
正好用到。mark一下背景
org.springframework.beans及org.springframework.context这两个包是Spring IoC容器的基础,其中重要的类有BeanFactory,BeanFactory是IoC容器的核心接口,其职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖关系。
ApplicationContext作为BeanFactory的子类,在Bean管理的功能上得到了很大的增强,也更易于与Spring AOP集成使用。
今天我们要讨论的并不是BeanFactory或者ApplicationContext的实现原理,而是对ApplicationContext的一种实际应用方式。
问题的提出
在实际工作中,我们经常会遇到一个接口及多个实现类的情况,并且在不同的条件下会使用不同的实现类。
从使用方式上看,有些类似SPI的用法,但是由于SPI的使用并不是太方便,那么怎么办呢?我们可以借助ApplicationContext的getBeansOfType来实现我们需要的结果。
首先我们看一下这个方法的签名
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
从上面的代码上我们可以看出来这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)。
接下来看看我们遇到的问题是什么?
"假设从A点到B点有多种交通方式,每种交通方式的费用不同,可以根据乘客的需要进行选择"(好吧,我承认这是个非常蹩脚的需求,但是可以联想一下类似的需求,比如支付方式、快递公司,在页面提供几个选项,业务代码根据选项的不同选择不同的实现类实例进行调用)。
实现
回到我们的例子,按照这个交通方式的需求,我们的设计如下:有一个交通方式的接口,接口有两个方式,一个查询费用、一个查询该交通方式的类型,同时,我们可以用一个枚举类型类标识交通类型。
我们还需要一个工厂类来根据交通类型标识查找该交通类型的Bean实例,从而使用该实例,获得交通类型的详细信息及该交通类型的操作。
代码如下
接口:
/**
* 交通方式
*/
public interface TrafficMode {
/**
* 查询交通方式编码
* @return 编码
*/
TrafficCode getCode();
/**
* 查询交通方式的费用,单位:分
* @return 费用
*/
Integer getFee();
}
枚举:
/**
* 交通类型枚举
*/
public enum TrafficCode {
TRAIN,
BUS
}
接口有两个实现类:
/**
* 汽车方式
*/
@Component
public class BusMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.BUS;
}
@Override
public Integer getFee() {
return 10000;
}
}
/**
* 火车方式
*/
@Component
public class TrainMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.TRAIN;
}
@Override
public Integer getFee() {
return 9000;
}
}
工厂类:
/**
* 交通方式工厂类
*/
@Component
public class TrafficModeFactory implements ApplicationContextAware {
private static Map<TrafficCode, TrafficMode> trafficBeanMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, TrafficMode> map = applicationContext.getBeansOfType(TrafficMode.class);
trafficBeanMap = new HashMap<>();
map.forEach((key, value) -> trafficBeanMap.put(value.getCode(), value));
}
public static <T extends TrafficMode> T getTrafficMode(TrafficCode code) {
return (T)trafficBeanMap.get(code);
}
}
验证
有了上面的代码之后,我们一起通过单元测试来看一下效果,单元测试代码片段如下:
@Test
public void testGetTrafficMode() {
TrafficMode mode = TrafficModeFactory.getTrafficMode(TrafficCode.BUS);
Assert.assertEquals(mode.getFee().intValue(), 10000);
mode = TrafficModeFactory.getTrafficMode(TrafficCode.TRAIN);
Assert.assertEquals(mode.getFee().intValue(), 9000);
}
运行之后的结果呢?必然是通过。
关于SPI
文章到这里,有同学可能会问:这和SPI有什么区别呢?SPI同样也能实现同样的功能啊。首先说明一下,SPI是JDK自带的功能,虽然历史是比较久远的了,但是不代表它不好,而且有些场景非SPI不可(比如JDBC)。
我们明确一下SPI是什么以及它的设计是用来做什么的。SPI的全名为Service Provider Interface(服务提供接口),因为这个是针对厂商或者插件的。比较经典的用法就是JDBC,java提供了标准的JDBC接口,每个数据库厂商提供自己的数据库驱动实现。
下面是JDBC驱动的接口
public interface Driver {
Connection connect(String var1, Properties var2) throws SQLException;
boolean acceptsURL(String var1) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String var1, Properties var2) throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
下面是MySQL的JDBC驱动实现的SPI配置
关于SPI我们点到为止,这里只是要说明SPI和我们前面例子中使用的AP具有不同的适用场景。
在前面的例子里,我们用的是什么呢?Spring的API,API的全称为Application Programming Interface(应用程序编程接口),在一个应用内部,使用API是非常便捷的方式,也是最直接的方式。
总结一下,就是编程中,我们使用API是最多的。当然我们也会使用SPI(虽然我们不是严格意义上的厂商或者插件),使用SPI也是在特定场景下为了解决问题的一种途径。
关于SPI更多的内容,以后在慢慢介绍吧。
【附】关于SPI的约定:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。
该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
jdk提供服务实现查找的一个工具类:java.util.ServiceLoader,通过其load方法,传入接口便能获得其实现类。
来源:https://blog.csdn.net/a718515028/article/details/72457436
猜你喜欢
- 云计算、大数据地快速发展催生了不少热门的应用及工具。作为老牌语言Java,其生态圈也出来了一些有关云服务、监控、文档分享方面的工具。本文总结
- JDBC Statement对象实例以下是利用以下三种查询以及打开和关闭说明的例子:boolean execute(String SQL)
- 前言本文的多租户是基于多数据库进行实现的,数据是通过不同数据库进行隔离。下面话不多说,来看看详细的介绍:MyCat 基本配置首先针对多租户配
- 下载maven 解压路径: 打开环境变量:右键此电脑-属性-高级系统设置-高级-环境变量添加以下系统变量:测试:win+
- 前言每次update Maven Project 的时候,看着进度条寸步难行,心里憋得十分难受,明显阻碍我学习的热情。 maven仓库默认在
- 1、synchronized的作用为了避免临界区的竞态条件发生,有多种手段可以达到目的。阻塞式的解决方案:synchronized,Lock
- 废话不多说了,直接给大家贴代码了,具体代码如下所示:<update id="updateAuditStateAndType&
- 一、java异常总结:异常就是程序运行时出现不正常运行情况1.异常由来:通过java的类的形式对现实事物中问题的描述,并封住成了对象其实就是
- 一、获取接口请求的数据可以在Interceptor的afterCompletion中实现但是要重写RequestWrapper代码记录如下:
- 目前为止,许多编程语言和工具都包含对正则表达式的支持,C#也不例外,C#基础类库中包含有一个命名空间(System.Text.Regular
- 网络中数据传输经常是xml或者json,现在做的一个项目之前调其他系统接口都是返回的xml格式,刚刚遇到一个返回json格式数据的接口,通过
- controller传boolean形式值@GetMapping("/check-cart")public List&l
- 对于初学java的同学来说,第一件事不是写hello world,而是搭建好java开发环境,下载jdk,安装,配置环境变量。这些操作在xp
- SpringBoot 项目启动之后执行自定义方法的两种方式在测试配置中心的配置时,想在项目启动成功之后打印配置项,然后需要执行自定义的类一般
- 在开发过程中,不少有Spring Aop的使用,在面向切面编程时,我们会使用< aop:aspect>;在进行事务管理时,我们会
- 前言在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法。volatile
- 本文实例为大家分享了java实现面板之间切换的具体代码,供大家参考,具体内容如下如图:关键技术:事件监听,设置显示面板,重新刷新验证。set
- final File imageFile = new File(getCacheDir().getPath() + "/img/&
- 目录前言正文自定义NameSpaceHandler自定义schemaParserDecorator总结前言在早期基于Xml配置的Spring
- 前言:在没有接触java8的时候,我们遍历一个集合都是用循环的方式,从第一条数据遍历到最后一条数据,现在思考一个问题,为什么要使用循环,因为