前几天在想一个功能时,卡壳了,搞了几天,终于完成了所想。
问题描述:
三级菜单联动、下拉框的选项等,都在字典表中做维护,在功能查询时,每个方法几乎能能用到,很频繁的查数据库。当然,对于增删改的操作很少,但也会有这方面的需求,因此,才会在字典表中做了增删改的维护。
但是,在日常操作中,百分之九十几的操作中只用到了查,也就是说每一步操作,可能就要去这个字典表中查询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());
}
}
}
将方法插进不同的请求方法中进行测试(例如:)
在系统中分别做一些请求,或重复,结果如下:
原始数据
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}
当然,目前有个问题,如果服务挂了,系统重启,之前做的又还原了,所以还需要再项目启动时,从数据库中取数据去添加到枚举中。