Bean拷贝组件(注解驱动)方案设计与落地

news2024/12/23 9:21:10

一、背景

数据流转在各层之间的过程,应当是改头换面的,字段属性数量,属性名称(一般不变,但也有重构时出现变化的情况),类型名称(普遍变化例如BO、VO、DTO)。对于转换的业务对象,原始的做法时直接实例采用Getter与Setter方法进行逐一填充。这太低效了,那我们就先了解最简单的拷贝工具。

二、问题

业界采用BeanCopyUtils、Orika、ReflectionUtils等填充工具类实现字段的拷贝。默认的实现都是以Field.getName()的值进行比对拷贝。所以针对属性名发生变化的情况很容易在不注意的情况下拷贝成null值。一旦拷贝成null值,后续的业务就会受到不同程度的影响,所以我设想以下两种方案,解决字段变化,且字段耦合面比较广泛,无法直接修改字段名称的情况。

三、方案

方案一:二次封装Orkia组件,设计classMap字段映射配置类,使用ServiceLoader服务加载器加载配置类,自定义配置,随用随配。(缺点:需要维护Java类配置变化字段的映射,变化越多,类越重)

方案二:设计类型注解与字段注解,使用spring ApplicationContextAware接口设计统一快速注册classMap中的字段映射(性能高,快速装配)。

接下来的两种方案都有一些思路以及遇到的问题及其解决方法,加深相关技术理解。

四、实现

(1)方案一实现
        核心工具类BeanCopyUtil.java
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import java.util.List;
import java.util.ServiceLoader;

/**
 * @author : forestSpringH
 * @description:
 * @date : Created in 2023/9/14
 * @modified By:
 * @project: 
 */
public class BeanCopyUtil {
    private static final MapperFactory MAPPER_FACTORY;
    private static final MapperFacade MAPPER_FACADE;

    static {
        MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
        MAPPER_FACADE = MAPPER_FACTORY.getMapperFacade();
        ServiceLoader<CopyInterface> serviceLoader = ServiceLoader.load(CopyInterface.class);
        for (CopyInterface beanCopyRules : serviceLoader) {
            beanCopyRules.register(MAPPER_FACTORY);
        }
    }

    public static <S, T> T map(S source, Class<T> targetClass) {
        return MAPPER_FACADE.map(source, targetClass);
    }

    public static <S, T> List<T> mapAsList(Iterable<S> source, Class<T> targetClass) {
        return MAPPER_FACADE.mapAsList(source, targetClass);
    }

}
        接口CopyInterface.java
import ma.glasnost.orika.MapperFactory;

/**
 * @author : forestSpringH
 * @description:
 * @date : Created in 2023/9/14
 * @modified By:
 * @project: 
 */
public interface CopyInterface {
    void register(MapperFactory mapperFactory);
}
         变化字段配置类BeanCopyRules.java
import com.runjing.tms.domain.dto.applet.RiderWaybillsDistributionDetailsDto;
import com.runjing.tms.repository.model.TransportExpressWaybills;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFactory;

/**
 * @author : forestSpringH
 * @description:
 * @date : Created in 2023/9/14
 * @modified By:
 * @project: 
 */
@Slf4j
public class BeanCopyRules implements CopyInterface{
    @Override
    public void register(MapperFactory mapperFactory) {
        log.info("加载字段映射工厂自定义字段映射");
        mapperFactory.classMap(TransportExpressWaybills.class, RiderWaybillsDistributionDetailsDto.class)
                .field("expectTime","expectStartTime")
                .field("id","waybillId").byDefault().register();

    }
}
        注意点:

                ServiceLoader服务加载器需要查找META-INF.services下的文件,加载对应的类路劲,所以如果文件中填写的也是接口CopyInterface.java的路径而不是其实现类BeanCopyRules.java的路径,就会加载出来ServiceLoader<CopyInterface>内部的实例为空,无法进入循环。

        META-INF.service下的com.runjing.tms.util.orika.CopyInterface文件
com.runjing.tms.util.orika.BeanCopyRules
(2)方案二实现
         代码分包结构:

         类型注解EnableOpenFieldCopy.java
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * @author : forestSpringH
 * @description:
 * @date : Created in 2023/9/14
 * @modified By:
 * @project: 
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public @interface EnableOpenFieldCopy {
    boolean value() default true;

    boolean callSuper() default false;

    boolean callSoon() default false;
}
        字段注解FieldCopyMapping.java
import java.lang.annotation.*;

/**
 * @author : forestSpringH
 * @description: 字段映射注解
 * @date : Created in 2023/9/14
 * @modified By:
 * @project:
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FieldCopyMapping {
    String targetFieldName() default "";

    Class<?>[] targetClass() default {};
}
        SpringHolder.java关键代码段
    public static List<Class<?>> getBeanByAnnotation(Class<? extends Annotation> annotationClazz){
        Assert.notNull(serviceApplicationContext, "容器上下文获取失败");
        Assert.notNull(annotationClazz,"注解字节码入参为空");
        List<String> collect = Arrays.stream(serviceApplicationContext.getBeanNamesForAnnotation(annotationClazz)).collect(Collectors.toList());
        List<Class<?>> classList = new LinkedList<>();
        if (!CollectionUtils.isEmpty(collect)){
            collect.forEach(s -> classList.add(getBeanByName(s).getClass()));
        }
        return classList;
    }
         BeanCopyService.java核心代码段
    @PostConstruct
    public void init() {
        log.info("初始化BeanCopyService组件");
        mapperFactory = new DefaultMapperFactory.Builder().build();
        mapperFacade = mapperFactory.getMapperFacade();
        log.info("加载字段拷贝映射注解类");
        List<Class<?>> beanList = SpringHolder.getBeanByAnnotation(EnableOpenFieldCopy.class);
        register(beanList);
    }
    public <S, T> T copyBean(S source, Class<T> targetClass) {
        return mapperFacade.map(source, targetClass);
    }
    private void register(List<Class<?>> beanCopyList) {
        if (!CollectionUtils.isEmpty(beanCopyList)) {
            beanCopyList.forEach(clazz -> {
                //获取类的属性
                log.info("获取映射注解类:{}下字段集合", clazz.getName());
                List<Field> collect = Arrays.stream(clazz.getDeclaredFields()).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(collect)) {
                    collect.forEach(field -> {
                        //获取属性中打上映射注解的注解
                        if (field.isAnnotationPresent(FieldCopyMapping.class)) {
                            FieldCopyMapping annotation = field.getAnnotation(FieldCopyMapping.class);
                            String sourceFieldName = field.getName();
                            //获取注解上的目标字段名
                            String targetFieldName = annotation.targetFieldName();
                            log.info("配置字段:{} 映射 {}", sourceFieldName, targetFieldName);
                            //获取注解上的目标拷贝对象字节码数组
                            List<Class<?>> targetClazzList = Arrays.stream(annotation.targetClass()).collect(Collectors.toList());
                            if (!CollectionUtils.isEmpty(targetClazzList)) {
                                //逐一注册
                                log.info("逐一注册字段映射模型列表");
                                targetClazzList.forEach(targetClazz -> {
                                    MapperModel model = new MapperModel(clazz.getName() + targetClazz.getName(), clazz, targetClazz, sourceFieldName, targetFieldName);
                                    mapperModelList.add(model);
                                });
                            }
                        }
                    });
                }
            });

            Map<String, List<MapperModel>> group = groupByMapperKey(mapperModelList);
            if (!CollectionUtils.isEmpty(group)) {
                group.values().forEach(modelList -> {
                    log.info("开始映射:{}", modelList);
                    ClassMapBuilder<?, ?> classMapBuilder = mapperFactory.classMap(modelList.get(0).getSourceClass(), modelList.get(0).getTargetClass());
                    for (MapperModel model : modelList) {
                        if (Objects.equals(modelList.get(modelList.size() - 1), model)) {
                            log.info("映射注册完毕:{}", model.getMapperKey());
                            classMapBuilder.field(model.getSourceFieldName(), model.getTargetFieldName()).byDefault().register();
                        } else {
                            classMapBuilder.field(model.getSourceFieldName(), model.getTargetFieldName());
                        }
                    }
                });
            }
        }
    }
    private Map<String, List<MapperModel>> groupByMapperKey(List<MapperModel> modelList) {
        Map<String, List<MapperModel>> groupMap = new HashMap<>();
        if (CollectionUtils.isEmpty(modelList)) {
            return groupMap;
        }
        Set<String> keys = modelList.stream().map(MapperModel::getMapperKey).collect(Collectors.toSet());
        keys.forEach(key -> {
            List<MapperModel> mapperModels = new LinkedList<>();
            modelList.forEach(mapperModel -> {
                if (Objects.equals(mapperModel.getMapperKey(), key)) {
                    mapperModels.add(mapperModel);
                }
            });
            groupMap.put(key, mapperModels);
        });
        return groupMap;
    }

 五、测试

        Person.java测试实体
@EnableOpenFieldCopy
@Data
public class Person {

    @FieldCopyMapping(targetFieldName = "id", targetClass = {PersonBo.class, PersonDto.class})
    private int age;

    @FieldCopyMapping(targetFieldName = "personName",targetClass = {PersonDto.class})
    private String name;
}
        PersonBo.java测试实体
@Data
public class PersonBo {
    private int id;
    private String name;
}
        PersonDto.java测试实体
@Data
public class PersonDto {
    private int id;
    private String personName;
}
        单元测试代码
    @Test
    public void copy(){
        Person person = new Person();
        person.setAge(1);
        person.setName("hlc");
        PersonBo personBo = beanCopyService.copyBean(person, PersonBo.class);
        PersonDto personDto = beanCopyService.copyBean(person, PersonDto.class);
        System.out.println(personBo);
        System.out.println(personDto);
    }
        断点查看结果

代码逻辑还需要继续优化,方案二跑通之后将会将其设计成jar包。

导入使用。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1010241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试 - 字符串加密(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

企业架构LNMP学习笔记45

失效机制&#xff08;了解&#xff09; 1&#xff09;如果key过期了&#xff0c;value会及时删除么&#xff1f;空间会及时清理么&#xff1f; 2&#xff09;如果分配的存储空间&#xff0c;写满了&#xff0c;还允许写么&#xff1f; -m可以配置内存大小。 memcached 内部不…

React TypeScript | 快速了解 antd 的使用

1. 安装&#xff1a; 就像安装其他插件库一样&#xff0c;在项目文件夹下执行&#xff1a; npm install antd --save如果你安装了 yarn&#xff0c;也可以执行&#xff1a; yarn add antd2. 引用 import { Button, Tooltip } from "antd"; import "antd/dis…

Linux内核—模块编译方法

一、向内核添加新功能 1.1 静态加载法&#xff1a; 即新功能源码与内核其它代码一起编译进uImage文件内 新功能源码与Linux内核源码在同一目录结构下 在linux-3.14/driver/char/目录下编写myhello.c&#xff0c;文件内容如下&#xff1a; #include <linux/module.h> #i…

AWS创建实例 启用/禁用 自动分配公有 IP

给AWS新账户做完了对等连接&#xff0c;因为默认VPC网段都冲突 就换了VPC&#xff0c;然后发现新VPC内创建的实例都没有分配公网IP地址&#xff0c;自动分配公网IP地址变成了禁用。后续建机子需要手动修改成启用太麻烦了。 在VPC里面找到编辑子网设置&#xff0c;勾上启用自动…

不使用辅助变量的前提下实现两个变量的交换

package operator; //不用第三个辅助变量&#xff0c;实现两个数的交换 public class Demo08 {public static void change(int a, int b){a ab;b a-b;a a-b;System.out.println(a);System.out.println(b);}public static void main(String[] args) {change(900,3000);} }后续…

IDEA2023.2.1取消空包隐藏,切换包结构(Compact Middle Packages)

解决2023版idea的包结构 取消勾选即可。 取消勾选Compact Middle Packages选项后&#xff0c;再创建包时&#xff0c;即可自动创建树形结构。 仅供学习使用&#xff01;

Matlab图像处理-三基色

三基色 在计算机中&#xff0c;显示器的任何颜色&#xff08;全色域&#xff09;都可以由红、绿、蓝三种颜色组成&#xff0c;称为三基色。 三基色的原理 各原色的取值范围为0~255。 任何颜色都可以与这3种颜色以不同的比例混合获得&#xff0c; 这就是三基色的原理。 在计算…

VMWare虚拟机扩容并挂载磁盘

零、说在前面 我们在使用在VMWare创建虚机运行系统的时候&#xff0c;难免会因为前期规划不足而遇到磁盘空间被占满的情况&#xff0c;此时就需要对虚机的原有存储空间进行扩容。而整体思路&#xff0c;就是将新追加的磁盘空间归属到逻辑卷下&#xff08;类似window的给磁盘分区…

【深度学习】 Python 和 NumPy 系列教程(十七):Matplotlib详解:2、3d绘图类型(3)3D条形图(3D Bar Plot)

目录 一、前言 二、实验环境 三、Matplotlib详解 1、2d绘图类型 2、3d绘图类型 0. 设置中文字体 1. 线框图 2. 3D散点图 3. 3D条形图&#xff08;3D Bar Plot&#xff09; 一、前言 Python是一种高级编程语言&#xff0c;由Guido van Rossum于1991年创建。它以简洁、易读…

vue的由来、vue教程和M-V-VM架构思想、vue的使用、nodejs

vue vue的由来 vue教程和M-V-VM架构思想 vue的使用 nodejs vue的由来 # 1 HTML(5)、CSS(3)、JavaScript(ES5、ES6、ES11)&#xff1a;编写一个个的页面 -> 给后端(PHP、Python、Go、Java) -> 后端嵌入模板语法 -> 后端渲染完数据 -> 返回数据给前端 -> 在浏览…

C++(day7)

思维导图 Vector #include <iostream>using namespace std;template <typename V> class Myvector{ private:V *data;int Capacity;int Size; public://无参构造Myvector():data(new V[Capacity]),Capacity(0),Size(0){cout<<"无参构造函数"<&…

web端动效 PAG

之前写过一篇lottie动效的文章&#xff1a;web端动效 lottie-web 使用&#xff0c;本篇写一下PAG-web的基础使用。 PAG是腾讯开发&#xff0c;支持移动端、桌面端以及Web端的动效工作流解决方案。目标是降低或消除动效相关的研发成本&#xff0c;能够一键将设计师在 AE&#x…

VoIP之IP直呼

在VoIP应用场景中&#xff0c;有一种功能叫IP直呼&#xff0c;也称为IP直拨。 就是两个SIP终端或终端和服务器之间&#xff0c;通过呼叫&#xff08;Invite)对方IP地址实现音视频通话的功能。 抓包如下&#xff1a; 与常见的SIP账号呼叫的区别是from/to字段没有账号&#xff0…

163邮箱开通发件功能

点击设置 查看详情 开启这个功能&#xff0c;如下 开通IMAP/SMTP服务 开通POP3/SMTP服务完成了 设置邮箱完成。

C++--day7

仿照vector手动实现自己的myVector&#xff0c;最主要实现二倍扩容功能 #include <iostream>using namespace std; class myvector { private:int *num;int size;int top0; public://有参构造函数myvector(int s,int val){int *tmpnew int [s];sizes;for(int i0;i<s;…

基于FPGA的图像sobel锐化实现,包括tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将FPGA的仿真结果导入到matlab显示图像效果 2.算法运行软件版本 MATLAB2022a,vivado2019.2 3.部分核心程序 .................................…

数据结构之洗牌算法

洗牌算法 1.买一副牌(生成一副牌)2.洗牌3.揭牌完整代码 1.买一副牌(生成一副牌) 2.洗牌 3.揭牌 完整代码 card中的代码: cardDemo中的代码 测试类代码

常微分方程的基本概念(二)

目录 微分方程的解 微分方程的通解 微分方程的特解 微分方程的初始条件 积分曲率 微分方程的解 微分方程的解是指满足给定微分方程的函数或函数集合。微分方程通常描述了一个函数与其导数之间的关系&#xff0c;解是满足这种关系的函数或一组函数。 微分方程可以分为多个…

部署ZFile在线网盘

部署ZFile应用 1.安装依赖 在部署ZFile应用之前&#xff0c;需要安装环境依赖。 yum install -y java-1.8.0-openjdk unzip 2.创建安装部署目录 创建安装目录&#xff0c;用于部署ZFile。 [roothecs-4981 ~]# mkdir -p /data/zfile [roothecs-4981 ~]# cd /data/zfile/ [ro…