有时候写代码,你会发现越写越有劲,觉得越有趣,时间在不知不觉中流逝
先看一下需求场景
现在开始发挥你们的思路,如果要你们对接,你们会怎么做,写在一个方法里然后判断msgType的类型,然后做相应的事情? 那可就完了,这么多个类型你要挨个判断不说,业务逻辑都写在了一个方法里,你不觉得这样耦合太紧了吗?你当然可以在订阅主题的方法里只写判断然后调用相应的方法,但是这么做。。总有点不好吧,如果你觉得没事,那就没事,当然你可能还会说把多个方法订阅这一个主题,但是注意。在kafka中同一用户组不能订阅同一主题中的同一分区,那你会说弄多个分区不就好了么?恩。。这样问题更大,建议去系统的学习下kafka,那你会觉得那我每个方法订阅的不在同一用户组不就行了?是可以,但是你不觉得弄这么多方法订阅同一主题很别扭?假如主题名称改了,那你得改32个(当然主题名不可能随意更改)总之这样不利于维护,而且看起来很别扭
在此之前我看过一些关于if优化的博客,大概也就是用策略模式加工厂模式代替
当我看到我对接的kafka的消息中是通过msgType来判断的时候我就想到了,但是。。这么做弊端更大。看过的应该知道,对于每个情况都得创建一个类来实现一个特定的接口,,,那么我这里有32种情况,就得创建32个类。。这不疯了么
当然聪明的我肯定不会这么干,那我会怎么做的呢?
其实和上面那个策略模式类似或者说就是,只是我把“类”改成了“方法”
你定义32个类来做,肯定疯了,但是32个方法不会呀。。所以我先写枚举,把每种情况都一一列出来
这里对应的就是那32种情况
然后定义一个注解
这个注解实际上是用来标注,即我们扫描的时候方便一点,不然你怎么扫描,你怎么知道哪个类中的哪个方法是我们需要调用的。
当然还有一个注解,是放在方法上的
里面有个值,就是我们刚刚定义的那个枚举类型,
整体思路是这样,通过扫描有DeliveryKafkaConsumer该注解的类,然后再扫描有该类中有DeliveryKafkaConsumerMethod注解的方法,然后拿到DeliveryKafkaConsumerMethod注解的值,就知道当前这个方法是对应的哪种情况
先来看下怎么使用
创建一个类加上DeliveryKafkaConsumer注解
然后定义一个方法加上DeliveryKafkaConsumerMethod注解并指定枚举类型说明你要干什么
那么问题来了,我该怎么去调用这个方法,
其实很简单具体实现
@Component
@Getter
public class DeliveryEventConsumerParsing implements ApplicationContextAware {
private final Map<String, Map<Object, Method>> map = Maps.newConcurrentMap();
private static final ImmutableMap.Builder<String, String> BUILDER = new ImmutableMap.Builder<>();
public static final ImmutableMap<String, String> INFO_MAP;
static {
MessageDeliveryEventConsumerEnum[] values = MessageDeliveryEventConsumerEnum.values();
for (MessageDeliveryEventConsumerEnum message : values) {
BUILDER.put(message.getId(), message.getName());
}
INFO_MAP = BUILDER.build();
}
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
parsing();
}
private void parsing() {
Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(DeliveryKafkaConsumer.class);
Set<Map.Entry<String, Object>> entries = beanMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
Object obj = entry.getValue();
scannerConsumerMethod(obj);
}
}
private void scannerConsumerMethod(Object obj) {
Class<?> clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
addConsumerMethod(obj, method);
}
}
private void addConsumerMethod(Object obj, Method method) {
method.setAccessible(true);
if (method.isAnnotationPresent(DeliveryKafkaConsumerMethod.class)) {
DeliveryKafkaConsumerMethod kafka = method.getAnnotation(DeliveryKafkaConsumerMethod.class);
Map<Object, Method> objMap = Maps.newHashMap();
objMap.put(obj, method);
map.put(kafka.value().getId(), objMap);
}
}
}
具体调用的方法,这个方法要订阅这个主题,记得要把DeliveryEventConsumerParsing这个类注入进来,因为我们的具体要调用的方法保存在这个类
这个订阅主题的方法,你们可以根据自己的需求的实现,我这里的匹配了一下方法的参数,如果目标方法的参数 我有或者我能从spring中拿到,那我就给他注入进去
自己灵活实现吧,
注意:这里还有个注意的点 如果你当前类加了 @DeliveryKafkaConsumer这个注解和类中的方法刚好也加了 @DeliveryKafkaConsumerMethod这个注解,那么这时候该类就不能被动态代理,比如定时任务之类的 因为在spring boot中默认使用cglib的动态代理方式,cglib是通过继承重写方法实现的,如果你这个类被代理了,那么他就会重写你的方法,但是导致的问题就是你这个方法上的注解就丢了,扫描不到了
如果你想在同一个类即使用@DeliveryKafkaConsumer和@DeliveryKafkaConsumerMethod注解,又想被动态代理,这里以定时任务为例
我这里是更新老师的信息,但是有个问题,先要去查询别的接口,并且这个接口要Token才能访问,如果我每次调用这个方法的时候都去重新获取Token 的话,显然是不合理的,
我看了一下,接口返回的Token有效期为12个小时,那就好办了,我维护一个定时任务,定时来更新Token
之前说过,不能写在同一类,但是我有想在一个类中维护,所以定义一个内部类就好了
其实我之前尝试过很多解决方案,都可以解决,但是我还是觉得不太好,所以最后才想到这个内部类的写法
这样的话就很方便我们写我们自己的业务逻辑了
如果我们想监听新增老师的 方法
只需和下面一样就行
这样写的主要目的就是把业务逻辑分开,有利于自己维护
如有不对,欢迎指正