首页 抖音快讯文章正文

Java通用枚举还能这样做?前后端终于不扯皮了!

抖音快讯 2025年09月05日 08:38 1 admin

“又双叒叕改枚举了?!”

Java通用枚举还能这样做?前后端终于不扯皮了!

前端同学看着后端刚提交的,稍稍增加了一个新状态值的接口文档,默默叹了口气,熟练的打开了自己维护的 constants.ts 文件。后端同学在心里嘀咕“不就加个状态吗?前端改个下拉框不就行啦?至于每次都要我同步吗?”

这熟悉的一幕,几乎每天都会上演。为了减少前后端同学的扯皮,有没有一种方式,能让一份定义,两端通用?答案是:有!而且比你想象的更优雅、更强大。 今天就来揭秘我在项目中使用的这种让前后端都拍案叫绝的“通用枚举”,彻底让前后端同学不在扯皮?

通用枚举

共提供两种枚举的返回类型,比如有枚举类UserType如下

@Getter@AllArgsConstructorpublic enum UserType{    NORMAL(1, "普通用户", ""),    ADMIN(2, "管理员", ""),    ;    private final Integer value;    private final String name;    private final String description;}

第一种返回枚举名称:ADMIN

第二种返回枚举JSON:{”value”:2, ”name”:”管理员”, ”description”:””}

顶级枚举

定义一个通用枚举接口 IBaseEnum,所有的枚举类都必须实现 IBaseEnum,提供一些通用的方法

/** * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-05-26 01:09:09 * @version: 1.0.0 */public interface IBaseEnum<T> {    /**     * 字典值     *     * @return value     */    T getValue();    /**     * 字典名称     *     * @return name     */    String getName();    /**     * 字典描述     *     * @return description     */    String getDescription();    /**     * 通过value获取枚举     *     * @param value value     * @param clazz clazz     * @param <E>   E     * @return result     */    static <E extends Enum<E> & IBaseEnumJson<T>, T> E getEnum(T value, Class<E> clazz) {        Objects.requireNonNull(value);        return getEnums(clazz).stream()                .filter(e -> ObjectUtil.equal(e.getValue(), value))                .findFirst()                .orElse(null);    }    /**     * 通过value获取name     *     * @param value value     * @param clazz clazz     * @param <E>   E     * @return name     */    static <E extends Enum<E> & IBaseEnumJson<T>, T> String getName(T value, Class<E> clazz) {        Objects.requireNonNull(value);        return getEnums(clazz).stream()                .filter(e -> ObjectUtil.equal(e.getValue(), value))                .map(IBaseEnumJson::getName)                .findFirst()                .orElse(null);    }    /**     * 通过name获取value     *     * @param name  name     * @param clazz clazz     * @param <E>   E     * @return value     */    static <E extends Enum<E> & IBaseEnumJson<T>, T> T getCode(String name, Class<E> clazz) {        Objects.requireNonNull(name);        return getEnums(clazz).stream()                .filter(e -> ObjectUtil.equal(e.getName(), name))                .map(IBaseEnumJson::getValue)                .findFirst()                .orElse(null);    }    /**     * 获取所有枚举     *     * @param clazz clazz     * @param <E>   E     * @return enums     */    static <E extends Enum<E> & IBaseEnumJson<T>, T> EnumSet<E> getEnums(Class<E> clazz) {        return EnumSet.allOf(clazz);    }}

简单枚举

为枚举中的需要序列化的字段添加 *@JsonValue* 即可实现简单枚举。我们通常会在枚举描述的字段上添加 *@JsonValue* 注解,但是前端传参就只能传递这个枚举描述字段的值(通常是中文),就不能传枚举名称了。为此需要使用 *@JsonCreator* 注解来自定义处理的逻辑。

/** * 为什么不直接使用@JsonCreator: * 1、在接口中使用该注解添加到from方法上是不生效的 * 2、如果将接口换成抽象类是可以的,但是枚举无法继承抽象类 * 3、单独在每个实现IBaseEnumSimple接口的枚举中添加的话,代价太大了 * // @JsonCreator * // public static UserType from(Object object) { * //     return IBaseEnumSimple.from(object, UserType.class); * // } * * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-05-25 20:37:37 * @version: 1.0.0 */@JsonDeserialize(using = DictSimpleDeserializer.class)public interface IBaseEnumSimple<T> extends IBaseEnum<T> {    // 可以不在此处做限制,开放给具体的继承类使用    @JsonValue    String getName();    /**     * 直接在此方法添加@JsonCreator注解是行不通的,除非在实现IBaseEnumSimple的枚举类中单独使用     *     * @param object    object     * @param enumClass enumClass     * @param <E>       E     * @return Enum     */    static <E extends Enum<E> & IBaseEnumSimple<?>> E from(Object object, Class<E> enumClass) {        if (object == null || enumClass == null) {            return null;        }        return Arrays.stream(enumClass.getEnumConstants())                .filter(e -> e.matches(object)).findFirst()                .orElseThrow(() -> new IllegalArgumentException("无效枚举值: " + object));    }    /**     * 枚举匹配逻辑,子类可覆盖     *     * @param object object     * @return boolean     */    default boolean matches(Object object) {        // 按照value,name或枚举名称匹配        // return String.valueOf(this.getValue()).equals(object.toString())        //         || this.getName().equals(object.toString())        //         || this.toString().equals(object.toString());        // 通过枚举名称匹配        return this.toString().equals(object.toString());    }}
/** * @description: 简单枚举反序列化器 * @author: Walker * @date: 2025-05-26 01:40:40 * @version: 1.0.0 */public class DictSimpleDeserializer<E extends Enum<E> & IBaseEnumSimple<?>> extends JsonDeserializer<E> implements ContextualDeserializer {    private Class<E> enumClass;    @Override    @SuppressWarnings("unchecked")    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) {        this.enumClass = (Class<E>) context.getContextualType().getRawClass();        return this;    }    @Override    public E deserialize(JsonParser p, DeserializationContext context) throws IOException {        String value = p.getValueAsString();        return IBaseEnumSimple.from(value, enumClass);    }}

JSON枚举

枚举接口添加 @JsonFormat(shape = JsonFormat.Shape.*OBJECT*) 即可返回枚举对应的 JSON 字符串

/** * @description: 通用枚举,所有枚举都必须实现该接口 * @author: Walker * @date: 2025-01-11 00:55:55 * @version: 1.0.0 */@JsonFormat(shape = JsonFormat.Shape.OBJECT)public interface IBaseEnumJson<T> extends IBaseEnum<T> {}

通用枚举示例

简单枚举使用示例,前端可直接传递枚举名称如:NORMAL、ADMIN,也可以通过修改 IBaseEnumSimple 中的 matches 方法修改匹配逻辑

@Getter@AllArgsConstructorpublic enum UserType implements IBaseEnumSimple<Integer> {    NORMAL(1, "普通用户", ""),    ADMIN(2, "管理员", ""),    ;    @EnumValue    private final Integer value;    private final String name;    private final String description;}

JSON枚举使用示例,前端可以直接传递枚举名称如:NORMAL、ADMIN

@Getter@AllArgsConstructorpublic enum UserType implements IBaseEnumJson<Integer> {    NORMAL(1, "普通用户", ""),    ADMIN(2, "管理员", ""),    ;    @EnumValue    private final Integer value;    private final String name;    private final String description;}

枚举自动注册

数据字典实体、数据字典数据实体

public class SysCodeMaster extends BaseEntity {    private String id;    private String name;    private String code;    private String description;    private Integer status;}
public class SysCodeItem extends BaseEntity {    private String id;    private String codeId;    private String name;    private String value;    private String description;    private Double displayOrder;    private Integer status;}

数据字典注解、数据字典数据注解

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface CodeMaster {    // 数据字典名    String name();    // 字典编码    String code();    // 数据字典项    CodeItem[] values() default {};    // 数据字典项对应的枚举类    Class<? extends IBaseEnum<?>> enumClass() default UnspecifiedEnum.class;    // 枚举表述    String description() default "";}
@Target(ElementType.ANNOTATION_TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface CodeItem {    // 名称    String name();    // 值    String value();    // 描述    String description() default "";}

数据字典自动注册Runner

@ConditionalOnProperty(name = "top.walker.dict.register.enabled", havingValue = "true", matchIfMissing = false)@Component@RequiredArgsConstructorpublic class CodeMasterRunner implements ApplicationRunner {    private final Logger logger = LoggerFactory.getLogger(CodeMasterRunner.class);    private final ISysCodeMasterService codeMasterService;    @Override    public void run(ApplicationArguments args) throws Exception {        logger.info("code master register start......");        // 创建扫描器        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);        // 添加过滤器,查找实现了IEntity接口的类        scanner.addIncludeFilter(new AssignableTypeFilter(IEntity.class));        // 扫描BASE_PACKAGES中所有实现IEntity的类        Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(ApplicationConstants.BASE_PACKAGES);        for (BeanDefinition beanDefinition : beanDefinitions) {            Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());            Field[] fields = clazz.getDeclaredFields();            for (Field field : fields) {                if (field.isAnnotationPresent(CodeMaster.class)) {                    CodeMaster codeMaster = field.getAnnotation(CodeMaster.class);                    SysCodeMaster sysCodeMaster = SysCodeMaster.builder()                            .name(codeMaster.name())                            .code(codeMaster.code())                            .description(codeMaster.description())                            .build();                    logger.info("dic : {}", JSONUtil.toJsonStr(sysCodeMaster));                    Map<String, SysCodeItem> codeItemMap = new HashMap<>();                    // 解析enumClass                    Class<? extends IBaseEnum<?>> enumClass = codeMaster.enumClass();                    if (!UnspecifiedEnum.class.equals(enumClass) && enumClass.isEnum()) {                        IBaseEnum<?>[] enumConstants = enumClass.getEnumConstants();                        for (IBaseEnum<?> enumConstant : enumConstants) {                            SysCodeItem sysCodeItem = SysCodeItem.builder()                                    .codeId(sysCodeMaster.getId())                                    .name(enumConstant.getName())                                    .value(String.valueOf(enumConstant.getValue()))                                    .description(enumConstant.getDescription())                                    .build();                            codeItemMap.put(String.valueOf(enumConstant.getValue()), sysCodeItem);                        }                    }                    // values 和 enumClass 同时存在时,values 优先级更高,所以 values 放在后面                    // 解析values                    for (CodeItem codeItem : codeMaster.values()) {                        SysCodeItem sysCodeItem = SysCodeItem.builder()                                .name(codeItem.name()).value(codeItem.value()).description(codeItem.description())                                .build();                        codeItemMap.put(codeItem.value(), sysCodeItem);                    }                    List<SysCodeItem> codeItems = Arrays.asList(codeItemMap.values().toArray(new SysCodeItem[0]));                    logger.info("      {}", JSONUtil.toJsonStr(codeItems));                    // 注册数据字典                    this.registerCode(sysCodeMaster, codeItems);                }            }        }        logger.info("code master register end......");    }    /**     * 注册数据字典和数据字典数据     *     * @param sysCodeMaster 数据字典     * @param codeItems     数据字典数据     */    private void registerCode(SysCodeMaster sysCodeMaster, List<SysCodeItem> codeItems) {		    // TODO         codeMasterService.saveOrUpdateCode(sysCodeMaster, codeItems);    }}

枚举自动注册示例

枚举注解的两种使用方式

public class User extends BaseEntity {    private String name;    private Integer age;    @CodeMaster(name = "性别", code = "sys_sex", values = {            @CodeItem(name = "男", value = "1"),            @CodeItem(name = "女", value = "2"),            @CodeItem(name = "未知", value = "3"),    })    private UserSex sex;    @CodeMaster(name = "类型", code = "sys_type_test", enumClass = UserType.class)    private UserType type;}

在application配置中开启枚举自动注册

top:  walker:    dict:      register:        enabled: true

发表评论

泰日号Copyright Your WebSite.Some Rights Reserved. 网站地图 备案号:川ICP备66666666号 Z-BlogPHP强力驱动