基于grpc从零开始搭建一个准生产分布式应用(5) - MapStruct传输对象转换

news2024/11/24 1:54:52

分层设计中上下游间接口调用时,如果接口方法参数很多最好的方式是包装一个对象。但每层都有自己专用的对象,比如vo、bo、po等。在对象转换时需要写大量的getter和setter方法或是用deepCopy简化代码,但无论哪种都少不了大量的代码。

这里我们会引用一个叫mapStruct的三方框架,它可以实现负责的对象间deepCopy操作,笔者试用了一段时间后已经集成到生产环境中了,这个框架不夸张的说可以100%覆盖日常开发需要。原理也非常简单,通过注释方式在编译期间生成源码。

本章代码任务:我们会在 ​​https://blog.51cto.com/arch/5379866​​ 这篇文档上的代码再进行优化,以一个简单的示例展示mapStruct的便利性。然后再以附加章节的方式扩展其它高级使用功能。

一、集成

集成也非常简单需引入两个jar包和一个编译插件(这个插件主要是解决和lombok插件的冲突的)。在maven中添加依赖,这里笔者用的是版本是 1.4.2.Final 。

<!--mapStruct依赖,代码拷贝-->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <scope>compile</scope>
        </dependency>

添加插件解决与lombok冲突(原因是lombok也是同样的原理实现的),因为mapStruct会用到对象的setter和getter方法,所以同时使用时要保证lombok先执行编译,配置如下:

<properties>
        <java.version>1.8</java.version>
        <maven.compile.source>1.8</maven.compile.source>
        <maven.compile.target>1.8</maven.compile.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <lombok.version>1.18.20</lombok.version>
        <mapstruct.version>1.4.2.Final</mapstruct.version>

        <lombok.mapstruct.binding.version>0.1.0</lombok.mapstruct.binding.version>
    </properties>
<!--处理lombok和mapstruft 加载顺序的问题 防止在生成的实体没有属性-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${mapstruct.version}</version>
                        </path>

                        <!-- additional annotation processor required as of Lombok 1.18.16 -->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok-mapstruct-binding</artifactId>
                            <version>${lombok.mapstruct.binding.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>

二、简单使用

这里我们只需要修改【base-grpc-framework-core】模块。修改后的工程文件如下,文件名称带颜色的是本次修改的文件(git版本控制的默认色)。

2.1、创建请求包装对象

新创建包装对象,源码如下,源码中的的@Data和@JsonNaming是lombok的注解:

  • @Data:自动生成getter和setter方法;
  • @JsonNaming:会把java中的骆峰参数名转换为_连接的参数名,比如bizId会转成biz_id;
/**
 * @Title: com.zd.baseframework.core.controller.core.request.SystemLogRequest
 * @Description 查询请求参数包装对象
 * @author liudong
 * @date 2022/6/14 1:54 PM
 */
@Data
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class SystemLogQueryRequest {
    private String bizId;

    private Long userId;

    private String code;

}

2.2、创建对象转换类

下面就是mapstruct的用法,httpReqObjectToGrpcReqObject()方法表示把SystemLogRequest转换成CreateSysRecordRequest对象。有@Mapper注解的参数解释如下:

  • collectionMappingStrategy:集合处理方式;
  • nullValueCheckStrategy:转换前是否做为空判断
  • imports:手动工具类,需用expression表达式人工处理,后续会讲到;
  • uses:自动工具类,mapstruct会根据类型自动匹配相应方法;
/**
 * @Title: com.zd.baseframework.core.controller.core.translator.SystemLogTranslator
 * @Description http层与grpc层对象转换工具
 * @author liudong
 * @date 2022/6/15 8:52 PM
 */
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
        imports = {MapStructMapperUtil.class},
        uses = BaseMapStructMapper.class)
public interface SystemLogTranslator {

    SystemLogTranslator INSTANCE = Mappers.getMapper(SystemLogTranslator.class);

    /*查询请求参数转换*/
    ListSystemLogRequest toGrpcQueryReqObject(SystemLogQueryRequest var);

}

编译后在target/generated-sources/annotations/会找到如下源码:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-06-14T14:25:42+0800",
    comments = "version: 1.4.2.Final, compiler: javac, environment: Java 1.8.0_144 (Oracle Corporation)"
)
public class SystemLogTranslatorImpl implements SystemLogTranslator {

    @Override
    public ListSystemLogRequest toGrpcQueryReqObject(SystemLogQueryRequest var) {
        if ( var == null ) {
            return null;
        }

        Builder createSysRecordRequest = ListSystemLogRequest.newBuilder();

        if ( var.getBizId() != null ) {
            createSysRecordRequest.setBizId( BaseMapStructMapper.javaStringToStringValue( var.getBizId() ) );
        }
        if ( var.getUserId() != null ) {
            createSysRecordRequest.setUserId( BaseMapStructMapper.longToInt64( var.getUserId() ) );
        }
        if ( var.getTrackUid() != null ) {
            createSysRecordRequest.setTrackUid( BaseMapStructMapper.javaStringToStringValue( var.getTrackUid() ) );
        }
        if ( var.getCode() != null ) {
            createSysRecordRequest.setCode( BaseMapStructMapper.javaStringToStringValue( var.getCode() ) );
        }
        if ( var.getCustomCode() != null ) {
            createSysRecordRequest.setCustomCode( BaseMapStructMapper.javaStringToStringValue( var.getCustomCode() ) );
        }

        return createSysRecordRequest.build();
    }
}

2.3、实现包含类型请求和自动对象转换

修改 com.zd.baseframework.core.controller.core.SystemLogController.java,添加如下方法,源代码的第5行和行为新增的内容。

@PostMapping("/v1/list_systemlog")
    public BaseResponse listSystemLog(@RequestBody SystemLogQueryRequest systemLogRequest){
        try{
            //构建查询参数
            ListSystemLogRequest listSystemLogRequest  = SystemLogTranslator.INSTANCE.toGrpcQueryReqObject(systemLogRequest);
            ListSystemLogResponse listSystemLogResponse = iSysRecordServiceBlockingStub.listSystemLogByCondition(listSystemLogRequest);

            List<SystemLogVo> vos = null;
            return BaseResponse.success(vos);

        }catch(StatusRuntimeException e){
            log.error("ErrorCode : " + e.getStatus().getCode());
            return BaseResponse.error(e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
            return BaseResponse.error(e.getMessage());
        }
    }

最后启动测试下。

三、辅助工具类源码

3.1、BaseMapStructMapper

注意这个类需要用@Mapper注解一下,而手动的工具类则不需要。

@Mapper
public class BaseMapStructMapper {

    /*Boolean---Integer*/
    public static int boolToInteger(Boolean source) {
        if (source) {
            return 1;
        }
        return 0;
    }

    public static Boolean integerToBool(Integer source) {
        if (source != null && source.intValue() != 0) {
            return true;
        }
        return false;
    }

    /*java.lang.String --- com.google.protobuf.StringValue*/
    public static String stringValueToString(StringValue value) {
        return value.getValue();
    }

    public static StringValue javaStringToStringValue(String value) {
        return StringValue.of(value);
    }

    /*java.lang.Integer --- com.google.protobuf.Int32Value*/
    public static Int32Value integerToInt32(Integer value) {
        return Int32Value.of(value);
    }

    public static Integer int32ToInteger(Int32Value value) {
        return value.getValue();
    }

    /*java.lang.Long --- com.google.protobuf.Int64Value*/
    public static Int64Value longToInt64(Long value) {
        return Int64Value.of(value);
    }

    public static Long int64ToLong(Int64Value value) {
        return value.getValue();
    }

    /*java.lang.Float --- com.google.protobuf.FloatValue*/
    public static FloatValue floatToFloatValue(Float value) {
        return FloatValue.of(value);
    }

    public static Float floatValueToFloat(FloatValue value) {
        return value.getValue();
    }

    /*java.util.Date---com.google.protobuf.util.Timestamps*/
    public static Timestamp javaDateToGoogleTimestamp(Date date) {
        if (date == null) {
            return null;
        }
        return Timestamps.fromMillis(date.getTime());
    }

    public static Date googleTimestampToJavaDate(Timestamp timestamp) {
        if (timestamp == null  || timestamp.getSeconds()<=0) {
            return null;
        }
        return new Date(timestamp.getSeconds() * 1000);
    }


    public static Date localDateTimeToDate(LocalDateTime time) {
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = time.atZone(zoneId);
        Date date = Date.from(zdt.toInstant());
        return date;
    }

    /*java.util.LocalDateTime---com.google.protobuf.util.Timestamps*/
    public static Timestamp localDateTimetoGoogleTimestamp(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        return Timestamps.fromMillis(localDateTimeToDate(localDateTime).getTime());
//        return Timestamp.newBuilder().setSeconds(localDateTime.getSecond()).setNanos(localDateTime.getNano()).build();
//        return Timestamps.fromMillis(localDateTime.getSecond());
    }

    /*
    *
    * 2022.04.18 update by shaocx timestamp.getSeconds()<= 0 -> timestamp.getSeconds()< 0 ,解决1970-01-01 入参 返回null问题
    * */
    public static LocalDateTime googleTimestampToLocalDateTime(Timestamp timestamp) {
        if (timestamp == null || timestamp.getSeconds()< 0) {
            return null;
        }
        Date date = new Date(timestamp.getSeconds() * 1000);
        Instant instant = date.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
        return localDateTime;
    }
    /*java.util.LocalDate---com.google.protobuf.util.Timestamps*/
    public static Timestamp localDatetoGoogleTimestamp(LocalDate localDate) {
        if (localDate == null) {
            return null;
        }
        LocalDateTime localDateTime = localDate.atStartOfDay();
        return BaseMapStructMapper.localDateTimetoGoogleTimestamp(localDateTime);
    }

    public static LocalDate googleTimestampToLocalDate(Timestamp timestamp) {
        if (timestamp == null  || timestamp.getSeconds()<=0) {
            return null;
        }
        return BaseMapStructMapper.googleTimestampToLocalDateTime(timestamp).toLocalDate();
    }

    /*jsonString---com.google.protobuf.util.Value*/
    public static String valueToJson(Value value) {
        String json = null;
        try {
            json = JsonFormat.printer().includingDefaultValueFields().print(value);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        return json;
    }

    public static Value jsonToValue(String json) {
        Value.Builder builder = Value.newBuilder();
        try {
            JsonFormat.parser().merge(json, builder);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        return builder.build();
    }

    /*jsonString---com.google.protobuf.util.Struct*/
    public static String structToJson(Struct struct) {
        String json = null;
        try {
            json = JsonFormat.printer().includingDefaultValueFields().print(struct);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        return json;
    }

    public static Struct jsonToStruct(String json) {
        Struct.Builder builder = Struct.newBuilder();
        try {
            JsonFormat.parser().merge(json, builder);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        return builder.build();
    }

    //LocalDateTime -> String
    public static String localDateTimeToStr(LocalDateTime time, String format) {
        if (null == time) {
            return "";
        }
        DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
        String timeStr = df.format(time);
        return timeStr;
    }

    //String -> LocalDateTime
    public static LocalDateTime strToLocalDateTime(String timeStr, String format) {
        if (StringUtils.isEmpty(timeStr)) {
            return null;
        }
        DateTimeFormatter df = DateTimeFormatter.ofPattern(format);
        LocalDateTime ldt = LocalDateTime.parse(timeStr, df);
        return ldt;
    }


}

3.2、MapStructMapperUtil

public class MapStructMapperUtil {

    //localdatetime to date and time
    public static String localDateToDate (LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return dtf2.format(localDateTime);
    }

    public static String localDateToTime (LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("HH:mm:ss");
        return dtf2.format(localDateTime);
    }

    //localdatetime to date
    public static LocalDateTime dateToLocalDate (Date date) {
        if (date == null) {
            return null;
        }
        Instant it = date.toInstant();
        ZoneId zid = ZoneId.systemDefault();
        return LocalDateTime.ofInstant(it, zid);
    }

    public Date localDateTimeToDate(LocalDateTime localDateTime) {
        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDateTime.atZone(zone).toInstant();
        return  Date.from(instant);
    }
}

后面章节,笔者会对mapstruct展开讨论,如果能用好可以省去好多工作量,然虽然只是一个对象deepCopy工具,但足够复杂,还是需要下点精力研究和试验的。

笔者要使用这个插件时就踩过好多坑,所以学习没有捷径。

 

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

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

相关文章

linux+c+qt杂记

虚拟机网络选择&#xff1b; 桥接模式&#xff1a;设置window宿主机的IP/dns,把虚拟机设置为桥接即可。 切换到终端&#xff1a;我的是 ctrlaltFnF1&#xff1f; 问题解决&#xff1a; Ubuntu系统下载&#xff08;清华大学开源软件镜像站&#xff09;&#xff08;ubuntu-20.…

使用easyExcel导入导出Date类型的转换问题

起因&#xff1a;在业务需求上需要将Excel表中的日期导入&#xff0c;存储到数据库中&#xff0c;但是entity中的日期类型使用Date来接收&#xff0c;这样导致时间精确到秒。这时&#xff0c;即使使用DateTimeFormat("yyyy-MM-dd")也无法成功转换&#xff0c;会报如下…

第4篇:vscode+platformio搭建esp32 arduino开发环境

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 1.配置默认安装路径&#xff0c;安装到D盘。 打开环境变量&#xff0c;点击新建 输入变量名PLATFORMIO_CORE_DIR与路径&#xff1a;D:\PLATF…

javafx应用程序线程异常Exception in thread “JavaFx Application Thread“

前几天用javafx做小桌面应用程序出现了一个问题&#xff1a; 反复检查&#xff0c;最终确定报错的原因是UI刷新频率过快导致的 javafx提供了Platform.runLater用于解决该问题&#xff1a; Platform.runLater(new Runnable() {Overridepublic void run(){//用Platform.runLate…

linux上传代码到gitee

一、在gitee创建一个仓库 1.创建仓库 2.获取仓库地址 二、克隆仓库文件到linux中 1.查看Linux中是否安装git&#xff1a;git --version 如果没有&#xff0c;在root下使用指令 yum install -y git 安装。 2.使用 git clone 仓库地址&#xff0c;克隆仓库文件到linux中 三、文…

常见API架构介绍

两个服务间进行接口调用&#xff0c;通过调用API的形式进行交互&#xff0c;这是常见CS架构实现的模式&#xff0c;客户端通过调用API即可使用服务端提供的服务。相较于SPI这种模式&#xff0c;就是服务端只规定服务接口&#xff0c;但具体实现交由第三方或者自身来实现&#x…

如何利用SFTP如何实现更安全的远程文件传输 ——【内网穿透】

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《高效编程技巧》《cpolar》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 1. 安装openSSH1.1 安装SSH1.2 启动ssh 2. 安装cpolar2.1 配置termux服务 3. 远程SFTP连接配置3.1 查看生成的随机公…

《Zookeeper》源码分析(二十)之 Follower

目录 Follower创建Follower实例followLeader()findLeader()connectToLeader()registerWithLeader()syncWithLeader() FollowerZooKeeperServer Follower Leader选举结束后&#xff0c;成为Follower的服务器开始进行Follower的工作&#xff0c;过程如下&#xff1a; 与Leader…

Docker 轻量级可视化工具Portainer

1. 是什么 Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 2. 安装 2.1 官网 https://www.protainer.io/ https://docs.portainer.io/ce-2.9/start/install/server/docker/linux 2.2 …

【C# Programming】编程入门:数组、操作符、控制流

目录 一、数组 1、数组的声明 1.1 一维数组声明&#xff1a; 1.2 多维数组声明&#xff1a; 2、数组的实例化和赋值 2.1 数组在声明时通过在花括号中使用以逗号分隔的数据项对数组赋值&#xff0c; 例如&#xff1a; 2.2 如果在声明后赋值&#xff0c;则需…

RT-Thread 线程管理(学习一)

RT-Thread是支持多任务的操作系统&#xff0c;多任务通过多线程的方式实现。线程是任务的载体&#xff0c;也是RTT中最基本的调度单位。 线程执行时的运行环境称为上下文&#xff0c;具体来说就是各个变量和数据&#xff0c;包括所有的寄存器变量、堆栈、内存信息等。 特点 …

前端需要理解的 TypeScript 知识

Typescript是添加了类型系统的JavaScript&#xff0c;属于弱类型&#xff08;即允许隐式类型转换&#xff09;、静态类型语言&#xff0c;适应于任何规模的项目&#xff0c;支持 ES6&#xff0c;由微软开发并开源。Typescript增加的功能包括&#xff1a;类型批注和编译时类型检…

支持向量机SVM原理

目录 支持向量机SVM原理 SVM原理 从线性分类器说起 SVM的目标是最大化分类间隔 转化为对偶问题求解 支持向量机SVM原理 【数之道】支持向量机SVM是什么&#xff0c;八分钟直觉理解其本质_哔哩哔哩_bilibili SVM是由Vapnik等人于1995年提出的&#xff0c;在之后的20多年里…

win11录屏,自带录屏,无需下载第三方软件

1.按win键&#xff0c;输入“截图”&#xff0c;打开截图工具 2.选择录屏功能 3.新建录制 4.选择录制区域录制就可以了

nginx会话保持

ip_hash:通过IP保持会话 作用&#xff1a; nginx通过后端服务器地址将请求定向的转发到服务器上。 将客户端的IP地址通过哈希算法加密成一个数值 如果后端有多个服务器&#xff0c;第一次请求到服务器A&#xff0c; 并在务器登录成功&#xff0c;那么再登录B服务器就要重新…

java-红黑树

节点内部存储 红黑树规则 或者&#xff1a; 红黑树添加节点规则&#xff1a; 添加节点默认是红色的&#xff08;效率高&#xff09; 红黑树示例 注&#xff1a;红黑树增删改查性能都很好

【Vue框架】 router和route是什么关系

前言 之前没太注意&#xff0c;写着写着突然发现它们貌似不太一样&#xff0c;记录以下&#xff0c;回顾的看总结就好。 1、总结✨ route&#xff1a;当前激活路由的对象&#xff0c;用于访问和操作当前路由的信息 router&#xff1a;管理多个route的对象&#xff0c;整个应…

[HarekazeCTF2019]Easy Notes-代码审计

文章目录 [HarekazeCTF2019]Easy Notes-代码审计 [HarekazeCTF2019]Easy Notes-代码审计 登录之后有几个功能点&#xff0c;可以添加节点&#xff0c;然后使用Export导出 我们查看源码&#xff0c; 我们发现想要拿到flag的条件时$_SESSION[admin]true 如果我们能够控制sessio…

具备这7 个功能将让你的网站聊天机器人更加出色

对于您和您的客户来说&#xff0c;客户支持体验是最重要的。这里有 7个强大的功能可以完善和增强您的网站聊天机器人。 在网站上使用聊天机器人可能是您可以为客户支持团队做出的最佳决策之一。然而&#xff0c;在2023 年互联网发达的今天&#xff0c;您的网站聊天机器人可以实…

如何为开源项目做出贡献?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…