Java框架-一站式事件实现

各位好,前面我们说了很多事件模型。为什么要用事件,在这里就不继续多讲了。 本篇文章主要讲如何封装事件,让我们统一用事件,一套代码完成事件。废话不多讲,直接上代码。

事件分为本地事件:意思是当前应用发的,当前应用自己消费。利用事件解耦,使代码看起来更优雅。

  • 如何统一代码
    • 消费者
    • 生产者
  • 本地事件
    • Spring事件实现
  • 远程事件
    • RedisMQ实现
    • RocketMQ实现

如何统一代码

消费者

消费者标签

java 复制代码
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MsgConsumer {

    String tag();

}

消费者如何写

java 复制代码
@Slf4j
@Component
@MsgConsumer(tag = EventTags.SYS_DEPT_ADD)
@RequiredArgsConstructor
public class DeptAddConsumer implements MessageConsumer<DeptAddEvent> {
   private final DeptService deptService;
   @Override
   public void consume(DeptAddEvent event) {
       deptService.consumeAdd(event);
   }
}

在类名上指定@MsgConsumer,并标记tag 从代码我们看到,该类实现了一个接口,并且还有泛化的参数 首先我们看泛化参数DeptAddEvent

java 复制代码
@NoArgsConstructor
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class DeptAddEvent extends BaseEvent {
    @ApiModelProperty(value = "组织id",required = true)
    @NotBlank(message = "组织id不能为空")
    private Long deptId;

    @ApiModelProperty(value = "组织名称",required = true)
    @NotBlank(message = "组织名称不能为空")
    private String deptName;
    @ApiModelProperty(value = "区域父子级编码")
    private String regionCode;
    @ApiModelProperty(value = "区域名称")
    private String regionName;
    @ApiModelProperty(value = "父级parentIds,若没有父级传0",required = true)
    @NotBlank(message = "父级parentIds不能为空")
    private String parentIds;

    @ApiModelProperty(value = "父级id,若没有父级传0",required = true)
    @NotBlank(message = "父级id不能为空")
    private Long parentId;

    @ApiModelProperty(value = "部门负责人id-列表")
    private String deptDirectorUserIds;

    @ApiModelProperty(value = "组织标签")
    private String deptTypeEnum;
}

该类继承了一个父类BaseEvent。所以我们所有的事件消息传递的我们都必须要继承该服务BaseEvent

java 复制代码
public abstract class BaseEvent implements Serializable {
    protected Long msgSerialNum;
    protected boolean idempotentEnabled = false;
    protected String tid = TraceUtils.putTidIfAbsent();
    protected String sid = TraceUtils.putSidIfAbsent();
    
    public BaseEvent() {
    }
}

然后再来看看接口如何定义

java 复制代码
public interface MessageConsumer<T extends BaseEvent> {
    void consume(T event);
}

消费者是不是看起来很简单,只要简单的继承MessageConsumer,定义消息体,并在类上增加@MsgConsumer(tag = "标签")

生产者

生产一般需要指定我发的是什么事件,所以我们定义了3个EventPublisher实现了EventPublisher接口

java 复制代码
public interface EventPublisher {

    <T extends BaseEvent> boolean sendMessage(T payload, String msgTag);
}

本地事件

Spring事件实现

Spring事件实现是指在Spring框架中使用事件机制进行组件之间的解耦和通信。 在Spring中,事件是通过ApplicationEvent和ApplicationListener两个接口来实现的。ApplicationEvent表示一个事件对象,它可以携带一些与事件相关的数据;ApplicationListener则是一个事件监听器,用于监听并处理特定的事件。

下面是Spring事件实现的基本步骤:

  1. 定义事件类:首先需要定义一个继承自ApplicationEvent的事件类,该类需要包含事件相关的数据。
  2. 创建事件发布者:在需要发布事件的地方,通过ApplicationEventPublisher接口提供的publishEvent方法发布事件。
  3. 注册事件监听器:在需要监听事件的地方,通过@EventListener注解将事件监听器注册到Spring容器中。
  4. 处理事件:当事件被发布后,所有注册的事件监听器都会收到通知,并执行相应的处理逻辑。
  5. 取消事件监听器:如果不再需要监听某个事件,可以通过Spring容器提供的ApplicationContext接口提供的removeBean方法取消事件监听器的注册。

总之,Spring事件实现是一种非常灵活和强大的组件通信方式,可以帮助开发者实现松耦合、高内聚的应用程序。

为了实现上面的统一代码消费逻辑,我首先定义了一个事件类

java 复制代码
public class LocalEvent<T extends BaseEvent> extends ApplicationEvent {

    //数据
    private T data;
    //tag
    private String tag;

    public LocalEvent(T data) {
        super(data);
    }

    public LocalEvent() {
        super(new Object());
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }
}

再定义了一个消费

java 复制代码
@Slf4j
@Component
public class LocalEventHandler implements ApplicationListener<LocalEvent> {

    //    @Override
    public void onEvent(LocalEvent event, long sequence, boolean endOfBatch) {
        log.info("接收到的消息:{}", event.getData());

        String messageTag = event.getTag();

        ConsumerType msgProcessorType = ConsumerCache.getConsumer(messageTag);
        if (msgProcessorType == null) {
            log.warn("No message processor present for tag: " + messageTag);
            return;
        }
        BaseEvent baseMessage = event.getData();
        TraceUtils.putSid(baseMessage.getSid());
        TraceUtils.putTid(baseMessage.getTid());
        AuthContext.set(baseMessage.getLoginUser());
        MessageConsumer<BaseEvent> messageConsumer = ApplicationContextHelper.getBean(msgProcessorType.getConsumerClass());
        if (messageConsumer == null) {
            throw new RuntimeException("No bean mapped for message :" + JSONUtil.toJsonStr(baseMessage));
        }

        try {
            //判断消息是否需要幂等消费
            if (baseMessage.isIdempotentEnabled()) {
                //计算key
                String key = DigestUtils.md5Hex(messageTag + "#" + baseMessage.getMsgSerialNum()
                        + "#" + baseMessage + "#" + messageConsumer.getClass().getName());
                Boolean consumed = ApplicationContextHelper.getBean(RedisService.class).setNx(key, 1, 24, TimeUnit.HOURS);

                //判断消息是否已经被当前服务消费过,如果消费过,则忽略当前消息
                if (Boolean.FALSE.equals(consumed)) {
                    log.warn("repeat consume message:{}", baseMessage);
                    return;
                }
                try {
                    messageConsumer.consume(baseMessage);
                    if (sequence != -1) {

                    }
                } catch (Throwable e) {
                    //执行错误,删除key,下次可以重复消费
                    ApplicationContextHelper.getBean(RedisService.class).delete(key);
                    throw e;
                }
            } else {
                messageConsumer.consume(baseMessage);
                if (sequence != -1) {

                }
            }
        } catch (Throwable e) {
            throw e;
        } finally {
            TraceUtils.removeTid();
            TraceUtils.removeSid();
            AuthContext.remove();
        }

    }

    @Override
    public void onApplicationEvent(LocalEvent event) {
        onEvent(event, -1L, false);
    }
}

后面再来将onEvent的实现逻辑。到这里基本完成了一个spring的事件消费逻辑。

远程事件

RedisMQ实现

事件类

java 复制代码
public class RedisEvent<T extends BaseEvent> implements Serializable {

    /**
     * DATA
     */
    private T data;
    /**
     * TAG
     */
    private String tag;

    private String className;

    public RedisEvent() {
    }

    public RedisEvent(T data, String tag, String className) {
        this.data = data;
        this.tag = tag;
        this.className = className;
    }

    public RedisEvent(T data, String tag) {
        this.data = data;
        this.tag = tag;
        this.className = data.getClass().getName();
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getClassName() {
        return className;
    }
}

Hander实现

java 复制代码
@Slf4j
public class RedisMessageListener implements MessageListener {

    public static String topic = "dragon:jjb-saas-common:redis:topic";
    private final RedisService redisService;

    public RedisMessageListener(RedisService redisService) {
        this.redisService = redisService;
    }

    @Override
    public void onMessage(Message message, byte[] pattern) {
        RedisEvent<?> redisEvent = (RedisEvent<?>) redisService.getRedisTemplate().getValueSerializer().deserialize(message.getBody());
        assert redisEvent != null;
        ConsumerType msgProcessorType = ConsumerCache.getConsumer(redisEvent.getTag());
        if (msgProcessorType == null) {
            log.warn("No message processor present for tag: " + redisEvent.getTag());
            return;
        }
        BaseEvent baseMessage = redisEvent.getData();
        TraceUtils.putSid(baseMessage.getSid());
        TraceUtils.putTid(baseMessage.getTid());
        AuthContext.set(baseMessage.getLoginUser());
        MessageConsumer<BaseEvent> messageConsumer = ApplicationContextHelper.getBean(msgProcessorType.getConsumerClass());

        try {
            //判断消息是否需要幂等消费
            if (baseMessage.isIdempotentEnabled()) {
                //计算key
                String key = DigestUtils.md5Hex(redisEvent.getTag() + "#" + baseMessage.getMsgSerialNum()
                        + "#" + baseMessage + "#" + messageConsumer.getClass().getName());
                Boolean consumed = redisService.setNx(key, 1, 24, TimeUnit.HOURS);

                //判断消息是否已经被当前服务消费过,如果消费过,则忽略当前消息
                if (Boolean.FALSE.equals(consumed)) {
                    log.warn("repeat consume message:{}", baseMessage);
                    return;
                }
                try {
                    messageConsumer.consume(baseMessage);
                } catch (Throwable e) {
                    //执行错误,删除key,下次可以重复消费
                    ApplicationContextHelper.getBean(RedisService.class).delete(key);
                    throw e;
                }
            } else {
                messageConsumer.consume(baseMessage);
            }
        } catch (Throwable e) {
            throw e;
        } finally {
            TraceUtils.removeTid();
            TraceUtils.removeSid();
            AuthContext.remove();
        }
    }
}

RocketMQ实现

hander实现

java 复制代码
@Slf4j
@RequiredArgsConstructor
public class SinkConsumer {

    private final MessageConsumerFactory messageConsumerFactory;


    @StreamListener(Sink.INPUT)
    public void inputConsumer(Message<Object> message) {
        messageConsumerFactory.doConsume(message);
    }

}
java 复制代码
@RequiredArgsConstructor
@Slf4j
public class MessageConsumerFactory {


    private final RedisService redisService;

    public void doConsume(Message<Object> message) {

        log.info("接收到的消息:{}", message);

        //String messageTag = String.valueOf(message.getHeaders().get("rocketmq_TAGS"));
        String messageTag = String.valueOf(message.getHeaders().get("ROCKET_TAGS"));
        ConsumerType msgProcessorType = ConsumerCache.getConsumer(messageTag);
        if (msgProcessorType == null) {
            log.warn("No message processor present for tag: " + messageTag);
            return;
        }
        Object payload = message.getPayload();
        String jsonData = "";
        if(payload instanceof byte[]){
            byte[] p = (byte[]) payload;
            jsonData = new String(p);
        }else {
            jsonData = String.valueOf(message.getPayload());
        }
        BaseEvent baseMessage = JSONUtil.toBean(jsonData, msgProcessorType.getMessageClass());
        TraceUtils.putSid(baseMessage.getSid());
        TraceUtils.putTid(baseMessage.getTid());
        AuthContext.set(baseMessage.getLoginUser());
        MessageConsumer<BaseEvent> messageConsumer = ApplicationContextHelper.getBean(msgProcessorType.getConsumerClass());
        if (messageConsumer == null) {
            throw new RuntimeException("No bean mapped for message :" + JSONUtil.toJsonStr(message));
        }

        try {
            //判断消息是否需要幂等消费
            if (baseMessage.isIdempotentEnabled()) {
                //计算key
                String key = DigestUtils.md5Hex(messageTag + "#" + baseMessage.getMsgSerialNum()
                        + "#" + jsonData + "#" + messageConsumer.getClass().getName());
                Boolean consumed = redisService.setNx(key, 1, 24, TimeUnit.HOURS);

                //判断消息是否已经被当前服务消费过,如果消费过,则忽略当前消息
                if (Boolean.FALSE.equals(consumed)) {
                    log.warn("repeat consume message:{}", message);
                    return;
                }
                try {
                    messageConsumer.consume(baseMessage);
                } catch (Throwable e) {
                    //执行错误,删除key,下次可以重复消费
                    redisService.delete(key);
                    log.error("mq consumer error !", e);
                    throw e;
                }
            } else {
                messageConsumer.consume(baseMessage);
            }
        } catch (Throwable e) {
            log.error("mq consumer error !", e);
            throw e;
        } finally {
            TraceUtils.removeTid();
            TraceUtils.removeSid();
            AuthContext.remove();
        }

    }
}
相关推荐
苹果酱05671 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱1 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
计算机学姐3 小时前
基于python+django+vue的影视推荐系统
开发语言·vue.js·后端·python·mysql·django·intellij-idea
JustinNeil4 小时前
简化Java对象转换:高效实现大对象的Entity、VO、DTO互转与代码优化
后端
青灯文案14 小时前
SpringBoot 项目统一 API 响应结果封装示例
java·spring boot·后端
微尘85 小时前
C语言存储类型 auto,register,static,extern
服务器·c语言·开发语言·c++·后端
计算机学姐5 小时前
基于PHP的电脑线上销售系统
开发语言·vscode·后端·mysql·编辑器·php·phpstorm
码拉松6 小时前
千万不要错过,优惠券设计与思考初探
后端·面试·架构
白总Server6 小时前
MongoDB解说
开发语言·数据库·后端·mongodb·golang·rust·php
计算机学姐7 小时前
基于python+django+vue的家居全屋定制系统
开发语言·vue.js·后端·python·django·numpy·web3.py