前言: 该功能实现是参考如何高效优雅的使用java枚举,对其中的枚举使用进一步进行改造
为何要对枚举类进行改造,其中最大的痛点问题就是,代码冗余问题:
最多的就是通过遍历去匹配然后获取对应的枚举的类型和信息,像这种:这些代码每个枚举都要写一遍,又臭又长。
// 通过类型获取名称方法
public static String getName(int index) {
for (BatchEnum c : BatchEnum.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
基于以上的情况,想要消除这样的冗余代码,可以参考如何高效优雅的使用java枚举对其中的枚举使用进一步进行改造,
但是上文的方式处理有个确定就是,还是通过枚举类进行主动注册,同样所有的枚举类都需要冗余写这一段代码,所以现在要想一种方式让系统自动注册扫描对应的枚举类,然后减少冗余代码;
所以改造点在于: 需要系统能自动扫描出对应的枚举信息,然后在系统加载类的时候执行对应的注册逻辑。
做web 项目通常会遇到一个问题,就是前端使用到的枚举值,后台也同样也会用到,此时前端作为展示端,通常数据都是由后端提供,传统做法是写在数据库里做一个数据字典的配置。但数据字典的弊端是无法在程序中直接使用枚举类得功能,比如类型和名称得转化,以及判断;所以基本上就是采用枚举类的方式定义枚举信息给前端;基于以上的枚举处理,改造一个基于自定义注解可以自动获取枚举返回给前端的实现
1.自定义一个注解,用于扫描和识别需要自动注册的枚举类信息,key,value 用作枚举的定义和名称;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface EnumDict {
String name() default "";
String key() default "index";
String value() default "name";
}
2.基于springboot 框架,实现一个自动注册器,负责扫描该项目路径下所有带有自定义注解的 类信息;通过在启动类上设置自定义的扫描路径注解 EnumScan,可以精确设置需要扫描的枚举类所在的路径信息。
@Slf4j
public class EnumDictRegister
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
private static Map<String, EnumDict> ENUM_MAP = new HashMap<String, EnumDict>();
public void setEnumMap(Map<String, EnumDict> enumMap) {
ENUM_MAP = enumMap;
}
private ResourceLoader resourceLoader;
private ClassLoader classLoader;
private Environment environment;
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* 注册 自定义注解的信息
* @param importingClassMetadata
* @param registry
*/
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logPackageScan(importingClassMetadata);
registerEnums(importingClassMetadata, registry);
}
/**
* 打印扫描的路径信息
* @param metadata
*/
private void logPackageScan(AnnotationMetadata metadata) {
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnumDict.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.size() > 0) {
log.info("section package scan: " + buildPackages((String[]) defaultAttrs.get("basePackages")));
}
}
/**
* 构建扫描的包信息
* @param basePackages
* @return
*/
private String buildPackages(String[] basePackages) {
if (basePackages == null || basePackages.length == 0) {
return "null";
}
StringBuilder stringBuilder = new StringBuilder();
for (String s : basePackages) {
stringBuilder.append(s).append(",");
}
stringBuilder.substring(0, stringBuilder.length() - 2);
return stringBuilder.toString();
}
/**
* 扫描所有的枚举值
* @param metadata
* @param registry
*/
public void registerEnums(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(EnumDict.class);
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
Map<String, EnumDict> enumMap = new HashMap<String, EnumDict>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
try {
// 这里特别注意一下类路径必须这样写
// 获取指定包下的所有类
basePackage = basePackage.replace(".", "/");
Resource[] resources = resourcePatternResolver.getResources("classpath*:" + basePackage);
MetadataReaderFactory metadata1 = new SimpleMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader metadataReader = metadata1.getMetadataReader(resource);
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
candidates.add(sbd);
}
for (BeanDefinition beanDefinition : candidates) {
String classname = beanDefinition.getBeanClassName();
// 扫描Section注解
EnumDict s = Class.forName(classname).getAnnotation(EnumDict.class);
if (s != null) {
enumMap.put(classname, s);
}
}
} catch (Exception e) {
log.error("扫描枚举类型信息失败",e);
}
}
//使用容器存储扫描出来的对象(类全限定名:section对象)
setEnumMap(enumMap);
//缓存对应的枚举信息
EnumDictProcessor.registerEnumDict();
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
if (beanDefinition.getMetadata().isIndependent()) {
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata().getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(beanDefinition.getMetadata().getClassName(),
EnumDictRegister.this.classLoader);
return !target.isAnnotation();
} catch (Exception ex) {
this.logger.error(
"Could not load target class: " + beanDefinition.getMetadata().getClassName(), ex);
}
}
return true;
}
return false;
}
};
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnumScan.class.getCanonicalName());
Set<String> basePackages = new HashSet<String>();
for (String pkg : (String[]) attributes.get("basePackages")) {
if (pkg != null && !"".equals(pkg)) {
basePackages.add(pkg);
}
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
public static Map<String, EnumDict> getEnumMap() {
return ENUM_MAP;
}
}
3.定义一个处理器,将上面扫描出来的含有自定义注解的类信息,通过反射处理成最终我们需要的放回给前端的样子。
public class EnumDictProcessor {
/**
* 获取枚举类型的下拉值
* 1.设置
*
* @return 枚举对应的下拉值
*/
public static void registerEnumDict() {
log.info("register enumDict start----");
Map<String, List<DictDTO>> dynamicDictList = new ConcurrentHashMap<>();
Map<String, EnumDict> enumDictMap = EnumDictRegister.getEnumMap();
enumDictMap.forEach((className, enumDict) -> {
try {
Class clazz = Class.forName(className);
Object[] enumConstants = clazz.getEnumConstants();
Method getKey = clazz.getMethod(buildGetMethodName(enumDict.key()));
Method getValue = clazz.getMethod(buildGetMethodName(enumDict.value()));
List<DictDTO> dictDTOS = new ArrayList<>();
for (Object enumConstant : enumConstants) {
DictDTO dictDTO = new DictDTO();
String key = String.valueOf(getKey.invoke(enumConstant));
String value = String.valueOf(getValue.invoke(enumConstant));
dictDTO.setValue(key);
dictDTO.setLabel(value);
dictDTOS.add(dictDTO);
//向工具类注册 value 值
EnumUtil.registerByValue(clazz, (Enum) enumConstant, getKey.invoke(enumConstant));
}
dynamicDictList.put(Constant.DICT_TYPE + Constant.STRING_SPLIT_UNLINE + enumDict.name(), dictDTOS);
} catch (Exception e) {
log.error("获取枚举值的枚举信息失败", e);
throw new BizException("获取枚举值的枚举信息失败");
}
});
EnumUtil.registerEnumDict(dynamicDictList);
log.info("register enumDict end ----");
}
private static String buildGetMethodName(String name) {
return Constant.GET_METHOD + name.substring(0, 1).toUpperCase() + name.substring(1);
}
}
4.定义一个工具类,获取枚举信息;基于之前的EnumUtil 的改造,增加通过枚举编码反射获取枚举信息 getEnumDict()等信息;
public class EnumUtil {
/**
* 以枚举任意值构建的缓存结构
**/
static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_VALUE = new ConcurrentHashMap<>();
/**
* 以枚举名称构建的缓存结构
**/
static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_NAME = new ConcurrentHashMap<>();
/**
* 枚举静态块加载标识缓存结构
*/
static final Map<Class<? extends Enum>, Boolean> LOADED = new ConcurrentHashMap<>();
/**
* 枚举静态块加载标识缓存结构
*/
static Map<String, List<DictDTO>> ENUM_DICT = new ConcurrentHashMap<>();
/**
* 以枚举名称构建缓存,在枚举的静态块里面调用
*
* @param clazz
* @param es
* @param <E>
*/
public static <E extends Enum> void registerByName(Class<E> clazz, E[] es) {
Map<Object, Enum> map = new ConcurrentHashMap<>();
for (E e : es) {
map.put(e.name(), e);
}
CACHE_BY_NAME.put(clazz, map);
}
/**
* 以枚举转换出的任意值构建缓存,在枚举的静态块里面调用
*
* @param clazz
* @param es
* @param enumMapping
* @param <E>
*/
public static <E extends Enum> void registerByValue(Class<E> clazz, E[] es, EnumMapping<E> enumMapping) {
if (CACHE_BY_VALUE.containsKey(clazz)) {
throw new RuntimeException(String.format("枚举%s已经构建过value缓存,不允许重复构建", clazz.getSimpleName()));
}
Map<Object, Enum> map = new ConcurrentHashMap<>();
for (E e : es) {
Object value = enumMapping.value(e);
if (map.containsKey(value)) {
throw new RuntimeException(String.format("枚举%s存在相同的值%s映射同一个枚举%s.%s", clazz.getSimpleName(), value, clazz.getSimpleName(), e));
}
map.put(value, e);
}
CACHE_BY_VALUE.put(clazz, map);
}
/**
* 以枚举转换出的任意值构建缓存,在枚举的静态块里面调用
*
* @param clazz 枚举类
* @param es 具体枚举值
* @param value 值信息
* @param <E>
*/
public static <E extends Enum> void registerByValue(Class<E> clazz, Enum es, Object value) {
Map<Object, Enum> map = CACHE_BY_VALUE.get(clazz);
if (CollectionUtil.isEmpty(map)) {
map = new ConcurrentHashMap<>();
}
map.put(value, es);
CACHE_BY_VALUE.put(clazz, map);
Map<Object, Enum> mapByName = CACHE_BY_VALUE.get(clazz);
if (CollectionUtil.isEmpty(mapByName)) {
mapByName = new ConcurrentHashMap<>();
}
map.put(es.name(), es);
CACHE_BY_NAME.put(clazz, map);
LOADED.put(clazz, true);
}
/**
* 从以枚举名称构建的缓存中通过枚举名获取枚举
*
* @param clazz
* @param name
* @param defaultEnum
* @param <E>
* @return
*/
public static <E extends Enum> E findRegisterEnumByName(Class<E> clazz, String name, E defaultEnum) {
return find(clazz, name, CACHE_BY_NAME, defaultEnum);
}
/**
* 从以枚举转换值构建的缓存中通过枚举转换值获取枚举
*
* @param clazz
* @param value
* @param defaultEnum
* @param <E>
* @return
*/
public static <E extends Enum> E findRegisterEnumByValue(Class<E> clazz, Object value, E defaultEnum) {
return find(clazz, value, CACHE_BY_VALUE, defaultEnum);
}
private static <E extends Enum> E find(Class<E> clazz, Object obj, Map<Class<? extends Enum>, Map<Object, Enum>> cache, E defaultEnum) {
Map<Object, Enum> map = null;
if ((map = cache.get(clazz)) == null) {
executeEnumStatic(clazz);// 触发枚举静态块执行
map = cache.get(clazz);// 执行枚举静态块后重新获取缓存
}
if (map == null) {
String msg = null;
if (cache == CACHE_BY_NAME) {
msg = String.format(
"枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByName(%s.class, %s.values());",
clazz.getSimpleName(),
clazz.getSimpleName(),
clazz.getSimpleName(),
clazz.getSimpleName()
);
}
if (cache == CACHE_BY_VALUE) {
msg = String.format(
"枚举%s还没有注册到枚举缓存中,请在%s.static代码块中加入如下代码 : EnumCache.registerByValue(%s.class, %s.values(), %s::getXxx);",
clazz.getSimpleName(),
clazz.getSimpleName(),
clazz.getSimpleName(),
clazz.getSimpleName(),
clazz.getSimpleName()
);
}
throw new RuntimeException(msg);
}
if (obj == null) {
return defaultEnum;
}
Enum result = map.get(obj);
return result == null ? defaultEnum : (E) result;
}
/**
* 以枚举名称构建缓存,在枚举的静态块里面调用
*
* @return 枚举对应的下拉值
*/
public static void registerEnumDict(Map<String, List<DictDTO>> enumDict) {
ENUM_DICT = enumDict;
}
/**
* 以枚举名称构建缓存,在枚举的静态块里面调用
*
* @return 枚举对应的下拉值
*/
public static Map<String, List<DictDTO>> getEnumDict() {
return ENUM_DICT;
}
/**
* 通过枚举编码反射获取枚举信息
* @param clazz 枚举类
* @param value 枚举值
* @param <E> 枚举对象
* @return
*/
/**
* 根据枚举值或者枚举的具体信息
* @param clazz
* @param value
* @param enumMapping
* @param <E>
* @return
*/
public static <E extends Enum> E getEnumDict(Class<E> clazz, Object value, EnumUtil.EnumMapping<E> enumMapping) {
try {
E[] enumConstants = clazz.getEnumConstants();
for (E enumConstant : enumConstants) {
Object valueEnum = enumMapping.value(enumConstant);
if(valueEnum.equals(value)){
return enumConstant;
}
}
} catch (Exception e) {
log.error("获取单个枚举值的枚举信息失败", e);
throw new BizException("获取枚举值的枚举信息失败");
}
return null;
}
/**
* 根据枚举值或者枚举的具体信息
* @param clazz
* @param <E>
* @return
*/
public static <E extends Enum> E[] getEnumList(Class<E> clazz) {
E[] enumConstants = clazz.getEnumConstants();
return enumConstants;
}
private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {
if (!LOADED.containsKey(clazz)) {
synchronized (clazz) {
if (!LOADED.containsKey(clazz)) {
try {
// 目的是让枚举类的static块运行,static块没有执行完是会阻塞在此的
Class.forName(clazz.getName());
LOADED.put(clazz, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
/**
* 枚举缓存映射器函数式接口
*/
@FunctionalInterface
public interface EnumMapping<E extends Enum> {
/**
* 自定义映射器
*
* @param e 枚举
* @return 映射关系,最终体现到缓存中
*/
Object value(E e);
}
}
5.在自己需要返回给前端的枚举上,加上自定义注解;
6.写一个服务类,然后通过EnumUtil.getEnumDict() 获取对应的缓存枚举信息就可以获取到最终的枚举信息了;
@Service
public class ConditionServiceImpl implements ConditionService {
/**
* 获取最终的枚举信息
* 1.枚举值的信息
* 2.配置中心的信息
* @param dictCommonAO
* @return
*/
@Override
public Map<String,List<DictDTO>> getDict(DictCommonAO dictCommonAO) {
Map<String,List<DictDTO>> dictList = new ConcurrentHashMap<>();
//获取枚举值中的信息
dictList.putAll(EnumUtil.getEnumDict());
//从配置中心获取枚举值
dictList.putAll(DynamicPropertyUtil.getDynamicPropertyDict());
return dictList;
}
}
7.最后看看,运行效果 :