当前位置: 首页 > news >正文

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

一、背景

数据流转在各层之间的过程,应当是改头换面的,字段属性数量,属性名称(一般不变,但也有重构时出现变化的情况),类型名称(普遍变化例如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{@Overridepublic 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核心代码段
    @PostConstructpublic 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;
}
        单元测试代码
    @Testpublic 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包。

导入使用。 

相关文章:

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

一、背景 数据流转在各层之间的过程&#xff0c;应当是改头换面的&#xff0c;字段属性数量&#xff0c;属性名称&#xff08;一般不变&#xff0c;但也有重构时出现变化的情况&#xff09;&#xff0c;类型名称&#xff08;普遍变化例如BO、VO、DTO&#xff09;。对于转换的业…...

hive的建表语句

hive建表语句CREATE TABLE ccwn_zh_event_push (customerid string,cardnumber string,accountnumber string,eventcode string,eventtime string,activities string,activityRefuseCode string,lables string)PARTITIONED BY(dt string)ROW FORMAT SERDE org.apache.hadoop.hi…...

提升效率:PostgreSQL准确且快速的数据对比方法

作为一款强大而广受欢迎的开源关系型数据库管理系统&#xff0c;PostgreSQL 在数据库领域拥有显著的市场份额。其出色的可扩展性、稳定性使其成为众多企业和项目的首选数据库。而在很多场景下&#xff08;开发|生产环境同步、备份恢复验证、数据迁移、数据合并等&#xff09;&a…...

【轻NAS】Windows搭建可道云私有云盘,并内网穿透公网访问

文章目录 1.前言2. Kodcloud网站搭建2.1. Kodcloud下载和安装2.2 Kodcloud网页测试 3. cpolar内网穿透的安装和注册4. 本地网页发布4.1 Cpolar云端设置4.2 Cpolar本地设置 5. 公网访问测试6.结语 1.前言 云存储作为近些年兴起的概念&#xff0c;成功吸引了各大互联网厂商下场&…...

计算机网络 第一章:概述

目录 一.因特网概述 1.1网络、互联网(互连网)和因特网 1.2internet与Internet的区别 1.3因特网服务提供者ISP(Internet Service Provider) 1.4因特网组成 二.三种交换方式 2.1电路交换 2.2分组交换(重点) 2.3报文交换 三.计算机网络的定义和分类 四.计算机网络的性能…...

centos7 firewalld ip转发设置、安装docker-compose出现错误、docker-compose部署Yapi

一 centos7 firewalld ip转发设置 #!/bin/bash #开启系统路由模式功能 vim /etc/sysctl.conf #添加下面一行 net.ipv4.ip_forward1 #运行这个命令会输出上面添加的那一行信息&#xff0c;意思是使内核修改生效 sysctl -p #开启firewalld systemctl start firewalld #防火墙开启…...

Cglib代理和JDK代理原理的区别

一、JDK Jdk动态代理&#xff0c;拿到目标类所继承的接口&#xff0c;生成代理类&#xff0c;并且代理类也会实现和目标类一样的接口。 二、Cglib Cglib代理功能更强&#xff0c;无论目标类是否实现接口都可以代理&#xff0c;他是基于继承的方式类代理目标类&#xff0c;如果…...

论文阅读-A General Language for Modeling Social Media Account Behavior

论文链接&#xff1a;https://arxiv.org/pdf/2211.00639v1.pdf 目录 摘要 1 Introduction 2 Related work 2.1 Automation 2.2 Coordination 3 Behavioral Language for Online Classification 3.1 BLOC alphabets 3.1.1 Action alphabet 3.1.2 Content alphabets 3.…...

Python中的异常处理4-3

在《Python中的异常处理4-2》中提到&#xff0c;except语句后面可以加上具体的异常类型。有时我们需要这个异常的其他细节&#xff0c;此时可以使用except...as语句。 1 except...as语句 except..as语句的格式为 except 异常类型 as 异常实例名 从以上格式中可以看到&#…...

Swift学习内容精选(一)

Swift 可选(Optionals)类型 Swift 的可选&#xff08;Optional&#xff09;类型&#xff0c;用于处理值缺失的情况。可选表示"那儿有一个值&#xff0c;并且它等于 x "或者"那儿没有值"。 Swfit语言定义后缀&#xff1f;作为命名类型Optional的简写&…...

Marin说PCB之封装设计系列---(02)--异形焊盘的封装设计总结

每天下班回家看电视本来是一件很美好的事情&#xff0c;可是正当我磕着瓜子看着异人之下的时候&#xff0c;手机突然响起来了&#xff0c;我以为是我们组哪个同事找我呢。一接电话居然是我的老朋友陈世美陈总&#xff0c;江湖人称少妇杀手。给我打电话主要是说他最近遇到一个异…...

SpringBoot使用AOP详解

目录 1 AOP是什么2 AOP概念3 Springboot中使用AOP4 AOP原理5 应用场景 1 AOP是什么 AOP&#xff08;Aspect Oriented Programming&#xff09;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续&…...

【Qt】QGroundControl入门1:介绍

1、简介 1.1 QGroundControl QGroundControl是一款开源的无人机地面控制站软件,依赖Qt库,简称QGC。 QGroundControl为任何支持 MAVLink协议 的无人机提供完整的飞行控制和任务规划。QGroundControl为 PX4 和 ArduPilot 驱动的无人机提供驱动配置。 源码:https://github.co…...

第36章_瑞萨MCU零基础入门系列教程之步进电机控制实验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…...

198.打家劫舍,213.打家劫舍II,337.打家劫舍III

代码随想录训练营第48天|198.打家劫舍&#xff0c;213.打家劫舍II&#xff0c;337.打家劫舍III 198.打家劫舍文章思路代码 213.打家劫舍III文章思路代码 337.打家劫舍III文章思路代码 总结 198.打家劫舍 文章 代码随想录|0198.打家劫舍 思路 d p [ i ] M a x ( d p [ i − …...

msvcp140.dll是什么东西,如何解决msvcp140.dll丢失的问题的方法分享

在现代生活中&#xff0c;电脑已经成为我们工作、学习和娱乐的重要工具。然而&#xff0c;电脑问题的出现往往会给我们的生活带来不便。其中&#xff0c;"msvcp140.dll丢失"是一个常见的电脑问题。本文将详细介绍这个问题的原因和解决方法&#xff0c;帮助大家更好地…...

音视频 SDL vs2017配置

一、首先我把SDL放在了C盘根目录下 二、新建空项目 三、添加main.cpp //main.cpp #include<iostream> #include <SDL.h>int main(int argc, char* argv[]) // main函数头必须这样写&#xff0c;因为SDL把main定义成了宏 {SDL_Delay(3000); // 让窗口在屏幕上保持…...

前端面试要点

0914 JScript深拷贝和浅拷贝&#xff08;js解构赋值算哪个&#xff1f;&#xff09; 深拷贝和浅拷贝 回流和重绘 回流和重绘 webpack打包流程 Webpack打包 虚拟DOM 虚拟DOM git合并分支 git合并分支 CSS盒子模型 CSS盒子模型 0911 WebPack分包 webpack分包 ts泛型 ts泛型 优化…...

shell字符串处理之字符串比较

引言 我们在使用shell编写脚本时&#xff0c;经常需要对字符串进行处理&#xff0c;如字符串大小比较、模式匹配、替换、截断等。本文将梳理字符串比较中常见的用法。 字符串比较 1. 直接比较字符串 a$1 b$2 c"" # 等于 if [ $a "abc" ];thenecho $a …...

怎么获取别人店铺的商品呢?

jd.item_search_shop(获得店铺的所有商品) 为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个JD应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载JDAPI的SDK并掌握基本的API…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

day36-多路IO复用

一、基本概念 &#xff08;服务器多客户端模型&#xff09; 定义&#xff1a;单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用&#xff1a;应用程序通常需要处理来自多条事件流中的事件&#xff0c;比如我现在用的电脑&#xff0c;需要同时处理键盘鼠标…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

nnUNet V2修改网络——暴力替换网络为UNet++

更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...