运行时修改枚举测试类之增删改查

前几天在想一个功能时,卡壳了,搞了几天,终于完成了所想。
问题描述:
三级菜单联动、下拉框的选项等,都在字典表中做维护,在功能查询时,每个方法几乎能能用到,很频繁的查数据库。当然,对于增删改的操作很少,但也会有这方面的需求,因此,才会在字典表中做了增删改的维护。
但是,在日常操作中,百分之九十几的操作中只用到了查,也就是说每一步操作,可能就要去这个字典表中查询n次。我就在想,能不能将这些"常量"全部分成n各枚举类型,每次查的时候不用去数据库中查,而是通过枚举类“.”枚举值去获取数据。
然而,这些又不是一成不变的常量,字典中回去做修改。
那么,能不能当我在字典中修改时,也去动态地去修改我的枚举类呢?
感谢各博主前辈们的分享,终于东拼西凑,解决了这一问题。于是,我也将我写的示例分享出来,供后来者参考。

首先,我先建了个枚举类

public enum EnumTest {
     

    a(111,"AAA",12),
    b(222,"BBB",12),
    c(333,"CCC",12);

    private int value;
    private String text;
    private int num;

    EnumTest(int value,String text,int num){
     
        this.value = value;
        this.text = text;
        this.num = num;
    }

    public int getValue() {
     
        return value;
    }

    public void setValue(int value) {
     
        this.value = value;
    }

    public String getText() {
     
        return text;
    }

    public void setText(String text) {
     
        this.text = text;
    }

    public int getNum() {
     
        return num;
    }

    public void setNum(int num) {
     
        this.num = num;
    }

    @Override
    public String toString() {
     
        return "EnumTest{" +
                "value=" + value +
                ", text='" + text + '\'' +
                ", num=" + num +
                '}';
    }

}

然后,借鉴前辈封装的添加方法,我进行扩展了删除,并进行一些中文注释

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;

class DynamicEnumUtil {
     

    private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();

    private static void setFailsafeFieldValue(Field field, Object target,
                                              Object value) throws NoSuchFieldException, IllegalAccessException {
     
        //令这个字段(field)变成可访问
        field.setAccessible(true);
        //接下来通过反射机制将字段的修饰符由‘最终的’改为’可访问的‘
        //getDeclaredField(name) 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
        // name 参数是一个 String,它指定所需字段的简称。
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        //PUBLIC: 1、PRIVATE: 2、PROTECTED: 4、STATIC: 8、FINAL: 16、SYNCHRONIZED: 32、
        //VOLATILE: 64、TRANSIENT: 128、NATIVE: 256、INTERFACE: 512、ABSTRACT: 1024、STRICT: 2048
        int modifiers = modifiersField.getInt(field);
        //清空修饰符int中的最后一位
        //&:位与运算符,只有两个操作数都是true,结果才是true; ~:位非运算符:如果位为0,结果是1,如果位为1,结果是0.
        modifiers &= ~Modifier.FINAL;
        modifiersField.setInt(field, modifiers);
        FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
        fa.set(target, value);
    }

    private static void blankField(Class<?> enumClass,
                                   String fieldName) throws NoSuchFieldException, IllegalAccessException {
     
        for (Field field : Class.class.getDeclaredFields()) {
     
            if (field.getName().contains(fieldName)) {
     
                AccessibleObject.setAccessible(new Field[] {
      field }, true);
                setFailsafeFieldValue(field, enumClass, null);
                break;
            }
        }
    }

    /**
     * 清除枚举类缓存
     * @param enumClass                 枚举类
     * @throws NoSuchFieldException     找不到特定方法时引发的异常
     * @throws IllegalAccessException   无访问权限时引发的异常
     */
    private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {
     
        blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
        blankField(enumClass, "enumConstants"); // IBM JDK
    }

    /**
     * 获取构造函数访问器
     * @param enumClass                 枚举类
     * @param additionalParameterTypes  附加参数类型
     * @throws NoSuchMethodException    找不到特定方法时引发的异常
     */
    private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes)
            throws NoSuchMethodException {
     
        Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
        parameterTypes[0] = String.class;
        parameterTypes[1] = int.class;
        System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
        return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
    }

    /**
     * 创建新的枚举类
     * @param enumClass         要新增的枚举的类
     * @param value             要新增的枚举名称(EnumName)
     * @param ordinal           原数组的个数
     * @param additionalTypes   要新增的枚举类型(例:int.class,String.class,int.class)
     * @param additionalValues  要新增的枚举值(例:444,"DDD",1)
     */
    private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes,
                                   Object[] additionalValues) throws Exception {
     
        Object[] parms = new Object[additionalValues.length + 2];
        parms[0] = value;   //要新增的枚举名称(EnumName)
        parms[1] = ordinal; //原数组的个数
        //将原数组additionalValues从下标为0到长度为additionalValues.length的数据复制一份放到目标数组从下标为2的位置,
        //从而实现在目标数组的某下标之后拼接原数组某段数据
        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
        return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
    }

    /**
     * 将枚举实例添加到作为参数给定的枚举类
     *
     * @param                枚举的类型(隐式)
     * @param enumType          要修改的枚举的类
     */
    private static <T extends Enum<?>> Field valuesFieldDeal(Class<T> enumType) {
     
        //1.健全性检查
        if (!Enum.class.isAssignableFrom(enumType)) {
     
            throw new RuntimeException("class " + enumType + " is not an instance of Enum");
        }
        //2.查找枚举类中的"$VALUES"holder并获取以前的枚举实例
        Field valuesField = null;
        Field[] fields = enumType.getDeclaredFields();
        for (Field field : fields) {
     
            if (field.getName().contains("$VALUES")) {
     
                valuesField = field;
                break;
            }
        }
        //令这个字段(field)变成可访问
        AccessibleObject.setAccessible(new Field[] {
      valuesField }, true);
        return valuesField;
    }

    /**
     * 将枚举实例添加到作为参数给定的枚举类
     *
     * @param                枚举的类型(隐式)
     * @param enumType          要修改的枚举的类
     * @param enumName          要添加到类中的新枚举实例的名称。
     */
    @SuppressWarnings("unchecked")
    static <T extends Enum<?>> void removeEnum(Class<T> enumType, String enumName) {
     
        Field valuesField = valuesFieldDeal(enumType);
        try {
     
            //3.复制
            assert valuesField != null;
            T[] previousValues = (T[]) valuesField.get(enumType);
            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
            List<T> valueList = remove(values, enumName);
            //5.设置新值字段
            setFailsafeFieldValue(valuesField, null, valueList.toArray((T[]) Array.newInstance(enumType, 0)));
            //6.清除枚举类缓存
            cleanEnumCache(enumType);
        } catch (Exception e) {
     
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 将枚举实例添加到作为参数给定的枚举类
     *
     * @param                枚举的类型(隐式)
     * @param enumType          要修改的枚举的类
     * @param enumName          要添加到类中的新枚举实例的名称。
     * @param additionalTypes   附加类型
     * @param additionalValues  附加值。
     */
    @SuppressWarnings("unchecked")
    static <T extends Enum<?>> void addEnum(Class<T> enumType, String enumName,
                                            Class<?>[] additionalTypes, Object[] additionalValues) {
     
        Field valuesField = valuesFieldDeal(enumType);
        try {
     
            //3.复制
            assert valuesField != null;
            T[] previousValues = (T[]) valuesField.get(enumType);
            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
            boolean containFlag = contains(values, enumName);
            if (containFlag) return;
            //创建新的枚举类
            T newValue = (T) makeEnum(enumType, enumName, values.size(), additionalTypes, additionalValues);
            //4.添加新值
            values.add(newValue);
            //5.设置新值字段
            setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));
            //6.清除枚举类缓存
            cleanEnumCache(enumType);
        } catch (Exception e) {
     
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    //判断是否已经存在
    private static <T extends Enum<?>> boolean contains(List<T> values, String enumName) {
     
        return values.stream().anyMatch(value -> enumName.equals(value.name()));
    }

    /**
     * 判断是否已经存在,如果存在,则删除(实际是将不匹配的数据放到新的list中)
     * @param values    要筛选的数组
     * @param enumName  要筛选的目标枚举名称
     * @param        枚举的类型(隐式)
     * @return          筛选后的list
     */
    private static <T extends Enum<?>> List<T> remove(List<T> values, String enumName) {
     
        List<T> list = new ArrayList<>();
        values.forEach(value -> {
     if (!value.name().equals(enumName)) {
     list.add(value);}});
        return list;
    }
}

最后,写一个测试用例

public class MainTest {
     
    
    /**
     * 新增枚举实例
     * @param enumName  要添加到类中的新枚举实例的名称。
     * @param value     枚举类属性value
     * @param text      枚举类属性text
     * @param num       枚举类属性num
     */
    private static void addEnumTest(String enumName,int value,String text,int num){
     
        DynamicEnumUtil.addEnum(EnumTest.class,enumName,new Class<?>[] {
     int.class,String.class,int.class},new Object[] {
     value,text,num});
    }

    /**
     * 删除枚举实例
     * @param enumName  要删除的新枚举实例的名称。
     */
    private static void removeTestEnum(String enumName){
     
        DynamicEnumUtil.removeEnum(EnumTest.class,enumName);
    }

    //原
    public static void test1(){
     
        for (EnumTest EnumTest:EnumTest.values()){
     
            System.out.println(EnumTest.toString());
        }
    }
    //新增
    public static void test2(){
     
        addEnumTest("d",444,"DDD",1);
        addEnumTest("e",555,"EEE",2);
        addEnumTest("f",666,"FFF",3);
        addEnumTest("g",777,"GGG",4);
        for (EnumTest EnumTest:EnumTest.values()){
     
            System.out.println(EnumTest.toString());
        }

    }
    //修改
    static void test3(){
     
        EnumTest.a.setValue(1212);
        EnumTest.a.setText("1212");
        EnumTest.a.setNum(1212);
        for (EnumTest EnumTest:EnumTest.values()){
     
            System.out.println(EnumTest.toString());
        }
    }
    //删除
    static void test4(){
     
        removeTestEnum("g");
        for (EnumTest EnumTest:EnumTest.values()){
     
            System.out.println(EnumTest.toString());
        }
    }
    
}

将方法插进不同的请求方法中进行测试(例如:)
运行时修改枚举测试类之增删改查_第1张图片
在系统中分别做一些请求,或重复,结果如下:

原始数据
EnumTest{
     value=111, text='AAA', num=12}
EnumTest{
     value=222, text='BBB', num=12}
EnumTest{
     value=333, text='CCC', num=12}

添加
EnumTest{
     value=111, text='AAA', num=12}
EnumTest{
     value=222, text='BBB', num=12}
EnumTest{
     value=333, text='CCC', num=12}
EnumTest{
     value=444, text='DDD', num=1}
EnumTest{
     value=555, text='EEE', num=2}
EnumTest{
     value=666, text='FFF', num=3}
EnumTest{
     value=777, text='GGG', num=4}

添加(再一次请求,可见结果没变化,表明已经存在就不再添加)
EnumTest{
     value=111, text='AAA', num=12}
EnumTest{
     value=222, text='BBB', num=12}
EnumTest{
     value=333, text='CCC', num=12}
EnumTest{
     value=444, text='DDD', num=1}
EnumTest{
     value=555, text='EEE', num=2}
EnumTest{
     value=666, text='FFF', num=3}
EnumTest{
     value=777, text='GGG', num=4}

修改a
EnumTest{
     value=1212, text='1212', num=1212}
EnumTest{
     value=222, text='BBB', num=12}
EnumTest{
     value=333, text='CCC', num=12}
EnumTest{
     value=444, text='DDD', num=1}
EnumTest{
     value=555, text='EEE', num=2}
EnumTest{
     value=666, text='FFF', num=3}
EnumTest{
     value=777, text='GGG', num=4}

删除g
EnumTest{
     value=1212, text='1212', num=1212}
EnumTest{
     value=222, text='BBB', num=12}
EnumTest{
     value=333, text='CCC', num=12}
EnumTest{
     value=444, text='DDD', num=1}
EnumTest{
     value=555, text='EEE', num=2}
EnumTest{
     value=666, text='FFF', num=3}

当然,目前有个问题,如果服务挂了,系统重启,之前做的又还原了,所以还需要再项目启动时,从数据库中取数据去添加到枚举中。

你可能感兴趣的:(学习笔记,java,反射)