当前位置: 首页 > 科技观察

喜新厌旧是我的本性,今天破坏Mapstruct有什么问题!

时间:2023-03-16 10:57:55 科技观察

本文转载自微信公众号《味姐》,作者杨沟姐2号,转载请联系味姐公众号。这些年写了很多Java,感觉Java越来越丑了。尤其是玩了TypeScript之后,看到Java代码总觉得恶心。这种想法的转变从侧面证明我不是一个专一的人。因为我是狗。喜新厌旧是我的本性,即使我表现得很纯洁。按理说,牛X的人物是不需要关注语言水平这种低级问题的。但是,无论是什么语言,各种属性的拷贝都是工程中无法回避的问题。比如折腾人的VO、BO、DTO、DO等等。项目中百分之六十的代码都在做这些无用的转换和各种数据验证。这个比例是我瞎扯的,不过也差不多了。在Java中,对这些属性拷贝的处理方式有3种:直接硬编码,使用各种BeanUtils将代码硬编码,使用MapStruct之类的工具通过反射完成赋值,在编译期直接完成其实,有利有弊,有些东西虽然香,但在实际使用中,还是要好好想想。个个衣冠楚楚,个个都是外皮。这篇文章主要介绍了Mapstruct的使用,从这个美味的工具中闻到了它的味道。1.如何使用?和往常一样,你需要在pom.xml中添加依赖包。我们在这里使用1.4.1.Final版本。org.mapstructmapstruct${org.mapstruct.version}这还没完,还需要在pom的构建部分,添加一个插件。之所以这么复杂,是因为它的原理和lombok是一样的,也是通过APT在编译器中实现的。这意味着它的代码是在编译时完成的。不需要反射,所以效率和直接写get、set是一样的。org.apache.maven.pluginsmaven-compiler-plugin3.8.11.81.8org.mapstructmapstruct-processor${org.mapstruct.version}org.projectlomboklombok1.18.16org.projectlomboklombok-mapstruct-binding0.2.0这时候我们可以使用它提供的注解,以方便属性复制。@Mapper(nullValueCheckStrategy=NullValueCheckStrategy.ALWAYS)publicinterfaceTransform{TransformT=Mappers.getMapper(Transform.class);MemberfromMemberEntity(MemberEntityentity);MemberEntityfromMember(Membermember);}以上是示例代码。Mapper注解表明这是一个类型转换工具(objectmapper),它提供了很多策略供我们选择。直接写接口文件,不做一些额外的动作,mapstruct知道你要做什么!在传统编程中,如果Member的属性很多,我们需要手动完成这个过程,代码量会非常大。使用Mapperstruct后,这部分重复的工作就由工具帮我们完成了。看下面的图片!上图是在target下的generated-source目录下生成了代码,这是我们上面添加的插件的功劳;代码内容其实就是一些非空的判断和get、set等。字段名相同、类型相同的属性会被乱复制。如果您有很多bean属性,此工具会将您的代码从数百行更改为几行!2、mapstruct与其他方法相比有什么优势吗?为什么不直接使用BeanUtils呢?它们的效果是一样的啊,后者是各种类库提供的。主要原因是效率问题。BeanUtils是通过反射实现的,效率肯定很低;而mapstuct是基于APT实现的,没有性能损失。BeanUtils的属性副本在判断空值和不同类型的属性时有很多障碍,会失败;而mapstruct有非常灵活的策略和转换方式,可定制性比较强(后面会讲到)。3.复杂场景让我们来看一个复杂场景。如果您的bean中只有一些公共属性,那么使用mapstruct就如丝般顺滑。但总有一些异常需要更高级的处理。假设我想从Unit转换为ProductUnitEntity,但是有一个字段measureType类型不同,我们可以使用Mappings注解来完成这个转换。@Mappings({@Mapping(source="measureType.value",target="measureType")})ProductUnitEntityfromUnit(Unitv);编译后的代码如下所示。使用源和目标,您可以获得比BeanUtils更棒的行为。您甚至可以使用dateFormat进行一些日期转换。其实上面的measureType就是一个枚举类型。如何将普通类型转换为枚举类型?我们只需要提供一个默认的方法就可以了。mapstruct会判断参数类型和返回值,所以方法名可以是任何合法的值。defaultUnit.MeasureTypemeasureTypeIntegerToDomain(Integervalue){for(Unit.MeasureTypes:Unit.MeasureType.values()){if(s.getValue()==value){returns;}}returnnull;}mapstruct可以实现List与Is之间的转换可能吗?下面两行代码可以自动补充for循环,让你的代码更加简洁。ListfromSkuEntityList(Listv);ListfromSkuList(Listv);End那么问题来了。既然这么好的东西,为什么现在很多项目都不用mapstruct,连BeanUtils都不用,直接去那里手动get和set呢?一个原因是这些工具会大大减少代码量。mapstruct+hibernate-validate,一个管理转换,一个管理验证,简直是靠代码行数谈天下的公司的噩梦。性能会降低!另一个原因是使用这些工具不利于项目的重构。如果您将DTO中的a字段更改为b字段,mapstruct将为您忽略这些更改。你的项目代码不会提示错误,风险会直接带到runtime。在get和set的使用方式中,除了代码量变化较大外,唯一的风险就是开发者忘记给新添加的字段赋值。在这种情况下,机器完成的工作并不一定比人类更可靠。所以使用mapstruct有一个很大的前提:你的团队可以通过约定,不乱命名变量,不乱重构。只有这样,它的价值才能发挥出来。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。我的个人微信xjjdog0,欢迎加好友进一步交流。