反射、注解、泛型项目中结合案例

🧀需求产生

当前公司项目中,对数据的处理,通常都是写逻辑进行查询,然后进行字段赋值。尤其分布式数据库,没办法写关联查询,例如表中的字典值转译、系统用户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男 12未知) */
    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"}]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值