设为首页 加入收藏

TOP

基于AbstractProcessor扩展MapStruct自动生成实体映射工具类(一)
2023-07-25 21:43:10 】 浏览:71
Tags:基于 AbstractProcessor 扩展 MapStruct 成实体

作者:京东物流 王北永 姚再毅

1 背景

日常开发过程中,尤其在 DDD 过程中,经常遇到 VO/MODEL/PO 等领域模型的相互转换。此时我们会一个字段一个字段进行 set|get 设置。要么使用工具类进行暴力的属性拷贝,在这个暴力属性拷贝过程中好的工具更能提高程序的运行效率,反之引起性能低下、隐藏细节设置 OOM 等极端情况出现。

2 现有技术

  1. 直接 set|get 方法:字段少时还好,当字段非常大时工作量巨大,重复操作,费时费力。
  1. 通过反射 + 内省的方式实现值映射实现:比如许多开源的 apache-common、spring、hutool 工具类都提供了此种实现工具。这种方法的缺点就是性能低、黑盒属性拷贝。不同工具类的处理又有区别:spring 的属性拷贝会忽略类型转换但不报错、hutool 会自动进行类型转、有些工具设置抛出异常等等。出现生产问题,定位比较困难。
  1. mapstruct:使用前需要手动定义转换器接口,根据接口类注解和方法注解自动生成实现类,属性转换逻辑清晰,但是不同的领域对象转换还需要单独写一层转换接口或者添加一个转换方法。

3 扩展设计

3.1 mapstruct 介绍

本扩展组件基于 mapstruct 进行扩展,简单介绍 mapstruct 实现原理。

mapstruct 是基于 JSR 269 实现的,JSR 269 是 JDK 引进的一种规范。有了它,能够实现在编译期处理注解,并且读取、修改和添加抽象语法树中的内容。JSR 269 使用 Annotation Processor 在编译期间处理注解,Annotation Processor 相当于编译器的一种插件,因此又称为插入式注解处理。

我们知道,java 的类加载机制是需要通过编译期运行期。如下图所示

mapstruct 正是在上面的编译期编译源码的过程中,通过修改语法树二次生成字节码,如下图所示

以上大概可以概括如下几个步骤:

1、生成抽象语法树。Java 编译器对 Java 源码进行编译,生成抽象语法树(Abstract Syntax Tree,AST)。

2、调用实现了 JSR 269 API 的程序。只要程序实现了 JSR 269 API,就会在编译期间调用实现的注解处理器。

3、修改抽象语法树。在实现 JSR 269 API 的程序中,可以修改抽象语法树,插入自己的实现逻辑。

4、生成字节码。修改完抽象语法树后,Java 编译器会生成修改后的抽象语法树对应的字节码文件件。

从 mapstruct 实现原理来看,我们发现 mapstruct 属性转换逻辑清晰,具备良好的扩展性,问题是需要单独写一层转换接口或者添加一个转换方法。能否将转换接口或者方法做到自动扩展呢?

3.2 改进方案

上面所说 mapstruct 方案,有个弊端。就是如果有新的领域模型转换,我们不得不手动写一层转换接口,如果出现 A/B 两个模型互转,一般需定义四个方法:

image.png

鉴于此,本方案通过将原 mapstruct 定义在转换接口类注解和转换方法的注解,通过映射,形成新包装注解。将此注解直接定义在模型的类或者字段上,然后根据模型上的自定义注解直接编译期生成转换接口,然后 mapstruct 根据自动生成的接口再次生成具体的转换实现类。

注意:自动生成的接口中类和方法的注解为原 mapstruct 的注解,所以 mapstruct 原有功能上没有丢失。详细调整如下图:

4 实现

4.1 技术依赖

  1. 编译期注解处理器 AbstractProcessor:Annotation Processor 相当于编译器的一种插件,因此又称为插入式注解处理。想要实现 JSR 269,主要有以下几个步骤。

1)继承 AbstractProcessor 类,并且重写 process 方法,在 process 方法中实现自己的注解处理逻辑。

2)在 META-INF/services 目录下创建 javax.annotation.processing.Processor 文件注册自己实现的

  1. 谷歌 AutoService:AutoService 是 Google 开源的用来方便生成符合 ServiceLoader 规范的开源库,使用非常的简单。只需要增加注解,便可自动生成规范约束文件。

知识点: 使用 AutoService 的好处是帮助我们不需要手动维护 Annotation Processor 所需要的 META-INF 文件目录和文件内容。它会自动帮我们生产,使用方法也很简单,只需要在自定义的 Annotation Processor 类上加上以下注解即可 @AutoService (Processor.class)

  1. mapstruct:帮助实现自定义插件自动生成的转换接口,并注入到 spring 容器中 (现有方案中已做说明)。
  2. javapoet:JavaPoet 是一个动态生成代码的开源库。帮助我们简单快速的生成 java 类文件,期主要特点如下:
  1. JavaPoet 是一款可以自动生成 Java 文件的第三方依赖。

  2. 简洁易懂的 API,上手快。

  3. 让繁杂、重复的 Java 文件,自动化生成,提高工作效率,简化流程。

4.2 实现步骤

  • 第一步:自动生成转换接口类所需的枚举,分别为类注解 AlpacaMap 和字段注解 AlpacaMapField。

1) AlpacaMap:定义在类上,属性 target 指定所转换目标模型;属性 uses 指定雷专转换过程中所依赖的外部对象。

2)AlpacaMapField:原始 mapstruct 所支持的所有注解做一次别名包装,使用 spring 提供的 AliasFor 注解。

知识点: @AliasFor 是 Spring 框架的一个注解,用于声明注解属性的别名。它有两种不同的应用场景:

注解内的别名

元数据的别名

两者主要的区别在于是否在同一个注解内。

  • 第二步:AlpacaMapMapperDescriptor 实现。此类主要功能是加载使用第一步定义枚举的所有模型类,然后将类的信息和类 Field 信息保存起来方便后面直接使用,片段逻辑如下:
AutoMapFieldDescriptor descriptor = new AutoMapFieldDescriptor();
            descriptor.target = fillString(alpacaMapField.target());
            descriptor.dateFormat = fillString(alpacaMapField.dateFormat());
            descriptor.numberFormat = fillString(alpacaMapField.numberFormat());
            descriptor.constant = fillString(alpacaMapField.constant());
            descriptor.expression = fillString(alpacaMapField.expression());
            descriptor.defaultExpression = fillString(alpacaMapField.defaultExpression());
            descriptor.ignore = alpacaMapField.ignore();
             ..........

  • 第三步:AlpacaMapMapperGenerator 类主要是通过 JavaPoet 生成对应的类信息、类注
首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇day12-实现Spring底层机制-02 下一篇读Java8函数式编程笔记03_高级集..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目