Java动态创建枚举类

3,918 阅读4分钟

一、前言

最近手头上在做一个公司项目,时间紧、任务重!想着先把基础业务先跑通,后期再优化吧...可是拖得时间越久,需要修改的地方就越复杂!好了,废话不多说了,先谈谈实际的业务需求,项目要求在页面上有一个专门的字典管理模块,这个模块管理整个业务的所有的枚举值,它是将定义的枚举值存到数据库中,嗯嗯想法确实很nice!但是后台java怎么去数据库中查数据,然后动态生成对应的枚举呢???

二、工具类

package com.cheers.util;

import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory;

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;

/**
 * @ClassName DynamicEnumUtil
 * @Description TODO
 * @Author Cheers
 * @Date 2021/8/10 22:51
 * @Version 1.0
 */
public class DynamicEnumUtil {

    private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();

    private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException,
            IllegalAccessException {

        // let's make the field accessible
        field.setAccessible(true);

        // next we change the modifier in the Field instance to
        // not be final anymore, thus tricking reflection into
        // letting us modify the static final field
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        int modifiers = modifiersField.getInt(field);

        // blank out the final bit in the modifiers int
        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;
            }
        }
    }

    private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {
        blankField(enumClass, "enumConstantDirectory"); // Sun (Oracle?!?) JDK 1.5/6
        blankField(enumClass, "enumConstants"); // IBM JDK
    }

    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));
    }

    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;
        parms[1] = Integer.valueOf(ordinal);
        System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
//       parms[1] = parms[parms.length-1];
        return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
    }

    /**
     * Add an enum instance to the enum class given as argument
     *
     * @param <T> the type of the enum (implicit)
     * @param enumType the class of the enum to be modified
     * @param enumName the name of the new enum instance to be added to the class.
     */
    @SuppressWarnings("unchecked")
    public static <T extends Enum<?>> T addEnum(Class<T> enumType, String enumName, Class<?>[] additionalTypes, Object[] additionalValues) {

        // 0. Sanity checks
        if (!Enum.class.isAssignableFrom(enumType)) {
            throw new RuntimeException("class " + enumType + " is not an instance of Enum");
        }

        // 1. Lookup "$VALUES" holder in enum class and get previous enum instances
        Field valuesField = null;
        Field[] fields = enumType.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().contains("$VALUES")) {
                valuesField = field;
                break;
            }
        }
        AccessibleObject.setAccessible(new Field[] { valuesField }, true);

        try {

            // 2. Copy it
            T[] previousValues = (T[]) valuesField.get(enumType);
            List<T> values = new ArrayList<T>(Arrays.asList(previousValues));

            // 3. build new enum
            T newValue = (T) makeEnum(enumType, enumName, values.size(), additionalTypes, additionalValues);

            // 4. add new value
            values.add(newValue);

            // 5. Set new values field
            setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));

            // 6. Clean enum cache
            cleanEnumCache(enumType);
            return newValue;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

三、枚举类

package com.cheers.enums;

import com.cheers.util.DynamicEnumUtil;

import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName YesNoEnum
 * @Description TODO
 * @Author Cheers
 * @Date 2021/8/10 22:52
 * @Version 1.0
 */
public enum YesNoEnum {

    YES(1,"yes"),
    NO(0,"no");

    private Integer value;
    private String name;
    private static Map<Integer, YesNoEnum> enumMap = new HashMap<>();

    static{
        //  可以在这里加载枚举的配置文件 比如从 properties  数据库中加载
        //  加载完后 使用DynamicEnumUtil.addEnum 动态增加枚举值
        //  然后正常使用枚举即可
        EnumSet<YesNoEnum> set = EnumSet.allOf(YesNoEnum.class);
        for (YesNoEnum each: set ) {
            // 增加一个缓存 减少对枚举的修改
            enumMap.put(each.value, each);
        }
    }

    YesNoEnum(Integer value ,String name){
        this.value = value;
        this.name = name;
    }

    public Integer getValue() {
        return value;
    }

    public String getName() {
        return name;
    }

    // 根据关键字段获取枚举值  可以在这里做一些修改 来达到动态添加的效果
    public YesNoEnum getEnum(Integer value){
        // 这里可以做一些修改  比如若从 enumMap 中没有取得 则加载配置动态添加
        return enumMap.get(value);
    }

    //  动态添加方法  添加完后加入缓存 减少对枚举的修改
    public static YesNoEnum addEnum(String enumName, Integer value,String name){
        YesNoEnum yesNoEnum = DynamicEnumUtil.addEnum(YesNoEnum.class, enumName, new Class[]{Integer.class, String.class}, new Object[]{value, name});
        enumMap.put(value,yesNoEnum);
        return yesNoEnum;
    }
}

四、测试类

package com.cheers.test;

import com.cheers.enums.YesNoEnum;
import org.junit.Test;

/**
 * @ClassName Test02
 * @Description TODO
 * @Author Cheers
 * @Date 2021/8/10 22:54
 * @Version 1.0
 */
public class Test02 {

    @Test
    public void test() throws NoSuchMethodException {
        YesNoEnum.addEnum("测试",1,"hello");

        for (YesNoEnum value : YesNoEnum.values()) {
            System.out.println(value.getValue());
        }
    }
}

五、源码

调用枚举类中的addEnum()方法,这个方法会调用DynamicEnumUtil.addEnum(),这个方法会在对应的枚举类中添加一个枚举实例

public static YesNoEnum addEnum(String enumName, Integer value,String name){
    YesNoEnum yesNoEnum = DynamicEnumUtil.addEnum(YesNoEnum.class, enumName, new Class[]{Integer.class, String.class}, new Object[]{value, name});
    enumMap.put(value,yesNoEnum);
    return yesNoEnum;
}

下面再看一下addEnum()的具体细节
1、判断enumType是否是枚举类型

if (!Enum.class.isAssignableFrom(enumType)) {
    throw new RuntimeException("class " + enumType + " is not an instance of Enum");
}

2、获取enumType枚举类中的所有字段(包括私有字段),并修改访问权限

Field valuesField = null;
//返回由该enumType表示的所有的类或接口声明的所有字段
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
    if (field.getName().contains("$VALUES")) {
        valuesField = field;
        break;
    }
}
//修改访问权限
AccessibleObject.setAccessible(new Field[] { valuesField }, true);

3、创建枚举实例,并将其加入到原枚举实例数组中

[] previousValues = (T[]) valuesField.get(enumType);
List<T> values = new ArrayList<T>(Arrays.asList(previousValues));
T newValue = (T) makeEnum(enumType, enumName, values.size(), additionalTypes, additionalValues);
values.add(newValue);

4、下面这个方法是最核心的地方,调用reflectionFactory.newFieldAccessor()获取FieldAccessor对象,通过这个对象来进行赋值操作

setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0)));

private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException,IllegalAccessException {
    ...
    FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
    fa.set(target, value);
}

六、反思

感觉最关键的一步还没分析明白,希望各位大佬踊跃发言,一起探索其中的奥秘!

七、结尾

我是Cheers!,刚步入职场的一名小白,欢迎各位大佬炮轰!谢谢!