需求描述
实体类AttributeVO中, name与value是一对一关系
数据库中只存储某个状态值,返回给前端需要转化为相应的文字
例如: is_deleted 1:删除 0:未删除
confirm_status 1:已确认 0:未确认 -1:不确认
pay_status 1:已付款 0:未付款 -1:已退款
假设现在有一个类 ,name字段与value字段对应,但是现在只知道value的值,需要根据value的值和映射关系来填充name值,其中实体类例如
@Data
class AttrubuteVO {
//名称;描述;
private String name;
//状态值;
private Integer value;
}
其中映射关系放在枚举类当中(也可以存储在数据库中,建造数据字典方便修改,后面需要加入持久层逻辑,这里只实现枚举版本)
@Getter
public enum SysYesNoEnum {
YES(1, "yes"),
NO(0, "no");
private Integer value;
private String name;
}
如果映射关系比教简单,可以不用枚举,在接下来的注解上添加映射关系
定义注解
我们定义两个注解,一个放在值上,另一个放在名称上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AttrName {
/**
* 与AttrValue注解对应的key
* @return key值
*/
String key();
/**
* 值列表
* @return 值列表
*/
String[] names() default {};
/**
* 可以指定枚举类来作为值映射
* @return 枚举类
*/
Class<?> enumClass() default Void.class;
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AttrValue {
/**
* 与AttrName注解对应的key
* @return key值
*/
String key();
/**
* 值列表
* @return 值列表
*/
String[] values() default {};
}
填充工具类
将添加注解的字段放在List中,方便后续使用
通过反射,获取注解中或枚举中的映射关系,将映射关系存储在Map当中
获取类字段后,循环列表,循环字段,判断是否标注注解,并且获取到映射关系
/**
* 将集合中的实体类中,根据注解@AttrValue标注字段填充@AttrName字段
* 1.在注解中指定映射关系
* AttrValue注解中的values数组个数必须和AttrName中的names个数相同
* 并且值与名称需要保持序号一致
* 2.使用枚举类映射关系
* 枚举类中,需要有name和value两个字段,并且有对应的Get方法
* 在AttrName注解中 enumClass 参数填写对应的枚举类
* AttrValue与AttrName以key作为映射关系
* @param sourceList List实体类
* @param <T> 泛型
*/
public static <T> void fillListAttribute(List<T> sourceList) {
if (sourceList.isEmpty()) {
return;
}
//将标注注解的字段名放在List中
List<Field> nameList = new ArrayList<>();
List<Field> valueList = new ArrayList<>();
Map<String, String[]> nameMap = new HashMap<>();
Map<String, String[]> valueMap = new HashMap<>();
Class<?> clazz = sourceList.get(0).getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//判断字段是否标注注解
AttrName attrName = field.getAnnotation(AttrName.class);
AttrValue attrValue = field.getAnnotation(AttrValue.class);
if (Objects.isNull(attrName) && Objects.isNull(attrValue)) {
continue;
}
// 同一个字段标注两个注解
if (Objects.nonNull(attrName) && Objects.nonNull(attrValue)) {
throw new RuntimeException("@AttrName和@AttrValue不能标注在同一个字段上");
}
//名称
if (Objects.nonNull(attrName)) {
String key = attrName.key();
nameList.add(field);
String[] names = attrName.names();
if (names.length == 0) {
//如果注解的names没有值,就获取注解的值
getEnumNameValues(attrName.enumClass(), key, nameMap, valueMap);
} else {
nameMap.put(key, names);
}
}
//值
if (Objects.nonNull(attrValue)) {
valueList.add(field);
String[] values = attrValue.values();
if (values.length == 0) {
continue;
}
String key = attrValue.key();
valueMap.put(key, values);
}
}
//映射值
mappingField(sourceList, clazz, nameList, valueList, nameMap, valueMap);
}
获取到映射关系后,循环列表,获取value字段,根据映射关系,得到name值并赋值
private static <T> void mappingField(List<T> sourceList, Class<?> clazz, List<Field> nameList, List<Field> valueList, Map<String, String[]> nameMap, Map<String, String[]> valueMap) {
for (T t : sourceList) {
//以value注解上的key作为唯一标识与name对应
Map<String, String> keyValueMap = new HashMap<>();
//读取value
for (Field valueField : valueList) {
PropertyDescriptor pd = null;
try {
pd = new PropertyDescriptor(valueField.getName(), t.getClass());
} catch (IntrospectionException e) {
throw new RuntimeException("未找到对应字段:" + valueField.getName());
}
Method readMethod = pd.getReadMethod();
//获取值
Object invoke = null;
try {
//设置私有变量访问权限
valueField.setAccessible(true);
readMethod.setAccessible(true);
invoke = readMethod.invoke(t);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("未找到对应字段:" + valueField.getName());
}
AttrValue annotation = valueField.getAnnotation(AttrValue.class);
String key = annotation.key();
//如果key、字段值、
if (strIsBlank(key) || Objects.isNull(invoke)) {
break;
}
keyValueMap.put(annotation.key(), invoke.toString());
}
//映射name
for (Field nameField : nameList) {
PropertyDescriptor pd = null;
try {
pd = new PropertyDescriptor(nameField.getName(), t.getClass());
} catch (IntrospectionException e) {
throw new RuntimeException("未找到对应字段");
}
Method wirteMethod = pd.getWriteMethod();
AttrName annotation = nameField.getAnnotation(AttrName.class);
//如果key为空或者获取不到value,直接跳过
String key = annotation.key();
if (strIsBlank(key)) {
break;
}
String value = keyValueMap.get(key);
String[] values = valueMap.get(key);
if (strIsBlank(value) || values.length == 0) {
break;
}
int i;
for (i = 0; i < values.length; i++) {
if (values[i].equals(value)) {
break;
}
}
//根据相应的索引获取name
String[] names = nameMap.get(key);
String name = names[i];
//设置值
try {
//设置私有变量访问权限
wirteMethod.setAccessible(true);
wirteMethod.invoke(t, name);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("注解 设置值失败");
}
}
}
}
获取枚举类中的映射关系
/**
* 获取枚举类的所有name字段的值
* @param clazz 枚举类
* @return 所有name字段的值
*/
private static void getEnumNameValues(Class<?> clazz, String key , Map<String, String[]> nameMap,
Map<String, String[]> valueMap) {
if (!clazz.isEnum()) {
throw new RuntimeException(clazz.getName() + "不是枚举类型");
}
Method getName = null;
Method getValue = null;
try {
getName = clazz.getMethod("getName");
getValue = clazz.getMethod("getValue");
} catch (NoSuchMethodException e) {
throw new RuntimeException("枚举类" + clazz.getName() + "没有getName方法");
}
//将所有的name, value字段的值放入list
List<String> nameList = new ArrayList<>();
List<String> valueList = new ArrayList<>();
for (Object enumConstant : clazz.getEnumConstants()) {
try {
String name = getName.invoke(enumConstant).toString();
nameList.add(name);
String value = getValue.invoke(enumConstant).toString();
valueList.add(value);
} catch (InvocationTargetException | IllegalAccessException e){
throw new RuntimeException("枚举类" + clazz.getName() + "没有name或者value字段");
}
}
nameMap.put(key, nameList.toArray(new String[]{}));
valueMap.put(key, valueList.toArray(new String[]{}));
}
测试结果
无枚举类
@Data
class AttributeTest {
@AttrName(key = "a", names = {"已付款", "未付款", "已退款"})
private String name;
@AttrValue(key = "a", values = {"0", "1", "-1"})
private Integer value;
}
结果测试
枚举类版本
@Data
class AttributeTest {
@AttrName(key = "a", enumClass = SysYesNoEnum.class))
private String name;
@AttrValue(key = "a")
private Integer value;
}
结果测试