业务需求:事件组件定义了事件类,项目启动的时候注册事件类的字段和字段说明到事件管理页面,可以查看每个事件的消息模板说明
1. 自定义注解
1.1 EventObject
package com.tophant.engine.event.starter.register;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
/**
* 事件对象
*
* @author wanfei
* @date 2023/03/22
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
@Component
public @interface EventObject {
/**
* 事件对象描述
*/
String value() default "";
}
1.2 EventField
package com.tophant.engine.event.starter.register;
import java.lang.annotation.*;
/**
* 事件字段说明
*
* @author wanfei
* @date 2023/03/22
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Inherited
public @interface EventField {
/**
* 事件字段说明
*/
String value() default "";
}
2. 创建事件类
package com.tophant.engine.event.starter.register;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 自动注册事件
*
* @author wanfei
* @date 2023/03/29
*/
@Data
@Accessors(chain = true)
@EventObject("自动注册事件")
public class EventRegisterEvent implements Serializable {
private static final long serialVersionUID = -8859370839349111445L;
/**
* 主题(profile + topic或事件类)
*/
@EventField("主题")
private String topic;
/**
* 名称
*/
@EventField("名称")
private String name;
/**
* 应用appid
*/
@EventField("应用appid")
private String appid;
/**
* 消息模板
*/
@EventField("消息模板")
private List<MessageTemplate> messageTemplate;
/**
* 消息模板
*
* @author wanfei
* @date 2023/04/14
*/
@Data
@Accessors(chain = true)
public static class MessageTemplate implements Serializable{
private static final long serialVersionUID = -4884206767786386008L;
/**
* 字段
*/
@EventField("字段")
private String field;
/**
* 字段描述
*/
@EventField("字段描述")
private String fieldDesc;
/**
* 对象字段
*/
@EventField("对象字段")
private List<MessageTemplate> objField;
}
}
注意:内部类字段
objField
集合类型是MessageTemplate
,可能会无限嵌套,后面设置了递归两层后终止
3. 创建项目启动运行的Runner
package com.tophant.engine.event.starter.register;
import cn.hutool.core.util.StrUtil;
import com.tophant.engine.event.core.exception.MessageServiceException;
import com.tophant.engine.event.core.utils.EventUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
/**
* 事件自动注册配置
*
* @author wanfei
* @date 2023/03/29
*/
@Slf4j
@RequiredArgsConstructor
@EnableConfigurationProperties(EventRegisterProperties.class)
public class EventAutoRegisterRunner implements ApplicationRunner, ApplicationContextAware {
@Value("${cloud.appid:}")
private String appid;
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
log.info("[Engine] |- Starter [Engine Event Auto Register Starter] Auto Configure.");
}
@Override
public void run(ApplicationArguments args) throws Exception {
// 扫描spring容器中所有包含 @EventObject 注解的类
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(EventObject.class);
beans.values().forEach(event -> {
EventObject eventObject = event.getClass().getAnnotation(EventObject.class);
if (StrUtil.isBlank(eventObject.value())) {
throw new MessageServiceException(StrUtil.format("未设置事件名称 [event={}] 配置参考: @EventObject(请配置事件名称)", event.getClass().getSimpleName()));
}
String topic = EventUtil.getTopic(event);
// 发送消息到服务端
EventUtil.send(new EventRegisterEvent()
.setName(eventObject.value())
.setTopic(topic)
.setAppid(appid)
.setMessageTemplate(getClassFieldDesc(event.getClass(), new ArrayList<>())));
log.info("[event 对象类 自动注册] topic: {}, ", topic);
// 删除spring容器中包含 @EventObject 注解的类
((ConfigurableApplicationContext) applicationContext).getBeanFactory().destroyBean(event);
});
}
/**
* 得到类字段和描述
*
* @param cls cls
* @param beforeClsNames cls名字之前 之前对象名字 如果 beforeClsNames 和当前对象的一样,说明循环了,直接终止
* @return {@link List}<{@link EventRegisterEvent.MessageTemplate}>
*/
private List<EventRegisterEvent.MessageTemplate> getClassFieldDesc(Class<?> cls, List<String> beforeClsNames) {
if (hasTwoOrMore(beforeClsNames, cls.getName())) {
// 如果之前链路里面已经包含了该类型 2 次,不能继续递归查询,直接终止,否则会进入死循环
return Collections.emptyList();
}
List<EventRegisterEvent.MessageTemplate> messageTemplateList = new ArrayList<>();
for (int i = 0; i < cls.getDeclaredFields().length; i++) {
if (i == 0) {
// 说明开始当前层级
beforeClsNames.add(cls.getName());
}
Field field = cls.getDeclaredFields()[i];
if (!StrUtil.equals(field.getName(), "serialVersionUID")) {
// 过滤 serialVersionUID 字段
EventField eventField = field.getAnnotation(EventField.class);
List<EventRegisterEvent.MessageTemplate> objField = new ArrayList<>();
if (Collection.class.isAssignableFrom(field.getType())) {
// 集合对象
ParameterizedType collectionType = (ParameterizedType) field.getGenericType();
Class<?> collectionClass = (Class<?>) collectionType.getActualTypeArguments()[0];
if (checkCustomClass(collectionClass)) {
// 每一层级都会创建新的集合,因为每个字段的链路都不一样,把前面层级的类名加进来
objField = getClassFieldDesc(collectionClass, new ArrayList<>(beforeClsNames));
}
} else if (checkCustomClass(field.getType())) {
Class<?> customClass = field.getType();
// 每一层级都会创建新的集合,因为每个字段的链路都不一样,把前面层级的类名加进来
objField = getClassFieldDesc(customClass, new ArrayList<>(beforeClsNames));
}
messageTemplateList.add(new EventRegisterEvent.MessageTemplate()
.setField(field.getName())
.setFieldDesc(Optional.ofNullable(eventField).map(EventField::value).orElse(StrUtil.EMPTY))
.setObjField(objField));
}
}
return messageTemplateList;
}
/**
* 检查是否自定义类
*
* @param cls cls
* @return boolean
*/
private boolean checkCustomClass(Class<?> cls) {
// 判断字段不是java的原始类型,不是hutool, spring和fastjson等json对象,说明是自定义的对象
return !cls.isPrimitive() && !StrUtil.startWithAny(cls.getName(), "java.", "org.springframework", "cn.hutool", "com.alibaba");
}
/**
* 有两个或更多
*
* @param list 列表
* @param target 目标
* @return boolean
*/
private boolean hasTwoOrMore(List<String> list, String target) {
int count = 0;
for (String str : list) {
if (str.equals(target)) {
count++;
if (count >= 2) {
return true;
}
}
}
return false;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
4. 测试
启动项目,注册完成,该事件的字段说明数据格式如下:
[{
"field": "topic",
"fieldDesc": "主题",
"objField": []
}, {
"field": "name",
"fieldDesc": "名称",
"objField": []
}, {
"field": "appid",
"fieldDesc": "应用appid",
"objField": []
}, {
"field": "messageTemplate",
"fieldDesc": "消息模板",
"objField": [{
"field": "field",
"fieldDesc": "字段",
"objField": []
}, {
"field": "fieldDesc",
"fieldDesc": "字段描述",
"objField": []
}, {
"field": "objField",
"fieldDesc": "对象字段",
"objField": [{
"field": "field",
"fieldDesc": "字段",
"objField": []
}, {
"field": "fieldDesc",
"fieldDesc": "字段描述",
"objField": []
}, {
"field": "objField",
"fieldDesc": "对象字段",
"objField": []
}]
}]
}]