各位好,前面我们说了很多事件模型。为什么要用事件,在这里就不继续多讲了。 本篇文章主要讲如何封装事件,让我们统一用事件,一套代码完成事件。废话不多讲,直接上代码。
事件分为本地事件:意思是当前应用发的,当前应用自己消费。利用事件解耦,使代码看起来更优雅。
- 如何统一代码
- 消费者
- 生产者
- 本地事件
- 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事件实现的基本步骤:
- 定义事件类:首先需要定义一个继承自ApplicationEvent的事件类,该类需要包含事件相关的数据。
- 创建事件发布者:在需要发布事件的地方,通过ApplicationEventPublisher接口提供的publishEvent方法发布事件。
- 注册事件监听器:在需要监听事件的地方,通过@EventListener注解将事件监听器注册到Spring容器中。
- 处理事件:当事件被发布后,所有注册的事件监听器都会收到通知,并执行相应的处理逻辑。
- 取消事件监听器:如果不再需要监听某个事件,可以通过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();
}
}
}