09-插件化策略模式

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
com.demo.mytest
├── strategy
│ ├── anno
│ │ ├── BaseDealAnno.java(注解)
│ ├── enums
│ │ ├── BasePluginEnum.java(枚举)
│ ├── factory
│ │ ├── BaseDealFactory.java
│ ├── listener
│ │ ├── ContextBaseDealListener.java(核心类)
│ ├── service
│ │ ├── impl
│ │ │ ├── BaseAudioService.java
│ │ │ ├── BaseDocumentService.java
│ │ │ ├── BaseImageService.java
│ │ └── BaseDealHandler.java(接口)
└── MytestApplication.java

实现

BaseDealAnno.java(注解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.demo.mytest.strategy.enums.BasePluginEnum;
import org.springframework.stereotype.Indexed;

import java.lang.annotation.*;

/**
* 插件处理业务注解
*
* @author Jerry(姜源)
* @date 2023/08/01 11:13
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface BaseDealAnno {
BasePluginEnum[] value();
}

BasePluginEnum.java(枚举)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 命中的业务类型
*/
public enum BasePluginEnum {
//文档
DOCUMENT,
//图片
IMAGE,
//链接
LINK,
//录音
AUDIO_RECORD,
//音频
AUDIO,
}

BaseDealFactory.java

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
import com.demo.mytest.strategy.enums.BasePluginEnum;
import com.demo.mytest.strategy.service.BaseDealHandler;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
* 针对不同插件的结果处理器
*/
@Slf4j
public class BaseDealFactory {

private static Map<BasePluginEnum, BaseDealHandler> handlerMap = new HashMap<>();

public static void registerStrategy(BasePluginEnum pluginEnum, BaseDealHandler handler) {
handlerMap.put(pluginEnum, handler);
}

public static BaseDealHandler loadHandler(BasePluginEnum pluginEnum) throws Exception {
if (handlerMap.containsKey(pluginEnum)) {
BaseDealHandler handler = handlerMap.get(pluginEnum);
log.info("loadHandler {} {}", pluginEnum, handler.getClass());
return handler;
} else {
throw new RuntimeException("未配置相应处理策略 " + pluginEnum);
}
}
}

ContextBaseDealListener.java(核心类)

项目启动就加载并注册。

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
36
37
38
39
40
41
42
43
import cn.hutool.extra.spring.SpringUtil;
import com.demo.mytest.strategy.anno.BaseDealAnno;
import com.demo.mytest.strategy.enums.BasePluginEnum;
import com.demo.mytest.strategy.factory.BaseDealFactory;
import com.demo.mytest.strategy.service.BaseDealHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
* spring 容器初始化监听器
*/
@Component
@Slf4j
public class ContextBaseDealListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
log.info("容器初始化完毕");

Map<String, Object> controllers = SpringUtil.getApplicationContext().getBeansWithAnnotation(BaseDealAnno.class);
for (Map.Entry<String, Object> entry : controllers.entrySet()) {
Object value = entry.getValue();
BaseDealHandler handler = (BaseDealHandler) value;

//获取注解
Class<?> aClass = AopUtils.getTargetClass(value);
BaseDealAnno Anno = aClass.getDeclaredAnnotation(BaseDealAnno.class);
log.info("加载:load beanName -> {}, {}", handler.getClass(), Anno.value());

if (null != Anno.value()) {
for (BasePluginEnum pluginEnum : Anno.value()) {
log.info("注册:register -> {}, {}", handler.getClass(), Anno.value());
BaseDealFactory.registerStrategy(pluginEnum, handler);
}
}
}
}

}

BaseDealHandler.java(接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.Map;

/**
* 通用service
*/
public interface BaseDealHandler<RES> {

/**
* 业务逻辑
*
* @param req 通用入参
*/
Object process(Map<String, RES> req);
}

BaseAudioService.java(可扩展多个)

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
import com.demo.mytest.strategy.anno.BaseDealAnno;
import com.demo.mytest.strategy.enums.BasePluginEnum;
import com.demo.mytest.strategy.service.BaseDealHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
* 音频处理
*/
@Slf4j
@Service
@BaseDealAnno(value = BasePluginEnum.AUDIO)
@RequiredArgsConstructor
public class BaseAudioService implements BaseDealHandler<String> {

@Override
public Object process(Map<String, String> req) {
log.info("process 音频处理完成。");
return new Object();
}
}

验证

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
import cn.hutool.json.JSONUtil;
import com.demo.mytest.strategy.enums.BasePluginEnum;
import com.demo.mytest.strategy.factory.BaseDealFactory;
import com.demo.mytest.strategy.service.BaseDealHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
class BaseAudioServiceTest {

@Test
void process() throws Exception {
BasePluginEnum plugin = BasePluginEnum.AUDIO;
BaseDealHandler baseDealHandler = BaseDealFactory.loadHandler(plugin);
StopWatch sw = new StopWatch();
sw.start();
Map<String, Object> req = new HashMap<>();
System.out.println("process start: req -> " + JSONUtil.toJsonStr(req));
Object resObj = baseDealHandler.process(req);
sw.stop();
System.out.println("process end " + sw.getTotalTimeSeconds() + ": res -> {}" + JSONUtil.toJsonStr(resObj));
}
}

2023-11-16 15:13:23.459 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 容器初始化完毕
2023-11-16 15:13:23.461 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 加载:load beanName -> class com.demo.mytest.strategy.service.impl.BaseAudioService, [AUDIO]
2023-11-16 15:13:23.464 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 注册:register -> class com.demo.mytest.strategy.service.impl.BaseAudioService, [AUDIO]
2023-11-16 15:13:23.465 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 加载:load beanName -> class com.demo.mytest.strategy.service.impl.BaseDocumentService, [DOCUMENT]
2023-11-16 15:13:23.465 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 注册:register -> class com.demo.mytest.strategy.service.impl.BaseDocumentService, [DOCUMENT]
2023-11-16 15:13:23.465 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 加载:load beanName -> class com.demo.mytest.strategy.service.impl.BaseImageService, [IMAGE]
2023-11-16 15:13:23.465 INFO 5043 — [ main] c.d.m.s.l.ContextBaseDealListener : 注册:register -> class com.demo.mytest.strategy.service.impl.BaseImageService, [IMAGE]
2023-11-16 15:13:23.469 INFO 5043 — [ main] c.d.m.s.s.impl.BaseAudioServiceTest : Started BaseAudioServiceTest in 1.382 seconds (JVM running for 2.726)
2023-11-16 15:13:23.643 INFO 5043 — [ main] c.d.m.strategy.factory.BaseDealFactory : loadHandler AUDIO class com.demo.mytest.strategy.service.impl.BaseAudioService
process start: req -> {}
2023-11-16 15:13:23.665 INFO 5043 — [ main] c.d.m.s.service.impl.BaseAudioService : process 音频处理完成。


09-插件化策略模式
https://janycode.github.io/2023/11/16/10_设计模式/09-插件化策略模式/
作者
Jerry(姜源)
发布于
2023年11月16日
许可协议