同学们可以看看,项目中是不是有这样类似的代码
问题不是很大,但是可能经常要改,只能一个小哥维护
public static void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
switch (httpMethod) {
case GET:
//doGet(paramsMap);
case PUT:
//doPut(paramsMap);
case HEAD:
//doHead(paramsMap);
case POST:
//doPost(paramsMap);
case PATCH:
//doPatch(paramsMap);
case TRACE:
//doTrace(paramsMap);
case DELETE:
//doDelete(paramsMap);
case OPTIONS:
//doOptions(paramsMap);
}
}
废话不多说直接上代码
先定义一个接口
public interface HttpMethodHandler {
void sendRequest(Map<String, Object> map);
}
为get请求写一个实现类
@Component
public class GetMethodHandler implements HttpMethodHandler{
@Override
public void sendRequest(Map<String, Object> map) {
doGet(map);
}
}
然后修改一下调用的方法,只需要传入一个处理器的clazz即可
public static void sendHttpRequest(Class<? extends HttpMethodHandler> clazz, Map<String, Object> paramsMap) {
MethodHandler handler = SpringUtil.getBean(clazz);
if(handler == null){
throw new RuntimeException("no such http hanlder");
}
handler.sendRequest(paramsMap);
这是一个比较简单的基于Spring的策略设计模式的实现.
有个小问题,那就是传入clazz这种方式只有开发的人比较熟悉,有什么什么实现类可以干什么什么事情,如果是我们日常工作的时候需要其他小伙伴调用的话,会有一些理解上的出入,一般情况下,我们使用枚举比较多.
/**
* 发送http请求
*
* @param httpMethod HTTP方法
* @param paramsMap 参数贴图
*/
public void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
}
在枚举文件稍加文档说明就可以把事情说清楚
所以我们需要将枚举和策略实现类进行一个绑定,可以使用这种简单粗暴的方式,问题也不是很大,多一个策略就多一个枚举值,没毛病
public enum HttpMethod {
GET(GetMethodHandler.class),
POST(PostMethodHandler.class);
public Class<? extends HttpMethodHandler> getClazz() {
return clazz;
}
private final Class<? extends HttpMethodHandler> clazz;
HttpMethod(Class<? extends HttpMethodHandler> clazz) {
this.clazz = clazz;
}
}
代码如下,使用SpringUtil通过class获取Bean
public static void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
HttpMethodHandler handler = SpringUtil.getBean(httpMethod.getClazz());
handler.sendRequest(paramsMap);
}
实际上我们项目中,使用的另一种方法: 注解 + 枚举
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpMethodStrategy {
HttpMethod value();
}
实现类添加我们定义的注解,并指定类型
@Component
@HttpMethodStrategy(HttpMethod.GET)
public class GetMethodHandler implements HttpMethodHandler {
@Override
public void sendRequest(Map<String, Object> map) {
//doGet(map);
}
}
写一个容器Map,将策略都塞进Map里,key是枚举,value是Bean.
简单说明一下,首先我们通过getBeanByAnnotation()方法获取方法上含有指定注解的Bean,这些Bean可能并没有实现HttpMethodHandler接口,这里需要我们开发自己保证.
然后我们将HttpMethod做为key,Handler处理器方法作为value,这样我们就做好一个枚举转换成策略实现类的一个工具类
@Component
public class HttpMethodContext implements ApplicationListener<ContextRefreshedEvent> {
private static final Map<HttpMethod, HttpMethodHandler> handlerMap = new EnumMap<HttpMethod, HttpMethodHandler>(HttpMethod.class);
public static HttpMethodHandler getHandler(HttpMethod httpMethod) {
return handlerMap.get(httpMethod);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
Map<String, Object> beans = contextRefreshedEvent.getApplicationContext().getBeansWithAnnotation(HttpMethodStrategy.class);
beans.forEach((key, bean) -> {
HttpMethodStrategy strategy = bean.getClass().getAnnotation(HttpMethodStrategy.class);
HttpMethod httpMethod = strategy.value();
handlerMap.put(httpMethod, (HttpMethodHandler) bean);
});
}
}
我们完善一下工具类的代码
/**
* 发送http请求
*
* @param httpMethod HTTP方法
* @param paramsMap 参数贴图
*/
public void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
HttpMethodHandler handler = HttpMethodContext.getHanlder(httpMethod);
handler.sendRequest(paramsMap);
}
简单的说,我们将一个枚举来映射一个Bean,来实现策略设计模式的.
我再举几个项目中使用到的例子.
MQ消费
比如我们系统收到一个MQ消息,我们已经得到了topi,tag和message内容,我们现在要进行消费,随着业务的发展,这些消息的数量越来越多,需要我们不断地去拓展消息类型.
先定义一个消息消费者
public interface RocketMQMsgConsumer {
default boolean match(String topic,String tag){
return false;
}
default boolean consume(String message){
return false;
}
}
写一个消费者存储列表
@Component
public class RocketMQMsgConsumerContext implements ApplicationListener<ContextRefreshedEvent> {
private static final List<RocketMqMsgConsumer> CONSUMERS = new ArrayList<>();
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Map<String, RocketMqMsgConsumer> beanMap = event.getApplicationContext().getBeansOfType(RocketMqMsgConsumer.class);
CONSUMERS.addAll(beanMap.values());
}
public List<RocketMqMsgConsumer> getConsumers(String topic,String tag){
return CONSUMERS.stream()
.filter(item -> item.match(topic,tag))
.collect(Collectors.toList());
}
}
mq消费监听器
这里我们的消费者可能是多个,只有全部成功才能成功,否则会提示RocketMQ等下再发消息过来.大家可以也可以设置一个consumer
@Component
public class MessageConsumerListener implements MessageListener {
@Override
public Action consume(Message message, ConsumeContext context) {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
List<RocketMqMsgConsumer> consumers = RocketMQMsgConsumerContext.getConsumers(message.getTopic(), message.getTag());
boolean result = true;
for (RocketMqMsgConsumer consumer : consumers) {
result &= consumer.consume(msg);
}
return result ? Action.CommitMessage : Action.ReconsumeLater;
}
}
其他例子,也有很多,讲一下可拓展的点
- 像前端在页面上配置了某个配置项(这个配置项下拉框列表可以直接给Enum.values()),然后存储到后台数据库,假设存储的值为 1,2,3,4,5 ,然后我们取出这个配置值,再用我们的context获取一个策略类,然后执行其中的方法.这样我们后端只需要定义策略类的类型,并进行一个实现,需要加一个策略就多一个枚举值,再写一个策略实现方法.
- 策略方法之间还可以使用一些继承,比如都继承一个abstractHandler,之类的,拓展性拉满
- getHandler()这个方法如果找不到对应的策略类还可以整一个默认的DefaultHandler,简直无敌啊.
- 在我们的接口上多写一些注解和参数说明,其他人也可以一起使用,一起写一个实现类,但是要注意事项说清楚.