一、前言
最近手头上在做一个公司项目,时间紧、任务重!想着先把基础业务先跑通,后期再优化吧...可是拖得时间越久,需要修改的地方就越复杂!好了,废话不多说了,先谈谈实际的业务需求,项目要求在页面上有一个专门的字典管理模块,这个模块管理整个业务的所有的枚举值,它是将定义的枚举值存到数据库中,嗯嗯想法确实很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!,刚步入职场的一名小白,欢迎各位大佬炮轰!谢谢!