Enum 枚举类只需增加注解就能获取对应的枚举类型,实现前端获取枚举接口

      前言: 该功能实现是参考如何高效优雅的使用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.最后看看,运行效果 :

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值