告别set和get,两大利器轻松搞定model转换

场景一:一般我们遇到需要新建model,常规做法就是创建一个类,老老实实的定义好model中的所有属性,一般来说属性对应的set方法和get方法都是少不了的,有时候还需要toString甚至equals和hashCode方法。

现在的IDE已经很成熟了,一般不会手写set和get方法,采用IDE自带的快捷方式自动生成居多。如下图所示

image

该方式相对手写方法来说,效率已经有了很大的提升,但还是有进一步的提升空间(下文会介绍)。而且该种方式维护性较差,当需要修改某个属性名称或者属性类型时,对应的set和get方法以及toString都需要调整。

场景二:大部分时候,我们都是基于当前流行的微服务架构和SSM(Spring+Spring MVC + Mybatis)框架进行开发,这时候我个人经常遇到一个问题就是model的转换问题。

不同层会有不同的model,比如DAO层的model,service层的model,对外API接口的model,还有更上层的controller层的model以及提供给前端的View model。

为了能够是接口正常调用,我们不得不处理这些model的转换,没有一个称手的工具,我们只能手写转换类,通过一个又一个的set和get方法来完成model的转换。

有时候,我们在测试接口的时候发现有些属性没有值,调试半天才发现,是因为其中一个属性忘记写set方法了。我们明知道这些工作并不需要太多的思考,但是却不得不小心翼翼的对待。

那么,是否有更加优雅的处理方式,请看下文介绍

Lombok

这是一个插件,能够很好的解决场景一中的难题

下载安装

我用的IDE是Intellij idea,可以在Preferences->Plugins中找到相应的插件安装并重启即可。

image

该插件的实现已经放在github上,有兴趣可以到https://github.com/mplushnikov/lombok-intellij-plugin查看

lombok使用

添加jar包依赖

在你需要的项目的pom文件中添加如下的依赖


<dependency>  <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId>  <version>1.16.18</version>  </dependency>

编写model

package com.jackie.wowjava.best.practice.java.orika.model;

import java.util.Date;

public class AuthorDTO {   private String name;

    private Date birthday;
}

添加需要的注解

Lombok可以通过注解的方式实现你需要添加的方法,比如你需要添加这些属性对应的set方法,那么只要在model类上添加注解@Setter即可,相应的,如果需要get方法添加@Getter

此外还有@ToString@NoArgsConstructor@AllArgsConstructor等方便使用的注解。

事实上,我们真的只需要这样添加注解的方式,就能够实现轻松调用set和get方法的需要。这样,以后如果model的属性有改变,我们只需要直接改相应的属性即可,不再需要做任何一点多余的操作。

将注解还原为具体方法

Lombok为我们提供可以将对应注解还原为对应方法的功能。

点击Refactor->Delombok选择想还原的方法即可

image

是不是很好用?

Orika

Orika是一个简单快速的model拷贝框架。

Orika使用

添加jar包依赖

在需要使用的项目的pom文件中添加如下依赖


<dependency>  <groupId>ma.glasnost.orika</groupId>  <artifactId>orika-core</artifactId>  <version>1.5.2</version>  </dependency>

创建两个需要转换的model

BookEntity


package com.jackie.wowjava.best.practice.java.orika.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Date;

@Setter @Getter @AllArgsConstructor @NoArgsConstructor public class BookEntity {   private String bookName;

    private String authorName;

    private Date authorBirthday;

    private String bookInformation;

    private Integer type;

} 

BookDTO


package com.jackie.wowjava.best.practice.java.orika.model;

import lombok.Getter;
import lombok.Setter;

@Setter @Getter public class BookDTO {   private String bookName;

    private AuthorDTO author;

    private BookType bookType;

    private BookInfo bookInfo;
}

备注:这里的AuthorDTO、BookType和BookInfo如下


package com.jackie.wowjava.best.practice.java.orika.model;

import lombok.*;

import java.util.Date;

@Setter @Getter @ToString @NoArgsConstructor @AllArgsConstructor public class AuthorDTO {   private String name;

    private Date birthday;
} 


package com.jackie.wowjava.best.practice.java.orika.model;

public enum BookType {
 NOVEL(1),
    ESSAY(2);

    private int value;

    BookType(int value) {
 this.value = value;
    }   public static BookType getBookType(int value) {
 BookType bookType = null;

        switch (value) {
 case 1:
                bookType = NOVEL;
                break;
            case 2:
                bookType = ESSAY;
                break;
            default:
                break;
        }
 return bookType;
    }   public int getValue() {
 return value;
    } } 


package com.jackie.wowjava.best.practice.java.orika.model;

import lombok.Getter;
import lombok.Setter;

@Setter @Getter public class BookInfo {   private String ISBN;

    private int page;
}

你没看错,就是BookDTO和BookEntity这两个model,需要相互转换,Orika可以帮你搞定,具体看下面是如何实现的。

model转换

我们看到两个model中包含了多种情况

  • 属性名称完全一样的,比如bookName

  • 一个属性对应一个对象的,BookDTO中的AuthorDTO对应BookEntity中的authorName以及authorBirthday

  • 枚举类型的,比如BookEntity的type和BookDTO的BookType

  • JSON类型的,比如BookEntity的bookInformation和BookDTO的bookInfo

1、属性名称完全一样的属性拷贝

新建测试类OrikaTest


package com.jackie.wowjava.best.practice.java.orika;

import com.alibaba.fastjson.JSON;
import com.jackie.wowjava.best.practice.java.orika.model.BookDTO;
import com.jackie.wowjava.best.practice.java.orika.model.BookEntity;
import com.jackie.wowjava.best.practice.java.orika.model.BookInfo;
import com.jackie.wowjava.best.practice.java.orika.model.BookType;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.converter.BidirectionalConverter;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.Type;

import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.util.Date;

public class OrikaTest {   private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    public static void main(String[] args) {   BookEntity bookEntity = new BookEntity(
 "银河系漫游指南",
                "道格拉斯·亚当斯",
                Date.from(LocalDate.of(1952, Month.MARCH, 11).atStartOfDay(ZoneId.systemDefault()).toInstant()),
                "{\"ISBN\": \"9787532754687\", \n \"page\": 279\n }",
                1);

        BookDTO bookDTO = mapperFactory.getMapperFacade().map(bookEntity, BookDTO.class);

        System.out.println(JSON.toJSONString(bookDTO));
    } } 

运行结果如下


{"bookName":"银河系漫游指南"}

没错,只有名称相同的属性被转换了。

从代码来看,我们不需要做任何特殊化处理就能做到这一点,因为Orika默认就是按照名称相同就拷贝进行处理的。

2、一个属性对应一个对象的属性拷贝

这里我们是想BookDTO中的AuthorDTO对应BookEntity中的authorName以及authorBirthday。

我们只需要添加如下代码即可实现。


mapperFactory.classMap(BookDTO.class, BookEntity.class)
 .field("author.name", "authorName")
 .field("author.birthday", "authorBirthday")
 .byDefault()
 .register();

运行结果如下


{"author":{"birthday":-562060800000,"name":"道格拉斯·亚当斯"},"bookName":"银河系漫游指南"}

3、枚举类型的属性拷贝

这时候我们需要新建一个转换器并注册到mapperFactory上。

注册转换器代码如下


mapperFactory.getConverterFactory().registerConverter("bookTypeConvert", new BidirectionalConverter<BookType, Integer>() {
 @Override
 public Integer convertTo(BookType bookType, Type<Integer> type, MappingContext mappingContext) {
 return bookType.getValue();
    }   @Override
 public BookType convertFrom(Integer value, Type<BookType> type, MappingContext mappingContext) {
 return BookType.getBookType(value);
    } });

注册转换器代码如下


mapperFactory.classMap(BookDTO.class, BookEntity.class)
 .field("author.name", "authorName")
 .field("author.birthday", "authorBirthday")
 .fieldMap("bookType", "type").converter("bookTypeConvert").add()
 .byDefault()
 .register();

运行结果如下:


{"author":{"birthday":-562060800000,"name":"道格拉斯·亚当斯"},"bookName":"银河系漫游指南","bookType":"NOVEL"}

4、JSON类型的属性转换

该属性的转换原理和上述的枚举类型转换相同,需要创建转换器并注册使用


mapperFactory.getConverterFactory().registerConverter("bookInfoConvert", new BidirectionalConverter<BookInfo, String>() {
 @Override
 public String convertTo(BookInfo bookInfo, Type<String> type, MappingContext mappingContext) {
 return JSON.toJSONString(bookInfo);
    }   @Override
 public BookInfo convertFrom(String s, Type<BookInfo> type, MappingContext mappingContext) {
 return JSON.parseObject(s, BookInfo.class);
    } });



mapperFactory.classMap(BookDTO.class, BookEntity.class)
 .field("author.name", "authorName")
 .field("author.birthday", "authorBirthday")
 .fieldMap("bookType", "type").converter("bookTypeConvert").add()
 .fieldMap("bookInfo", "bookInformation").converter("bookInfoConvert").add()
 .byDefault()
 .register();

运行结果如下


{"author":{"birthday":-562060800000,"name":"道格拉斯·亚当斯"},"bookInfo":{"iSBN":"9787532754687","page":279},"bookName":"银河系漫游指南","bookType":"NOVEL"}

是不是很神奇?

总结

相信有了这两大神奇Lombok和Orika,基本上实现了和set和get的真正告别。

项目代码地址:https://github.com/DMinerJackie/rome

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 变,这是个很日常的话题!变好变坏,变丑变美,变穷变富……这个世界每一秒钟都在发生着变化,大大小小,多多少...
    祸福相依_阅读 303评论 0 0
  • 第011 校园的花儿开了 文/何文斌 没有清华园荷叶的清韵 没有末名湖畔春桃的灵动 火红的石榴 在逝去的春光中嫣红...
    何文斌简书阅读 347评论 0 1
  • 文/小番茄Zoe 废话说在前头,我这个人真的不爱刷剧。但!我最近真的是太(重音)无聊了!!!心血来潮,点开了当时为...
    菜菜要开心呀阅读 10,785评论 0 0