LI YAO'S BLOG

有趣的人生, 一半家长里短, 一半山川胡海

使用Spring特性实现接口多实现类的动态调用

问题的提出

在实际工作中,我们经常会遇到一个接口及多个实现类的情况,并且在不同的条件下会使用不同的实现类。从使用方式上看,有些类似SPI的用法,但是由于SPI的

使用并不是太方便,那么怎么办呢?我们可以借助ApplicationContext的getBeansOfType来实现我们需要的结果。

首先我们看一下这个方法的签名

1
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;

从上面的代码上我们可以看出来这个方法能返回一个接口的全部实现类(前提是所有实现类都必须由Spring IoC容器管理)。

org.springframework.beans及org.springframework.context这两个包是Spring IoC容器的基础,

其中重要的类有BeanFactory,BeanFactory是IoC容器的核心接口,其职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖关系。

ApplicationContext作为BeanFactory的子类,在Bean管理的功能上得到了很大的增强,也更易于与Spring AOP集成使用。

ApplicationContext的一种实际应用方式,如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方

法,将bean所在应用上下文引用传入进来。

工作实例

我们有N多个报表,每个报表需要实现不同的查询功能,同时,我们可以用一个枚举类型类标识不同的报表,以下是具体实现

枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public enum ReportEnum {

R004_FISP("R004_FISP"),

R004_PBOC("R004_PBOC"),

R004_AMAC("R004_AMAC"),

R004_CUS("R004_CUS"),

R004_SAC("R004_SAC");

private final String type;

ReportEnum(String type) {
this.type = type;
}

/**
* 实现字符串转枚举的静态方法
*
* @param type 类型
* @return {@link ReportEnum}
*/
public static ReportEnum fromType(String type) {
if (type == null) {
return null;
}
return EnumSet.allOf(ReportEnum.class).stream()
.filter(s -> s.toString().equals(type))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Invalid type: " + type));
}

}

接口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AssociatedProcessService {

/**
* 报表枚举
*
* @return {@link ReportEnum}
*/
ReportEnum getCode();

/**
* 获取细节信息
*
* @return {@link List}<{@link AssociatedProcessDetailVo}>
*/
List<AssociatedProcessDetailVo> getDetail(Map<String, Object> request) throws Exception;

}

实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class AssProFispServiceImpl implements AssociatedProcessService {

@Override
public ReportEnum getCode() {
return ReportEnum.R004_FISP;
}

@Override
public List<AssociatedProcessDetailVo> getDetail(Map<String, Object> request) throws Exception {
...
}

}

工厂类/工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class SpringContextUtil implements ApplicationContextAware {

private static Map<ReportEnum, AssociatedProcessService> associatedProcessBeanMap;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AssociatedProcessService> map = applicationContext.getBeansOfType(AssociatedProcessService.class);
associatedProcessBeanMap = new EnumMap<>(ReportEnum.class);
map.forEach((key, value) -> associatedProcessBeanMap.put(value.getCode(), value));
}

public static <T extends AssociatedProcessService> T getAssociatedProcessService(ReportEnum code) {
return (T) associatedProcessBeanMap.get(code);
}

}

控制器调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 查询关联流程详情
*
* @param request 基础数据
* @return {@link ObjectRestResponse}
*/
@ResourceDeclare(sysId = BaseConst.SYS_ID, note = "", type = BaseConst.PUBLIC_URL)
@PostMapping("/queryAssociatedProcessesDetail")
@ApiOperation(value = "查询关联流程详情", notes = "查询关联流程详情")
public ObjectRestResponse<List<AssociatedProcessDetailVo>> queryAssociatedProcessesDetail(@RequestBody Map<String, Object> request) {
try {
log.info("开始处理-查询关联流程详情接口逻辑, request:{}", request);
String reportType = (String) request.get("reportType");
return new ObjectRestResponse<List<AssociatedProcessDetailVo>>()
.data(SpringContextUtil.getAssociatedProcessService(ReportEnum.fromType(reportType)).getDetail(request))
.rel(true);
} catch (Exception e) {
throw new BaseException(e.getMessage(), e);
}
}
赞赏一杯咖啡