前言

在我们的java项目中,有很多页面。其中,筛选条件也是很复杂,有的下拉框是枚举类的映射,有的下拉框是查了数据库,比如字典表,型号等信息。

那么,如果新增一个下拉框,后端就得对前端提供一个下拉的接口。基于这个场景,我这边设计并实现了一个自定义的starter。它可以:

  • 统一管理枚举,可以让你只提供一个接口给前端。
  • 兼容非枚举类的下拉场景。如果不涉及自定义的筛选条件,则可以共用一个公共下拉接口,如果涉及到自定义的筛选条件,则还是需要单独给出新的下拉接口。但是内部实现可以简化、以及复用之前的部分代码。

正文

一、项目结构

本文涉及到两个java项目模块。一个是公共枚举starter ,即【commons-enums-starter】。另一个是用于测试这个starter的模块,即【admin-starter】,引入了spring-boot-starter-web依赖,并定义了简单的配置和controller来测试公共枚举starter。

项目整体使用 java8 和 springboot 2.7.6版本。

1.1 commons-enums-starter 结构

在这里插入图片描述

1.2 admin-starter 结构

在这里插入图片描述

二、完整代码

2.1 commons-enum-starter

2.1.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.feng.commons</groupId>
    <artifactId>commons-enums-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
2.1.2 resources/META-INF/spring.facories

配置启用自动配置类:枚举自动配置类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.feng.commons.ec.EnumAutoConfiguration
2.1.3 IBaseEnum

基础枚举接口,定义常用的静态工具方法,如通过枚举类的Class 获取对应的 EnumSet集合。

在接口 IBaseEnum<T> 定义时,T 表示枚举编码的类型,常见的是整数或字符串。 T getValue() 方法则是获取枚举编码, String getDesc() 获取描述。

在枚举作为下拉的接口源数据时,枚举编码会作为key,描述作为 value返回。

package org.feng.commons.ec;

import java.util.EnumSet;

/**
 * 枚举接口:使用本组件管理的枚举,都需要实现这个接口
 *
 * @param <T> 枚举值类型
 */
public interface IBaseEnum<T> {
    T getValue();

    String getDesc();

    /**
     * 获取枚举集合
     *
     * @param enumClass 枚举类
     * @param <E>       枚举类型
     * @return 枚举集合
     */
    static <E extends Enum<E>> EnumSet<E> getEnums(Class<E> enumClass) {
        return EnumSet.allOf(enumClass);
    }

    /**
     * 获取枚举
     *
     * @param value     枚举值
     * @param enumClass 枚举类
     * @param <E>       枚举类型
     * @param <T>       枚举值类型
     * @return 枚举
     */
    static <E extends Enum<E> & IBaseEnum<T>, T> E getEnum(T value, Class<E> enumClass) {
        return getEnums(enumClass).stream().filter(e -> e.getValue().equals(value)).findFirst().orElse(null);
    }

    /**
     * 获取枚举描述
     *
     * @param value     枚举值
     * @param enumClass 枚举类
     * @param <E>       枚举类型
     * @param <T>       枚举值类型
     * @return 枚举描述
     */
    static <E extends Enum<E> & IBaseEnum<T>, T> String getEnumDesc(T value, Class<E> enumClass) {
        return getEnums(enumClass).stream().filter(e -> e.getValue().equals(value)).map(IBaseEnum::getDesc).findFirst().orElse(null);
    }

}
2.1.4 CustomerRegisterEnumConfigure

自定义配置枚举的注册信息。

本starter 支持配置扫描包,注册枚举类。
同时也使用这个接口来作为一个扩展,即你想修改枚举的注册信息,或者查询时的筛选条件,可以实现这个接口,然后将实现类注入的spring容器就会自动生效。

package org.feng.commons.ec;

/**
 * 枚举注册配置
 */
@FunctionalInterface
public interface CustomerRegisterEnumConfigure {

    /**
     * 注册枚举信息,以及过滤条件
     */
    void register();
}

2.1.5 ScanUtil

扫描工具类,提供通过包名扫描获取指定的枚举类。

package org.feng.commons.ec;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.ClassMetadata;

import java.util.HashSet;
import java.util.Set;

/**
 * 扫描工具类
 */
@Slf4j
public class ScanUtil {
    private ScanUtil() {
    }

    /**
     * 扫描指定包下实现特定接口的枚举类
     *
     * @param batchPackages 扫描包路径
     * @return 符合条件的枚举类集合
     */
    public static <E extends Enum<E> & IBaseEnum<T>, T> Set<Class<E>> scanEnumClasses(String[] batchPackages) {
        Set<Class<E>> enumClasses = new HashSet<>();
        for (String singlePackage : batchPackages) {
            enumClasses.addAll(scanEnumClasses(singlePackage));
        }

        log.info("扫描到枚举类:{}", enumClasses);
        return enumClasses;
    }


    @SuppressWarnings("unchecked")
    private static <E extends Enum<E> & IBaseEnum<T>, T> Set<Class<E>> scanEnumClasses(String enumPackage) {
        Set<Class<E>> enumClasses = new HashSet<>();
        try {
            Set<BeanDefinition> candidates = getBeanDefinitions(enumPackage);
            for (BeanDefinition candidate : candidates) {
                try {
                    Class<?> clazz = Class.forName(candidate.getBeanClassName());
                    if (IBaseEnum.class.isAssignableFrom(clazz)) {
                        enumClasses.add((Class<E>) clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // 忽略无法加载的类
                    log.error("扫描枚举类异常", e);
                }
            }
        } catch (Exception e) {
            // 处理扫描异常
            log.error("扫描枚举类异常", e);
        }

        return enumClasses;
    }

    /**
     * 获取指定包下的类定义: 筛选条件是该类实现了 IBaseEnum.class
     *
     * @param enumPackage 包名
     * @return 类定义集合
     */
    private static Set<BeanDefinition> getBeanDefinitions(String enumPackage) {
        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(false);

        // 添加过滤器,只扫描枚举类
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
            try {
                ClassMetadata classMetadata = metadataReader.getClassMetadata();

                // 检查是否实现了指定接口 interfaceClass
                String className = classMetadata.getClassName();
                Class<?> clazz = Class.forName(className);
                return IBaseEnum.class.isAssignableFrom(clazz);
            } catch (Exception e) {
                return false;
            }
        });

        return scanner.findCandidateComponents(enumPackage);
    }
}

2.1.6 NonEnumObjectHandler

非枚举对象处理器。顾名思义,就是支持对非枚举对象的处理,比如我们有查数据库中某个表,然后根据这个表的数据构建一个下拉接口。就需要用到它。

package org.feng.commons.ec;

import org.springframework.lang.Nullable;

import java.util.List;

/**
 * 非枚举对象处理器
 */
public interface NonEnumObjectHandler<T, QueryCriteria> {

    /**
     * 处理非枚举对象数据,并转换为枚举响应类型
     *
     * @param criteria 查询条件
     * @return 枚举响应集合
     */
    List<EnumResponse<T>> handle(@Nullable QueryCriteria criteria);

    /**
     * 获取处理器名称:建议重写,名字在全局不能重复
     *
     * @return 处理器名称
     */
    default String getHandlerName() {
        return this.getClass().getSimpleName();
    }

    /**
     * 获取查询条件类:建议在实现类中重写,因为实现类已经能确定类型
     *
     * @return 查询条件类
     */
    default Class<?> getQueryCriteriaClass() {
        // 获取泛型中第二个参数的class类型
        return ((Class<?>) ((java.lang.reflect.ParameterizedType) getClass().getGenericInterfaces()[0]).getActualTypeArguments()[1]);
    }
}

2.1.7 EnumResponse

枚举响应,也可以直接作为下拉的主要数据结构。

package org.feng.commons.ec;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 枚举响应
 *
 * @param <T> 枚举值类型
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EnumResponse<T> implements Serializable {

    private static final long serialVersionUID = 7765499629374479258L;

    /**
     * 枚举值
     */
    private T value;

    /**
     * 枚举描述
     */
    private String desc;
}

2.1.8 EnumRegister

对外提供枚举注册的方法,有两种注册:
1、注册枚举set,也就是枚举常量的注册。通常通过包扫描的方式就可以完成。也就是在使用时,按照本文的配置方式即可完成注册。注册的枚举set是全量的枚举常量。

2、注册枚举查询条件。这个是因为有些时候我们的下拉会有筛选条件。当然,这个条件只有在返回到响应的时候才生效。

package org.feng.commons.ec;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * 枚举注册器
 */
public class EnumRegister {

    /**
     * 注册枚举set
     *
     * @param enumClass 枚举类
     * @param <E>       枚举类型
     * @param <T>       枚举值类型
     */
    public static <E extends Enum<E> & IBaseEnum<T>, T> void registerEnumSet(Class<E> enumClass) {
        Objects.requireNonNull(enumClass);

        // 注册枚举
        EnumSetRegister.register(enumClass);
    }

    /**
     * 注册枚举查询条件
     *
     * @param enumClass 枚举类
     * @param predicate 查询筛选条件,可以为空,为空时表示无筛选条件
     * @param <E>       枚举类型
     * @param <T>       枚举值类型
     */
    public static <E extends Enum<E> & IBaseEnum<T>, T> void registerQueryPredicate(Class<E> enumClass, Predicate<E> predicate) {
        Objects.requireNonNull(enumClass);

        // 注册查询筛选条件
        EnumPredicateRegister.register(enumClass.getSimpleName(), predicate);
    }

    /**
     * 获取枚举集合
     *
     * @param enumName 枚举名称
     * @param <E>      枚举类型
     * @param <T>      枚举值类型
     * @return 枚举集合
     */
    @SuppressWarnings("all")
    public static <E extends Enum<E> & IBaseEnum<T>, T> EnumSet<E> getEnumSet(String enumName) {
        return (EnumSet<E>) EnumSetRegister.getEnumSet(enumName);
    }

    /**
     * 获取筛选条件
     *
     * @param enumName 枚举名称
     * @param <E>      枚举类型
     * @param <T>      枚举值类型
     * @return 筛选条件
     */
    @SuppressWarnings("all")
    public static <E extends Enum<E> & IBaseEnum<T>, T> Predicate<E> getPredicate(String enumName) {
        return (Predicate<E>) EnumPredicateRegister.getPredicate(enumName);
    }

    /**
     * 扫描包批量注册枚举,无筛选条件
     *
     * @param packageName 枚举所在的包名
     */
    public static void scanPackageAndRegister(String... packageName) {
        ScanUtil.scanEnumClasses(packageName)
                .forEach((enumClass) -> {
                    // 注册枚举set
                    EnumRegister.registerEnumSet(enumClass);

                    // 注册查询筛选条件
                    EnumRegister.registerQueryPredicate(enumClass, null);
                });
    }


    /**
     * 枚举集合注册器
     */
    private static class EnumSetRegister {
        private static final Map<String, EnumSet<?>> ENUM_SET_MAP = new HashMap<>();

        private static EnumSet<?> getEnumSet(String enumName) {
            return ENUM_SET_MAP.get(enumName);
        }

        private static <E extends Enum<E> & IBaseEnum<T>, T> void register(Class<E> enumClass) {
            String enumName = enumClass.getSimpleName();

            // 如果之前注册过,则不重复注册EnumSet
            if (isRegistered(enumName)) {
                return;
            }

            // 注册enumSet
            ENUM_SET_MAP.put(enumName, IBaseEnum.getEnums(enumClass));
        }


        /**
         * 判断枚举是否已注册
         *
         * @param enumName 枚举名称
         * @return 是否已注册
         */
        private static boolean isRegistered(String enumName) {
            return ENUM_SET_MAP.containsKey(enumName);
        }

        private EnumSetRegister() {
        }
    }

    /**
     * 筛选条件注册器
     */
    private static class EnumPredicateRegister {
        private static final Map<String, Predicate<?>> PREDICATE_MAP = new HashMap<>();

        private static Predicate<?> getPredicate(String enumName) {
            return PREDICATE_MAP.get(enumName);
        }

        private static <E extends Enum<E> & IBaseEnum<T>, T> void register(String enumName, Predicate<E> predicate) {
            // 如果筛选条件为空,则默认为true
            if (predicate == null) {
                predicate = e -> true;
            }

            PREDICATE_MAP.put(enumName, predicate);
        }

        private EnumPredicateRegister() {
        }
    }
}

2.1.9 EnumConvertor

枚举转换器,用于将EnumSet转换为 EnumResponse。
并支持将普通的 List<E> 集合转换为 EnumResponse,这个是对应到非枚举对象场景的。

package org.feng.commons.ec;

import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 枚举转换器
 */
@Component
public class EnumConvertor {

    /**
     * 枚举转换成响应
     *
     * @param enumSet 枚举集合
     * @param <E>     枚举类型
     * @param <T>     枚举值类型
     * @return 响应集合
     */
    public final <E extends Enum<E> & IBaseEnum<T>, T> List<EnumResponse<T>> toEnumResponse(EnumSet<E> enumSet) {
        return enumSet.stream().map(e -> new EnumResponse<>(e.getValue(), e.getDesc())).collect(Collectors.toList());
    }

    /**
     * 枚举转换成响应
     *
     * @param enumSet   枚举集合
     * @param predicate 过滤条件
     * @param <E>       枚举类型
     * @param <T>       枚举值类型
     * @return 响应集合
     */
    public final <E extends Enum<E> & IBaseEnum<T>, T> List<EnumResponse<T>> toEnumResponse(EnumSet<E> enumSet, Predicate<E> predicate) {
        if (predicate == null) {
            return toEnumResponse(enumSet);
        }

        return enumSet.stream()
                .filter(predicate)
                .map(e -> new EnumResponse<>(e.getValue(), e.getDesc())).collect(Collectors.toList());
    }

    /**
     * 普通对象转换成枚举响应
     *
     * @param elementList   对象集合
     * @param valueFunction 值转换函数
     * @param descFunction  描述转换函数
     * @param <E>           对象类型
     * @param <T>           对象值类型
     * @return 响应集合
     */
    public final <E, T> List<EnumResponse<T>> toEnumResponse(List<E> elementList, Function<E, T> valueFunction, Function<E, String> descFunction) {
        List<EnumResponse<T>> enumResponseList = new ArrayList<>();
        if (elementList == null || elementList.isEmpty()) {
            return enumResponseList;
        }

        elementList.stream().filter(element -> {
                    T value = valueFunction.apply(element);
                    String desc = descFunction.apply(element);
                    return !ObjectUtils.isEmpty(value) && !ObjectUtils.isEmpty(desc);
                }).collect(Collectors.toMap(valueFunction, descFunction, (a, b) -> b, LinkedHashMap::new))
                .forEach((value, desc) -> enumResponseList.add(new EnumResponse<>(value, desc)));

        return enumResponseList;
    }
}

2.1.10 EnumService

枚举服务实现,提供查询枚举、查询非枚举对象、判断当前传递的name是否是非枚举对象这些方法。

EnumService也是最终我们要使用的方法。会通过自动配置的方式注入到Spring容器中。

package org.feng.commons.ec;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 枚举服务
 */
@SuppressWarnings("all")
@Slf4j
public class EnumService {

    @Autowired
    private EnumConvertor enumConvertor;

    /**
     * 非枚举对象转换处理器
     */
    private Map<String, NonEnumObjectHandler> nonEnumObjectHandlerMap;

    public EnumService(List<NonEnumObjectHandler> nonEnumObjectHandlers) {
        this.enumConvertor = enumConvertor;
        if (CollectionUtils.isEmpty(nonEnumObjectHandlers)) {
            nonEnumObjectHandlerMap = Collections.emptyMap();
        } else {
            log.info("初始化非枚举对象转换处理器:{}", nonEnumObjectHandlers);
            nonEnumObjectHandlerMap = nonEnumObjectHandlers.stream()
                    .collect(Collectors.toMap(NonEnumObjectHandler::getHandlerName, Function.identity()));
        }
    }

    /**
     * 查询枚举
     *
     * @param enumName 枚举名称,枚举类的简单类名
     * @param queryAll 是否查询所有,即不使用筛选条件
     * @param <E>      枚举类型
     * @param <T>      枚举值类型
     * @return 枚举集合
     */
    @SuppressWarnings("all")
    public <E extends Enum<E> & IBaseEnum<T>, T> List<EnumResponse<T>> queryEnums(String enumName, boolean queryAll) {
        // 处理枚举
        EnumSet<E> enumSet = EnumRegister.getEnumSet(enumName);
        if (enumSet == null) {
            return Collections.emptyList();
        }

        if (queryAll) {
            return enumConvertor.toEnumResponse(enumSet);
        }

        Predicate<E> predicate = EnumRegister.getPredicate(enumName);
        return enumConvertor.toEnumResponse(enumSet, predicate);
    }

    /**
     * 查询非枚举对象
     *
     * @param handlerName 处理器名称
     * @param criteria    查询条件
     * @param <T>         枚举值类型
     * @param <QueryCriteria> 查询条件类型
     * @return 枚举集合
     */
    public <T, QueryCriteria> List<EnumResponse<T>> queryNonEnumObject(String handlerName, Object criteria) {
        if (nonEnumObjectHandlerMap.containsKey(handlerName)) {
            NonEnumObjectHandler<T, QueryCriteria> nonEnumObjectHandler = nonEnumObjectHandlerMap.get(handlerName);

            // handler 的泛型以及handle方法参数类型必须和criteria类型一致
            if (criteria != null) {
                Assert.isTrue(nonEnumObjectHandler.getQueryCriteriaClass().isInstance(criteria), "查询条件类型错误,类型不匹配");
                return nonEnumObjectHandler.handle((QueryCriteria) criteria);
            }

            return nonEnumObjectHandler.handle(null);
        }
        return Collections.emptyList();
    }

    /**
     * 是否是非枚举对象
     *
     * @param name 名称(处理器名称或枚举名)
     * @return 是否是非枚举对象
     */
    public boolean isNonEnumObject(String name) {
        return nonEnumObjectHandlerMap.containsKey(name);
    }
}

2.1.11 EnumProperties

枚举starter 对应的配置属性。目前仅一个配置项,就是指定包名。
这个包就是你想注册的枚举所在的包,可以配置多个。

package org.feng.commons.ec;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Set;

@Data
@ConfigurationProperties(prefix = "commons.enums.component")
public class EnumProperties {

    /**
     * 指定枚举所在的包名
     */
    private Set<String> enumPackages;
}

2.1.12 EnumAutoConfiguration

枚举自动配置类。

package org.feng.commons.ec;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Set;

/**
 * 枚举自动配置类
 */
@AutoConfiguration
@Slf4j
@SuppressWarnings("all")
@AllArgsConstructor
@EnableConfigurationProperties({EnumProperties.class})
public class EnumAutoConfiguration {

    private final EnumProperties enumProperties;

    @Autowired(required = false)
    private List<NonEnumObjectHandler> nonEnumObjectHandlers;

    /**
     * 装配枚举服务
     */
    @Bean
    public EnumService enumService(@Autowired CustomerRegisterEnumConfigure customerConfigure) {
        // 获取配置的枚举包名
        Set<String> enumPackages = enumProperties.getEnumPackages();
        if (!CollectionUtils.isEmpty(enumPackages)) {
            log.info("注册枚举包名:{}", enumPackages);
            // 扫描并注册枚举
            EnumRegister.scanPackageAndRegister(enumPackages.toArray(new String[0]));
        }

        // 自定义枚举信息注册
        customerConfigure.register();

        return new EnumService(nonEnumObjectHandlers);
    }

    @Bean
    @ConditionalOnMissingBean
    public CustomerRegisterEnumConfigure registerEnumConfigure() {
        log.info("注册默认枚举配置信息");
        return () -> {
        };
    }
}

2.2 admin-starter

这个模块是一个简单的 spring-web 结构。

2.2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.feng</groupId>
    <artifactId>admin-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.feng.commons</groupId>
            <artifactId>commons-enums-starter</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                    <parameters>true</parameters>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>org.feng.starter.MainApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
2.2.2 resources/application.yml

配置项,这里配置了需要扫描注册的包名。

commons:
  enums:
    component:
      enum-packages:
        - org.feng.starter.enums
2.2.3 枚举示例
package org.feng.starter.enums;


import lombok.AllArgsConstructor;
import lombok.Getter;
import org.feng.commons.ec.IBaseEnum;

@AllArgsConstructor
@Getter
public enum OrderStatusEnum implements IBaseEnum<Integer> {

    PENDING(1, "待处理"),
    PROCESSING(2, "处理中"),
    COMPLETED(3, "已完成"),
    ;
    private final Integer value;
    private final String desc;
}

package org.feng.starter.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.feng.commons.ec.IBaseEnum;

@Getter
@AllArgsConstructor
public enum SexEnum implements IBaseEnum<Integer> {

    MALE(1, "男"),
    FEMALE(2, "女"),
    OTHER(3, "其他");

    private final Integer value;
    private final String desc;
}
2.2.4 DictDTO
package org.feng.starter.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DictDTO {

    private String key;

    private String value;

    private String type;
}

2.2.5 DictQueryCriteria

字典查询条件。

package org.feng.starter.dto;

import lombok.Data;

import java.io.Serializable;

@Data
public class DictQueryCriteria implements Serializable {

    private static final long serialVersionUID = -8600134784888228198L;

    private String type;
}

2.2.6 DictNonEnumObjectHandler

字典项的非枚举对象处理器实现。

package org.feng.starter.handler;

import org.feng.commons.ec.EnumConvertor;
import org.feng.commons.ec.EnumResponse;
import org.feng.commons.ec.NonEnumObjectHandler;
import org.feng.starter.dto.DictDTO;
import org.feng.starter.dto.DictQueryCriteria;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class DictNonEnumObjectHandler implements NonEnumObjectHandler<String, DictQueryCriteria> {

    @Autowired
    private EnumConvertor enumConvertor;

    @Override
    public List<EnumResponse<String>> handle(DictQueryCriteria criteria) {

        // 原数据,可能从库里查到的,可能从缓存获取,可能从其他接口获取等
        List<DictDTO> dicts = new ArrayList<>();
        dicts.add(new DictDTO("1", "1", "a"));
        dicts.add(new DictDTO("2", "2", "a"));
        dicts.add(new DictDTO("3", "3", "b"));

        if (criteria != null) {
            // 筛选条件
            dicts = dicts.stream().filter(dict -> dict.getType().equals(criteria.getType())).collect(Collectors.toList());
        }

        // 转换成枚举响应
        return enumConvertor.toEnumResponse(dicts, DictDTO::getKey, DictDTO::getValue);
    }

}

2.2.7 EnumConfig

枚举配置类,当需要额外配置时需要定义这样一个配置类。
主要是配置CustomerRegisterEnumConfigure 的 Bean实例,以及NonEnumObjectHandler 的实现。

如果你的项目中,只是涉及到简单的查询下拉全部枚举,则可以不用这个配置类。仅在配置中指定包名即可。

package org.feng.starter.config;

import lombok.AllArgsConstructor;
import org.feng.commons.ec.CustomerRegisterEnumConfigure;
import org.feng.commons.ec.EnumRegister;
import org.feng.starter.enums.SexEnum;
import org.feng.starter.handler.DictNonEnumObjectHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@AllArgsConstructor
@Configuration
public class EnumConfig {

    /**
     * 自定义注册枚举(没特殊需要,可以不用定义):可以覆盖扫描包生成的一部分配置
     */
    @Bean
    public CustomerRegisterEnumConfigure registerEnumConfigure() {
        return () -> {
            // 注册自己需要的枚举信息
            // 注册性别枚举,查询时的筛选条件是 忽略其他,注意这里的配置会覆盖掉扫描包中的枚举
            EnumRegister.registerQueryPredicate(SexEnum.class, e -> !SexEnum.OTHER.equals(e));
        };
    }

    /**
     * 枚举字典转换器:非枚举对象转换;或者可以在 DictNonEnumObjectHandler 类上使用 Component 注解。
     */
    @Bean
    public DictNonEnumObjectHandler dictNonEnumObjectHandler() {
        return new DictNonEnumObjectHandler();
    }
}

2.2.8 DropDownController
package org.feng.starter.controller;

import org.feng.commons.ec.EnumResponse;
import org.feng.commons.ec.EnumService;
import org.feng.starter.dto.DictQueryCriteria;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/dropDown")
public class DropDownController {

    @Autowired
    private  EnumService enumService;

    /**
     * 下拉-查枚举
     *
     * @param enumName 枚举名称
     * @param queryAll 是否查询所有
     * @return 枚举集合
     */
    @GetMapping("/queryEnum")
    public List<EnumResponse<Object>> queryEnum(@RequestParam("enumName") String enumName, @RequestParam(value = "queryAll", required = false, defaultValue = "true") boolean queryAll) {
        return enumService.queryEnums(enumName, queryAll);
    }

    /**
     * 下拉-查字典(定制化下拉,字典本身不是枚举,并且携带自定义查询请求体)
     *
     * @param handlerName 处理器名称
     * @param criteria    查询条件
     * @return 字典集合
     */
    @PostMapping("/queryDict")
    public List<EnumResponse<Object>> queryDict(@RequestParam("handlerName") String handlerName, @RequestBody DictQueryCriteria criteria) {
        return enumService.queryNonEnumObject(handlerName, criteria);
    }

    /**
     * 下拉-查全部(不携带任何查询条件),可以查非枚举对象和枚举对象的下拉
     *
     * @param name 处理器名称或枚举名
     * @return 响应集合
     */
    @GetMapping("/queryAll")
    public List<EnumResponse<Object>> query(@RequestParam("name") String name) {
        if (enumService.isNonEnumObject(name)) {
            return enumService.queryNonEnumObject(name, null);
        }
        return enumService.queryEnums(name, true);
    }
}

2.2.9 MainApplication
package org.feng.starter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = {"org.feng.commons", "org.feng.starter"})
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

三、测试运行结果

运行MainApplication,当运行成功后,使用以下接口进行调试。

3.1 查询枚举

3.1.1 查全部性别枚举

http://127.0.0.1:8080/dropDown/queryEnum?enumName=SexEnum&queryAll=true
响应结果:

[
  {
    "value": 1,
    "desc": "男"
  },
  {
    "value": 2,
    "desc": "女"
  },
  {
    "value": 3,
    "desc": "其他"
  }
]
3.1.2 使用筛选条件,查部分性别枚举

http://127.0.0.1:8080/dropDown/queryEnum?enumName=SexEnum&queryAll=false
响应结果:

[
  {
    "value": 1,
    "desc": "男"
  },
  {
    "value": 2,
    "desc": "女"
  }
]

3.2 查询全部,支持查询枚举和非枚举

使用聚合的接口方法 queryAll。内部判断name对应的是枚举还是非枚举对象处理器。

3.2.1 使用聚合接口查性别枚举

http://127.0.0.1:8080/dropDown/queryAll?name=SexEnum
响应结果:

[
  {
    "value": 1,
    "desc": "男"
  },
  {
    "value": 2,
    "desc": "女"
  },
  {
    "value": 3,
    "desc": "其他"
  }
]
3.2.2 使用聚合接口插非枚举对象

http://127.0.0.1:8080/dropDown/queryAll?name=DictNonEnumObjectHandler
响应结果:

[
  {
    "value": "1",
    "desc": "1"
  },
  {
    "value": "2",
    "desc": "2"
  },
  {
    "value": "3",
    "desc": "3"
  }
]

3.3 定制化查询非枚举对象

携带查询条件查询非枚举对象。
http://127.0.0.1:8080/dropDown/queryDict?handlerName=DictNonEnumObjectHandler

请求参数:

{
  "type": "a"
}

响应参数:

[
  {
    "value": "1",
    "desc": "1"
  },
  {
    "value": "2",
    "desc": "2"
  }
]
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐