反射、注解、泛型项目中结合案例
🧀需求产生
当前公司项目中,对数据的处理,通常都是写逻辑进行查询,然后进行字段赋值。尤其分布式数据库,没办法写关联查询,例如表中的字典值转译、系统用户ID转译、省市区区域转译。由于在表中存的都是code编码,亦或者是ID字段,当前端需要展示时,一般可以通过关联查询、或者是写逻辑进行处理。当前公司系统用户数据和字典数据都是存储在es里面,区域数据存储在mongodb里面。没法通过关联进行查询赋值,普通写法只能查询出来,然后一个个字段set,当然大家也都是这么做的,闲暇之余,我就想每次都这么写逻辑确实有点low,能不能通过简洁的方式进行动态处理一下。于是我就想到了注解配置,加上反射进行字段赋值的想法,泛型的应用能够适配各种实体类。说干就干!
一、自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 描述 通用值处理注解
* @author cxyfyf
* Create by 2022/12/7 17:08
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CommonValue {
/**
* 查询标识, 字典值必传 声明查询哪个 字典分类 code码 <br/>
* 多个字典值,逗号分隔 例:DICT508506,DICT747462 <br/>
* 例如字典表 等级分类编码 DICT508506,其子集就是 青铜 ITEM001、白银 ITEM002、黄金 ITEM003 类似字典形式
*/
String value() default "";
/**
* 要赋值字段名称 小驼峰命名方式,不传默认替换 查询标识 字段
*/
String name() default "";
/**
* 默认值
*/
String defaultValue() default "";
/**
* 处理值类型。默认字典值
*/
Type type() default Type.DICT;
/**
* 类型
*/
enum Type {
/**
* 字典值
*/
DICT,
/**
* 系统用户赋值
*/
SYSTEM_USER,
/**
* 区域,省市区
*/
AREA
}
}
上面注解的定义,目的进行标志要转译字段
二、注解处理器的逻辑实现
import cn.cxyfyf.base.framework.anno.CommonValue;
import cn.cxyfyf.base.framework.utils.StringUtils;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* 描述 通用值处理器 @CommonValue <br/>
* 核心通过 反射 + 注解 + 泛型 进行动态处理
* @author cxyfyf
* Create by 2022/12/8 17:08
*/
public class CommonValueProcessor<T> {
/**
* 处理单个实体类全部带@CommonValue的字段数据
* @param t model
*/
public void dealWithData(T t) {
if (Objects.isNull(t)) return; // 判空
// 获取所有要查询的值,转换为 id -> name 形式
Map<String, String> map = this.getValueMap(Lists.newArrayList(t), null);
// 通过反射 和注解值 处理 数据
this.dealWithClass(t, map, null);
}
/**
* 处理单个实体类数据
* @param t model 已实例化的实体类
* @param allowFields 要处理的字段 多个,逗号分开,小驼峰形式 例:userId,userName,phone
*/
public void dealWithData(T t, String allowFields) {
if (Objects.isNull(t)) return; // 判空
// 要处理的字段列表
List<String> allowList = StringUtils.isEmpty(allowFields) ? Collections.emptyList() :
Arrays.stream(allowFields.split(",")).map(String::trim).filter(StringUtils::isNotEmpty).toList();
// 获取所有要查询的值,转换为 id -> name 形式
Map<String, String> map = this.getValueMap(Lists.newArrayList(t), allowList);
// 通过反射 和注解值 处理 数据
this.dealWithClass(t, map, allowList);
}
/**
* 处理列表中全部带@CommonValue的字段数据
* @param list 列表数据
*/
public void dealWithListData(List<T> list) {
if (CollectionUtils.isEmpty(list)) return; // 判空
// 获取所有要查询的值,转换为 id -> name 形式
Map<String, String> map = this.getValueMap(list, null);
// 通过反射 和注解值 处理 数据
for (T t : list) {
this.dealWithClass(t, map, null);
}
}
/**
* 处理单个实体类数据
* @param list 列表数据
* @param allowFields 要处理的字段列表
*/
public void dealWithListData(List<T> list, String allowFields) {
if (CollectionUtils.isEmpty(list)) return; // 判空
// 要处理的字段列表
List<String> allowList = StringUtils.isEmpty(allowFields) ? Collections.emptyList() :
Arrays.stream(allowFields.split(",")).map(String::trim).filter(StringUtils::isNotEmpty).toList();
// 获取所有要查询的值,转换为 id -> name 形式
Map<String, String> map = this.getValueMap(list, null);
// 通过反射 和注解值 处理 数据
for (T t : list) {
this.dealWithClass(t, map, allowList);
}
}
/**
* 处理具体对象
* @param t 实体类
* @param map 数据集
* @param allowList 要处理的字段列表
*/
private void dealWithClass(T t, Map<String, String> map, List<String> allowList) {
// 获取class 对象
Class<?> aClass = t.getClass();
// 获取字段数组
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
// 判断若allowList不为空,且不在要处理的字段列表之中的字段直接跳过
if (CollectionUtils.isNotEmpty(allowList) && !allowList.contains(field.getName())) continue;
// 获取注解数据
CommonValue commonValue = field.getAnnotation(CommonValue.class);
// 不为空
if (Objects.nonNull(commonValue)) {
// 获取要调用的方法名
String setMethodName = this.getSetMethodName(commonValue, field);
// 调用set方法,赋值
this.invokeSetMethod(setMethodName, aClass, field, map, commonValue, t);
}
}
}
/**
* 通过反射调用set方法
* @param setMethodName set方法名称
* @param aClass 实体类class对象
* @param field 字段
* @param map 数据集map
* @param anno 注解
* @param t 当前对象
*/
private void invokeSetMethod(String setMethodName, Class<?> aClass, Field field, Map<String, String> map, CommonValue anno, T t) {
Method setMethod; // 方法对象
try {
// 根据名称获取当前类的set方法
setMethod = aClass.getDeclaredMethod(setMethodName, String.class);
// 设置允许访问
setMethod.setAccessible(true);
// 获取字段值
String filedValue = this.getFiledValue(field, t);
// 调用set方法,进行赋值
setMethod.invoke(t, map.getOrDefault(filedValue, anno.defaultValue()));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
// 报错继续运行
}
}
/**
* 获取set方法名
* @param commonValue 注解
* @param field 字段
* @return set方法名
*/
private String getSetMethodName(CommonValue commonValue, Field field) {
String setMethodName = "set";
//若注解中要赋值的名称为空
if (StringUtils.isEmpty(commonValue.name())) {
// 则使用当前字段的字段名进行赋值,即替换
setMethodName += StringUtils.capitalize(field.getName());
} else {
// 则使用注解,字段名
setMethodName += StringUtils.capitalize(commonValue.name());
}
return setMethodName;
}
/**
* 获取字段值
* @param field 字段
* @param t 当前实体对象
* @return 转 string 的字段值
*/
private String getFiledValue(Field field, T t) {
try {
field.setAccessible(true);
return Objects.isNull(field.get(t)) ? "" : String.valueOf(field.get(t));
} catch (IllegalAccessException e) {
return "";
}
}
/**
* 获取字段值
* @param name 字段名称
* @param t 当前实体对象
* @return 转 string 的字段值
*/
private String getFiledValue(String name, T t) {
try {
Field field = t.getClass().getDeclaredField(name);
field.setAccessible(true);
return Objects.isNull(field.get(t)) ? "" : String.valueOf(field.get(t));
} catch (IllegalAccessException | NoSuchFieldException e) {
return "";
}
}
/**
* 获取全部要处理的字典数据、系统用户数据、区域数据
* @param list 列表数据
* @param allowList 要处理的字段列表
* @return 数据集 id -> name 形式
*/
public Map<String, String> getValueMap(List<T> list, List<String> allowList) {
// 数据集 map
Map<String, String> map = new HashMap<>();
// 字典code列表
Set<String> dictList = new HashSet<>();
// 用户ID列表
Set<String> userList = new HashSet<>();
// 区域code列表
Set<String> areaList = new HashSet<>();
for (T t : list) {
// 获取class 对象
Class<?> aClass = t.getClass();
// 获取字段数组
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
// 判断若allowList不为空,且不在要处理的字段列表之中的字段直接跳过
if (CollectionUtils.isNotEmpty(allowList) && !allowList.contains(field.getName())) continue;
CommonValue commonValue = field.getAnnotation(CommonValue.class); // 注解数据
if (Objects.nonNull(commonValue)) {
if (Objects.equals(CommonValue.Type.DICT, commonValue.type())) { // 汇总字典code
// 处理code数据
List<String> dictCodes = StringUtils.isEmpty(commonValue.value()) ? Collections.emptyList() :
Arrays.stream(commonValue.value().split(",")).map(String::trim).filter(StringUtils::isNotEmpty).toList();
dictList.addAll(dictCodes);
}
if (Objects.equals(CommonValue.Type.SYSTEM_USER, commonValue.type())) { // 汇总用户ID
userList.add(this.getFiledValue(field, t));
}
if (Objects.equals(CommonValue.Type.AREA, commonValue.type())) { // 汇总区域code
areaList.add(this.getFiledValue(field, t));
}
}
}
}
map = getExampleMap(); // 获取数据来源
// Map<String, String> dict = this.getDictInfo(dictList);
// if (MapUtils.isNotEmpty(dict)) map.putAll(dict);
// Map<String, String> user = this.getUserInfo(userList);
// if (MapUtils.isNotEmpty(user)) map.putAll(user);
// Map<String, String> area = this.getAreaInfo(areaList);
// if (MapUtils.isNotEmpty(area)) map.putAll(area);
return map;
}
/**
* 模拟数据源
*/
public Map<String, String> getExampleMap() {
Map<String, String> map = new HashMap<>();
map.put("item001", "字典值001");
map.put("user001", "用户001");
map.put("370200", "青岛市");
map.put("item002", "字典值002");
map.put("user002", "用户002");
map.put("370100", "济南市");
return map;
}
}
注意包的引用,替换成自己本地的包
三、操作案例
实体类:model、to、vo、dto之类的
import cn.cxyfyf.base.framework.anno.CommonValue;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.Setter;
import java.io.Serial;
import java.io.Serializable;
/**
* dto
*/
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ExampleDTO implements Serializable {
@Serial
private static final long serialVersionUID = -4473308292250129990L;
/** id */
private Long id;
/** 登录账号 */
private String loginName;
/** 用户昵称 */
private String userName;
/** 用户类型(00系统用户 01注册用户) */
private String userType;
/** 用户邮箱 */
private String email;
/** 手机号码 */
private String phoneNumber;
/** 用户性别(0男 1女 2未知) */
private String sex;
/** 头像路径 */
private String avatar;
/** 备注 */
private String remark;
@CommonValue(value = "dict001", name = "dictName")
private String dictCode;
private String dictName;
@CommonValue(name = "userName", type = CommonValue.Type.SYSTEM_USER)
private String userId;
@CommonValue(type = CommonValue.Type.AREA, name = "cityName")
private String cityCode;
private String cityName;
public ExampleDTO(){}
}
import cn.cxyfyf.base.controller.ExampleDTO;
import cn.cxyfyf.base.test.CommonValueProcessor;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.List;
/**
* 描述
*
* @author fengyingfeng
* Create by 2022/12/9 15:49
*/
public class CommonValueTest {
/**
* 初始化CommonValueProcessor
* 在实际项目中可以通过注入使用
* 例:
* @Autowired
* CommonValueProcessor<ExampleDTO> processor;
*/
CommonValueProcessor<ExampleDTO> processor = new CommonValueProcessor<>();
public static void main(String[] args) {
CommonValueTest test = new CommonValueTest();
test.test01();
test.test02();
test.test03();
test.test04();
}
/**
* 处理单个实体类全部参数
*/
private void test01() {
ExampleDTO vo = new ExampleDTO();
vo.setDictCode("item001");
vo.setCityCode("370100");
vo.setUserId("user001");
processor.dealWithData(vo);
System.out.println(JSON.toJSONString(vo));
}
/**
* 处理单个实体类部分参数
*/
private void test02() {
ExampleDTO vo = new ExampleDTO();
vo.setDictCode("item001");
vo.setCityCode("370100");
vo.setUserId("user001");
processor.dealWithData(vo, "userId,cityCode"); // 只处理用户、城市
System.out.println(JSON.toJSONString(vo));
}
/**
* 处理列表全部参数
*/
private void test03() {
List<ExampleDTO> list = new ArrayList<>();
ExampleDTO vo = new ExampleDTO();
vo.setDictCode("item001");
vo.setCityCode("370100");
vo.setUserId("user001");
list.add(vo);
ExampleDTO vo2 = new ExampleDTO();
vo2.setDictCode("item002");
vo2.setCityCode("370200");
vo2.setUserId("user002");
list.add(vo2);
processor.dealWithListData(list);
System.out.println(JSON.toJSONString(list));
}
/**
* 处理列表部分参数
*/
private void test04() {
List<ExampleDTO> list = new ArrayList<>();
ExampleDTO vo = new ExampleDTO();
vo.setDictCode("item001");
vo.setCityCode("370100");
vo.setUserId("user001");
list.add(vo);
ExampleDTO vo2 = new ExampleDTO();
vo2.setDictCode("item002");
vo2.setCityCode("370200");
vo2.setUserId("user002");
list.add(vo2);
processor.dealWithListData(list, "userId,cityCode");
System.out.println(JSON.toJSONString(list));
}
}
# test01
{"cityCode":"370100","cityName":"济南市","dictCode":"item001","dictName":"字典值001","userId":"user001","userName":"用户001"}
# test02
{"cityCode":"370100","cityName":"济南市","dictCode":"item001","userId":"user001","userName":"用户001"}
# test03
[{"cityCode":"370100","cityName":"济南市","dictCode":"item001","dictName":"字典值001","userId":"user001","userName":"用户001"},{"cityCode":"370200","cityName":"青岛市","dictCode":"item002","dictName":"字典值002","userId":"user002","userName":"用户002"}]
# test04
[{"cityCode":"370100","cityName":"济南市","dictCode":"item001","userId":"user001","userName":"用户001"},{"cityCode":"370200","cityName":"青岛市","dictCode":"item002","userId":"user002","userName":"用户002"}]
本文作者:玩单机的零度
本文链接:https://www.cnblogs.com/cxyfyf/p/16969535.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步