java自定义注解获取类的字段说明

业务需求:事件组件定义了事件类,项目启动的时候注册事件类的字段和字段说明到事件管理页面,可以查看每个事件的消息模板说明

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": []
		}]
	}]
}]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值