是时候丢掉 BeanUtils 了!

BeanMapper-开源 Java的简单/高效的递归属性映射器。 从外部Bean复制/转换/合并属性到目标Bean,反之亦然。 泛型/类型感知。 隐式在数组和集合之间以及已配置的类之间进行转换。 立即下载

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

前后端微服务商城项目,手把手教学!

前言

为了更好的进行开发和维护,我们都会对程序进行分层设计,例如常见的三层,四层,每层各司其职,相互配合。也随着分层,出现了 VO,BO,PO,DTO,每层都会处理自己的数据对象,然后向上传递,这就避免不了经常要将一个对象的属性拷贝给另一个对象。

例如我有一个 User 对象和一个 UserVO 对象,要将 User 对象的10个属性赋值个 UserVO 的同名属性:

  • 一种方式是手写,一个属性一个属性赋值,相信大家最开始学习时都是这么干的,这种方式就是太低效了。

  • 在 idea 中可以安装插件帮我们快速生成 set 属性代码,虽然还是逐个属性赋值,但比一个个敲,效率提高了很多。

上面两种方式虽然最原始,做起来很麻烦,容易出错,但程序运行效率是最高的,现在仍有不少公司要求这么做,一是这样运行效率高,二是不需要引入其它的组件,避免出现其它问题。

但对于我们来说,这种操作要是多了,开发效率和代码可维护性都会受到影响,这种赋值属性代码很长,看起来很不舒服,所以有了下面几种方式。

bean copier

apache 的 BeanUtils,内部使用了反射,效率很低,在《阿里java开发规范中》明令禁止使用,这里就不过多讨论。

bcc16c65034411581a18df59b74ab62d.png

spring的BeanUtils,对 apache BeanUtils 做了优化,运行效率较高,可以使用。

BeanUtils.copyProperties(source, target);
BeanUtils.copyProperties(source, target, "id", "createTime"); //不拷贝指定的字段

cglib 的 BeanCopier,使用动态技术代替反射,在运行时生成一个子类,只有在第一次动态生成类时慢,后面基本就本接近原始的set,所以呀运行效率比上面两种要高很多。

BeanCopier beanCopier = BeanCopier.create(SourceData.class, TargetData.class, false); 
beanCopier.copy(source, target, null);

我们使用的是Spring BeanUtils,至少出现过两次问题:

  • 一次是拷贝一方的对象类型变了,由int变成long,source.id int 拷贝到 target.id long 结果是空,因为类型不匹配,BeanUtils 不会拷贝。由于是使用反射,所以当时修改类型时,只修改了编译报错的地方,忘记这种方式,导致结果都是空,这也很难怪开发,这种方式太隐蔽了。同样如果属性重命名,也会得到一个空,并且只能在运行时发现。

  • 另一次拷贝的时候会把所有属性都拷过去,漏掉忽略主键 id,结果在插入的时候报了唯一索引冲突。我们的场景比较特殊,id,createTime,updateTime 这三个字段是表必须有的,通常也是不能被拷贝的,如果每个地方都手写忽略,代码比较麻烦也容易忘记。

上面3种方式都非常简单,意味着功能非常有限,如果你有一些复杂场景的拷贝,它们就无法支持,例如深拷贝,拷贝一个 List。

另外一个最重要的点是:它们都是运行时的,这意味着你无法在编译时得到任何帮助,无法提前发现问题。

从标题可以看出我们本篇要讲的是另一个 copier:MapStruct,接下来就看下它是如何解决我们问题的。

MapStruct

MapStruct 是一个基于 Java 注解处理器,用于生成类型安全且高性能的映射器。总结一下它有以下优点:

  • 高性能。 使用普通方法赋值,而非反射,MapStruct 会在编译期间生成类,使用原生的 set 方法进行赋值,所以效率和手写 set 基本是一样的。

  • 类型安全。 MapStruct 是编译时的,所以一旦有类型、名称等不匹配问题,就可以提前编译报错。

  • 功能丰富。 MapStruct 的功能非常丰富,例如支持深拷贝,指定各种拷贝行为。

  • 使用简单。 你所需要做的就是定义接口和拷贝的行为,MapStruct 会在编译期生成实现类。

  • 全新基于SpringCloud+Vue的微商城项目完结啦,欢迎学习!

示例

和学习其它组件一样,我们先用起来,准备两个类,SourceData,TargetData 属性完全一样,其中 TestData 是另一个类。

public class SourceData {

    private String id;
    private String name;
    private TestData data;
    private Long createTime;

    public String getId() {
            return id;
    }
    public void setId(String id) {
            this.id = id;
    }
    public String getName() {
            return name;
    }
    public void setName(String name) {
            this.name = name;
    }
    public TestData getData() {
            return data;
    }
    public void setData(TestData data) {
            this.data = data;
    }
    public Long getCreateTime() {
            return createTime;
    }
    public void setCreateTime(Long createTime) {
            this.createTime = createTime;
    }
}
导入包 pom
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>${org.mapstruct.version}</version>
</dependency>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
定义接口

这里的 Mapper 是 MapStruct 的,可不是 Mybatis 的。

@Mapper
public interface BeanMapper {
    BeanMapper INSTANCE = Mappers.getMapper(BeanMapper.class);
    TargetData map(SourceData source);
}
使用
SourceData source = new SourceData();
source.setId("123");
source.setName("abc");
source.setCreateTime(System.currentTimeMillis());
TestData testData = new TestData();
testData.setId("123");

TargetData target = BeanMapper.INSTANCE.map(source);
System.out.println(target.getId() + ":" + target.getName() + ":" + target.getCreateTime());
//true
System.out.println(source.getData() == target.getData());

可以看到使用非常简单,默认情况下 MapStruct 是浅拷贝,所以看到最后一个输出是 true。编译后我们可以在 target 目录下找到帮我们生成的一个接口实现类 BeanMapperImpl,如下:

a3c5fbf4a3ef3e7b0500595cf24d2230.png
深拷贝

可以看到它也是帮生成 set 代码,且默认是浅拷贝,所以上面最后一个输出是 true。如果想变成深拷贝,在 map 方法上标记一下 DeepClone 即可:

@Mapping(target = "data", mappingControl = DeepClone.class) 
TargetData map(SourceData source);

重新编译一下,看到生成的代码变成如下,这次是深拷贝了。

70895ab001a9f4cf7af1294e2b640f1b.png
集合拷贝

支持,新增一个接口方法即可。

List<TestData> map(List<TestData> source);
类型不一致

如果我将 TargetData 的 createTime 改成 int 类型,再编译一下,生成代码如下:

6670fcb7f621b42c4cf322b44594e642.png

可以看到它会默认帮我们转换,但这是个隐藏的问题,如果我希望它能在编译时就提示,那么可以在 Mapper 注解上指定一些类型转换的策略是报错,如下:

全新基于SpringCloud+Vue的微商城项目完结啦,欢迎学习!

@Mapper(typeConversionPolicy = ReportingPolicy.ERROR)

重新编译会提示错误:

java: Can't map property "Long createTime". It has a possibly lossy conversion from Long to Integer.
禁止隐式转换

如果我将类型改成 String 呢,编译又正常了,生成代码如下:

3c7ee1c9655f8b0d7aa4ef3ec6910124.png

对于 String 和其它基础类型的包装类,它会隐式帮我们转换,这也是个隐藏问题,如果我希望它能在编译时就提示,可以定义一个注解,并在 Mapper 中指定它,如下:

@Retention(RetentionPolicy.CLASS)
@MappingControl(MappingControl.Use.DIRECT)
@MappingControl(MappingControl.Use.MAPPING_METHOD)
@MappingControl(MappingControl.Use.COMPLEX_MAPPING)
public @interface ConversationMapping {
}

@Mapper(typeConversionPolicy = ReportingPolicy.ERROR, mappingControl = ConversationMapping.class)

重新编译会提示报错:

java: Can't map property "Long createTime" to "String createTime". Consider to declare/implement a mapping method: "String map(Long value)".

这个可以参见 issus 上的讨论:issus1428  issus3186

忽略指定字段

忽略字段可以使用 Mapping 注解的 ignore 属性,如下:

@Mapping(target = "id", ignore = true)

如果我想忽略某些字段,并且复用起来,就像我们的场景应用,可以定义一个IgnoreFixedField注解,然后打在方法上

@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Target(METHOD)
@Retention(RUNTIME)
@Documented
@interface IgnoreFixedField {
}

@IgnoreFixedField
@Mapping(target = "data", mappingControl = DeepClone.class)
TargetData map(SourceData source);

这样只要打上这个注解,这3个字段就不会拷贝了。

与 lombok 集成

如果你的项目使用了 lombok,上面的代码可能没法正常工作。需要在 maven 对 lombok 也做下配置,在上面的 annotationProcessorPaths 加入如下配置即可。

<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</path>

上面只是结合本人的实际场景的一些例子,MapStruct 还有更多的功能,参见官方文档。

总结

会用之后我们可以学习一下它的原理了,这也是我们平时学习一个新的东西的习惯,别一下子就扎到原理,源码里头,这样会严重打击学习热情,要先跑起来先,看到成果后你会更有激情学习下去。

其实 MapStruct 的原理和 lombok 是一样的,都是在编译期间生成代码,而不会影响运行时。例如我们最常见的 @Data 注解,查看源文件你会发现 getter/setter 生成了,源文件的类不会有 @Data 注解。

java 代码编译和执行的整个过程包含三个主要机制:

  1. java源码编译机制

  2. 类加载机制

  3. 类执行机制。

其中 java 源码编译由3个过程组成:

  1. 分析和输入到符号表

  2. 注解处理

  3. 语义分析和生成class文件。

如下:

3b36095f18fde350ba19ce15f85bf931.png

其中 annotation processing 就是注解处理,jdk7 之前采用 APT技术,之后的版本使用了 JSR 269 API。

JSR 是什么?java Specification Requests,Java 规范提案,是指向 JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。jsr 269 是什么?在这里[1]

注解我们非常熟悉,其实java里的注解有两种,一种是运行时注解,如常用 @Resource, @Autowired,另一种是编译时注解,如 lombok 的 @Data。

编译时注解主要作用是在编译期间生成代码,这样就可以避免在运行时使用反射。编译时注解处理核心接口是 Processor,它有一个抽象实现类 AbstractProcessor 封装了许多功能,如果要实现继承它即可。

知道原理后,我们完全可以模仿 lombok 写一个简单的生成器。

关于性能,知道原理后其实你也知道根本不用担心mapstruct的性能问题了,可以参考这个:benchmark[2]

如果要说它的缺点,就是得为了这个简单的拷贝功能导这个包,如果你的程序只有很少的拷贝,那手动写一下也未尝不可,如果有大量拷贝需求,那就推荐使用了。

参考资料

[1]

jsr 269: https://jcp.org/en/jsr/detail?id=269

[2]

benchmark: https://github.com/arey/java-object-mapper-benchmark

来源:cnblogs.com/jtea/p/17592696.html

推荐全新学习项目

全新基于springboot+vue+vant的前后端分离的微商城项目,包括手机端微商城项目和后台管理系统,整个电商购物流程已经能流畅支持,涵盖商品浏览、搜索、商品评论、商品规格选择、加入购物车、立即购买、下单、订单支付、后台发货、退货等。功能强大,主流技术栈,非常值得学习。

项目包含2个版本:

  • 基于springboot的单体版本

  • 基于spring cloud alibaba的微服务版本

线上演示:https://www.markerhub.com/vueshop

271765e719016d9a448279ba710f340b.jpeg

从文档到视频、接口调试、学习看板等方面,让项目学习更加容易,内容更加沉淀。全套视频教程约44小时,共260期,讲解非常详细细腻。下面详细为大家介绍:

架构与业务

使用主流的技术架构,真正手把手教你从0到1如何搭建项目手脚架、项目架构分析、建表逻辑、业务分析、实现等。

单体版本:springboot 2.7、mybatis plus、rabbitmq、elasticsearch、redis

微服务版本:spring cloud alibaba 2021.0.5.0,nacos、seata、openFeign、sentinel

前端:vue 3.2、element plus、vant ui

26011082ffea884bf0fc81bbe3b09d9b.png

更多详情请查看:

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

手把手教学,从0开发前后端微商城项目,主流Java技术一网打尽!

org.dozer JAR 包(实现java bean mapper) dozer是一款优秀的java bean映射开源框架,完成深度转换Bean<->Bean的Mapper实现 立即下载

相关推荐

DozerBeanMapper映射工具

dozer是一种JavaBean的映射工具,类似于apache的BeanUtils。但是dozer更强大,它可以灵活的处理复杂类型之间的映射。不但可以进行简单的属性映射、复杂的类型映射、双向映射、递归映射等,并且可以通过XML配置文件进行灵活的配置。

Java常见bean mapper的性能及原理分析

在分层的代码架构中,层与层之间的对象避免不了要做很多转换、赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils、BeanCopier、Dozer、Orika等等,本文将讲述上面几个工具的使用、性能对比及原理分析。...

1533

mybatis自动生成bean、mapper工具

mybatis自动生成bean、mapper工具,不限于mybatis

beanmapper java_Java今日收获——BeanMapper

假如有一个Person类,一个Student类,如下:public class Person{public int id{get;set;}public string name{get;set;}public byte sex{get;set;}public int age{get;set;}}public class Student{public string name{get;set;}pub...

weixin_34917038的博客 1365

BeanMapper:比BeanUtils.copyProperties更快

BeanMapper 一个BeanUtils.copyProperties的小型快速替代。 起因 由于BeanUtils(Spring或Apache Commons)的copyProperties实现是利用反射实现的,它在大量调用时具有比较严重的性能问题。 BeanMapper通过javassist类库实现在运行时直接生成字节码来转换类,在首次生成后,后续运行可以提供近原生的读写速度。 限制条件 类必须具有符合小驼峰规范的JavaBean setter和getter。否则无法正常访问类。 类必须是公共可见的,否则会发生访问错误。 效率 在10000000次复制测试中 BeanUtils.copyProperties用时9809毫秒 StaticBeanMapper.copy用时100毫秒(首次编译)+ 3633毫秒(后续10000000次调用) BeanMapper.copy用时98毫

技能伤害继承英雄属性【War3地图编辑器】

风暴之锤跟随山丘之王的力量增长

ArYe 2308

BeanMapper

&lt;dependency&gt; &lt;groupId&gt;net.sf.dozer&lt;/groupId&gt; &lt;artifactId&gt;dozer&lt;/artifactId&gt; &lt;version&gt;5.5.1&lt;/version&gt; &lt;/dependency&gt; &lt;dependen

Gertent 1067

淘宝链接跳转微信全攻略:自建合规中转页实现私域引流

在跨平台运营中,链接跳转是连接不同生态、实现流量引导的关键技术。其核心原理是通过服务器端或前端逻辑,将用户从一个平台的链接安全引导至目标页面,同时规避平台间的访问限制。这项技术的核心价值在于打破数据孤岛,实现跨平台用户沉淀与精细化运营,广泛应用于电商引流、私域流量构建和跨平台营销等场景。本文聚焦于**淘宝短链**与**企业微信**之间的跳转难题,提供了一套基于自建中转页面的完整工程解决方案,手把手教你如何通过合规的技术手段,将淘宝流量高效、安全地引导至微信生态,并实现数据追踪与客户分流。

weixin_34185512的博客 414

是时候丢掉BeanUtils了

会用之后我们可以学习一下它的原理了,这也是我们平时学习一个新的东西的习惯,别一下子就扎到原理,源码里头,这样会严重打击学习热情,要先跑起来先,看到成果后你会更有激情学习下去。其实mapstruct的原理和lombok是一样的,都是在编译期间生成代码,而不会影响运行时。例如我们最常见的@Data注解,查看源文件你会发现getter/setter生成了,源文件的类不会有@Data注解。java代码编译和执行的整个过程包含三个主要机制:1.java源码编译机制 2.类加载机制 3.类执行机制。

qq_64170174的博客 172

基于单片机的智能体重秤设计与嵌入式程序实现

智能体重秤的基本功能是测量用户的体重,并将数据通过无线方式传输到终端设备(如智能手机、电脑或云平台)进行管理。体重测量:实时测量用户体重,并进行数据存储。BMI(体质指数)计算:基于体重和身高计算BMI值,帮助用户了解健康状况。多用户管理:通过蓝牙或Wi-Fi技术支持多用户体重数据记录。健康数据分析:结合历史数据分析体重变化趋势。语音播报:通过语音播报用户体重,便于老人或视力不便的人士使用。数据同步:通过App或云平台同步数据,进行长期健康监控。

2201_76125393的博客 1757

华为od 面试八股文_测试/测试开发_02_含答案

华为od 面试八股文_测试/测试开发_02_含答案

MISAYAONE的博客 2174

快丢掉BeanUtils吧,MapStruct才是YYDS

场景 通常,在后端项目开发中,因为有项目分层的设计,例如MVC架构,以及最近很火热的DDD架构中,会在不同的层级,有对应的DO,BO,VO,DTO等各种各样的POJO类,而我们在层级之间进行调用的数据传递时,通常要进行对象属性之间的映射。对于一些简单的对象,可能会直接使用get,set方法完成,或者使用BeanUtils工具类来完成属性之间的映射。 这些代码往往是枯燥、无聊的,并且在不同的业务处理类中可能需要重复地对两个对象进行互相转换。导致代码里充斥着大量的get,set转换,如果使用BeanUt..

python6_quanzhan的博客 404

杰理可视化SDK开发-杰理AC1123N芯片SDK开发笔记合集

该文档主要描述 AC112N开发相关的使用说明和注意事项,收集并持续更新开发调试过程中的相关疑问和解答,对SDK介绍文档做一个补充说明。写作人:李永奉,参考项目MD-026H; AC112N-SDK项目帮助文档在杰理开发者平台(JL DP)'帮助'处进入,如本文档有与后续帮助文档或工具注释说明冲突,以后者为标准。

李永奉的博客 188

SpringBoot 整合mapstruct|赶紧丢掉BeanUtils吧

本文整理了SpringBoot集成mapstruct的基本过程,解决了mapstruct和lombok一起使用,导致mapstruct失效的bug,另外也介绍了mapstruct的基本使用方法,后续会出更多集成指南,敬请期待!

3010

【存算一体架构实战】四、突破编译壁垒:手把手实现AI模型到存算硬件的自动映射

摘要:存算一体硬件的性能优势难以充分释放,核心瓶颈在于传统编译器无法高效适配"计算-存储融合"的新型架构。本文系统剖析传统编译流程在存算硬件上的三大痛点:数据布局不匹配导致90%带宽浪费、算子映射效率低使硬件利用率不足30%、缺乏专用优化_pass导致延迟居高不下。通过构建MLIR-PIM编译框架,实现从AI模型到存算硬件的全自动化映射,核心流程包括:TensorFlow模型→MLIR Dialect转换→存算优化_pass→硬件指令生成。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 243

银行统一监管报送系统

依据《银行业金融机构数据治理指引》,银行业金融机构应当建立适应监管数据报送工作需要的信息系统,实现流程控制的程序化,提高监管数据加工的自动化程度。银行业金融机构应当将监管数据纳入数据治理,建立工作机制和流程,确保监管数据报送工作有效组织开展,监管数据质量持续提升。银行通常要对接报送EAST5.0、1104、CRSS等近40个监管应用,涉及近1000张报表。表13-01是主要的监管报送应用、报送依据、报送规范、监管机构名称。

银行信息系统应用架构详解 4350

SpringBoot 整合系列导航

一、SpringBoot整合 mybatisPlus-入门篇 二、SpringBoot整合MybatisPlus 实现多租户 三、SpringBoot 整合MybatisPlus 实现字段自动注入

558

手把手教你学Simulink--控制策略与算法的DC-DC变换器领域仿真领域建模实例:基于Simulink实现一个双闭环控制Buck变换器的仿真建模

目录主要步骤1. 前期准备2. 构建Buck变换器模型3. 定义双闭环控制器4. 设置参考电压和反馈回路5. 实现双闭环控制器逻辑6. 仿真参数设置7. 运行仿真与结果分析示例整合代码片段1. Simulink模型2. 双闭环控制器参数调节3. 仿真结果分析总结双闭环控制是一种常见的控制策略,广泛应用于电力电子系统(如DC-DC变换器)中。它通过内外两个环路的协同工作,分别控制系统的动态性能和稳态性能。对于Buck变换器,通常采用电流内环和电压外环的双闭环控制结构。以下是一个手把手教程,帮助你基于Simul

MHD0815的博客 326

黑马点评项目 | Redis学习笔记「纯享版」10w字超详细总结(含资料)

这次笔记是本人二刷Redis实战篇黑马点评的8w字汇总笔记。如果需要资料,于本文末尾查找即可

2025优快云博客之星Top246,后端开发技术领域新星创作者。 3万+

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

上一篇: 字节一面:POST 为什么会发送两次请求?
下一篇: 一套万能的异步处理方案(典藏版)
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值