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();
        }

    }
}
相关推荐
喜欢打篮球的普通人32 分钟前
rust高级特征
开发语言·后端·rust
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
豌豆花下猫2 小时前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai
喔喔咿哈哈2 小时前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
夏微凉.2 小时前
【JavaEE进阶】Spring AOP 原理
java·spring boot·后端·spring·java-ee·maven
彭亚川Allen2 小时前
数据冷热分离+归档-亿级表优化
后端·性能优化·架构
Goboy2 小时前
Spring Boot 和 Hadoop 3.3.6 的 MapReduce 实战:日志分析平台
java·后端·架构
不会编程的懒洋洋4 小时前
Spring Cloud Eureka 服务注册与发现
java·笔记·后端·学习·spring·spring cloud·eureka
NiNg_1_2344 小时前
SpringSecurity入门
后端·spring·springboot·springsecurity
Lucifer三思而后行5 小时前
YashanDB YAC 入门指南与技术详解
数据库·后端