博客搬迁至https://blog.wangjiegulu.com

RSS订阅:https://blog.wangjiegulu.com/feed.xml

原文链接https://blog.wangjiegulu.com/2018/04/16/rapidooo-android-pojo-converter/

RapidOOO

Android POJO 转换器:根据 POJO 类编译时自动生成支持扩展互相绑定的领域对象。

English Version

Github: https://github.com/wangjiegulu/RapidOOO

为什么使用 RapidOOO?

我们在领域驱动设计中经常会在不同层级之间传递数据,例如 VO, PO, DO, DTO, BO等。Android 的开发中也经常会遇到这些情况,比如在 Android-CleanArchitectureUserModelDataMapper::transform, UserEntityDataMapper::transform 等。手工地进行拷贝转换的过程不但繁琐,而且错误的风险比较大,在新增、删除字段时也增加了维护的成本。Dozer 可以很好地解决这个问题,但是在 Android 上可能就不太适用了。

RapidOOO 可以做到:

  1. 在编译时针对指定的初始 POJO,可以自动生成 Java 类(比如 UserVO, UserBO 等),非反射。
  2. 可以在生成的 POJO 类中增加配置,添加新的字段(比如通过 User 中的 gender 在生成的 POJO(UserVO) 中扩展出一个 genderDesc 字段,并且与原来的 gender 类共存并进行双向绑定)
  3. 字段进行转换时可以通过指定 conversionMethodName, inverseConversionMethodName 等方法来进行特殊的转换,类似 Databinding 中的 @BindingMethod
  4. 链式的 POJO 生成,如从 User 生成 UserDO, 从 UserDO 生成 UserBO, 从 UserBO 生成 UserVO...
  5. 生成类中自动生成转换方法 UserBo.create(User user), userBo.toUser()
  6. 支持 POJO 继承.
  7. 支持对象池(比如 android.support.v4.util.Pools)。

怎么使用?

Gradle Check Newest Version

implementation "com.github.wangjiegulu:rapidooo-api:x.x.x"
annotationProcessor "com.github.wangjiegulu:rapidooo-compiler:x.x.x"

以下通过两个例子来说明:

User POJO:

public class User implements Serializable {
private Long userId;
private String username;
private String nickname;
private Integer age;
private Integer gender;
// getter setter
}

Pet POJO:

public class Pet {
private Long petId;
private String petName;
private boolean isCat;
private boolean delete;
private Boolean isDog;
private Boolean clear;
private User owner;
// getter settter
}

POJO 转换为 BO

创建 BOGenerator 类,配置以下注解:

@OOOs(suffix = BOGenerator.BO_SUFFIX, ooos = {
@OOO(id = "user_bo_id", from = User.class, suffix = BOGenerator.BO_SUFFIX_USER),
@OOO(from = Pet.class, conversion = {
@OOOConversion(
fieldName = "owner",
targetTypeId = "user_bo_id",
targetFieldName = "ownerUser",
replace = true
)
})
})
public class BOGenerator {
public static final String BO_SUFFIX = "BO";
public static final String BO_SUFFIX_USER = "_BO";
}

这里使用 @OOOs 注解来进行转换的配置,通过 @OOO 注解来显示地指定需要转换成哪些类。

@OOO(id = "user_bo_id", from = User.class, suffix = BOGenerator.BO_SUFFIX_USER)

以上表示一个类的转换:

  • id:表示本地转换的 id,可以为任意字符串(需唯一),默认不设置 id。
  • from:表示转换源,从 User 转换,必填。
  • suffix:表示生成的 POJO 类的名字后缀,这里是 _BO,所以生成的类名为 User_BO,默认使用 @OOOs 中的 suffix
@OOO(from = Pet.class, conversion = {
@OOOConversion(
fieldName = "owner",
targetTypeId = "user_bo_id",
targetFieldName = "ownerUser",
replace = true
)
})

以上也表示一个类的转换,但是可以通过 @OOOConversion 来新增一个字段:

  • fieldName:指定新的字段是从转换源 POJO 的哪个字段派生出来的
  • targetTypeId:用来指定新的字段的类型id,需要与其它的 @OOO 指定的 id 一致;也可以通过 targetType 来指定 Class 类型。
  • targetFieldName:指定新字段的名字,可以任意。
  • replace:新的字段是否替换原来的字段(fieldName),如果 false,则共存。

然后编译将会自动生成以下代码:

public class User_BO implements Serializable {
private Long userId;
private String username;
private String nickname;
private Integer age;
private Integer gender; // getter setter public static User_BO create(User user) {
User_BO user_BO = new User_BO();
user_BO.userId = user.getUserId();
user_BO.username = user.getUsername();
user_BO.nickname = user.getNickname();
user_BO.age = user.getAge();
user_BO.gender = user.getGender();
return user_BO;
} public User toUser() {
User user = new User();
user.setUserId(userId);
user.setUsername(username);
user.setNickname(nickname);
user.setAge(age);
user.setGender(gender);
return user;
} }
public class PetBO {
private Long petId;
private String petName;
private boolean isCat;
private boolean delete;
private Boolean isDog;
private Boolean clear;
private User_BO ownerUser; // getter setter public static PetBO create(Pet pet) {
PetBO petBO = new PetBO();
petBO.petId = pet.getPetId();
petBO.petName = pet.getPetName();
petBO.isCat = pet.isCat();
petBO.delete = pet.isDelete();
petBO.isDog = pet.getDog();
petBO.clear = pet.getClear();
petBO.ownerUser = User_BO.create(pet.getOwner());
return petBO;
} public Pet toPet() {
Pet pet = new Pet();
pet.setPetId(petId);
pet.setPetName(petName);
pet.setCat(isCat);
pet.setDelete(delete);
pet.setDog(isDog);
pet.setClear(clear);
pet.setOwner(ownerUser.toUser());
return pet;
}
}

BO 转换为 VO

如下新建 VOGenerator:

@OOOs(suffix = VOGenerator.VO_SUFFIX, fromSuffix = BOGenerator.BO_SUFFIX, ooosPackages = {
VOGenerator.PACKAGE_BO
}, ooos = {
@OOO(id = "user_vo_id", from = User_BO.class),
@OOO(from = User_BO.class/*, suffix = VOGenerator.VO_SUFFIX_USER*/,
fromSuffix = BOGenerator.BO_SUFFIX_USER,
conversion = {
@OOOConversion(
fieldName = "gender",
targetFieldName = "genderDesc",
targetType = String.class,
conversionMethodName = "conversionGender",
inverseConversionMethodName = "inverseConversionGender",
replace = false
),
@OOOConversion(
fieldName = "age",
targetFieldName = "ageDes",
targetType = String.class,
conversionMethodName = "conversionAge",
conversionMethodClass = AgeConversion.class,
replace = true
)
}
),
@OOO(from = PetBO.class,
conversion = {
@OOOConversion(
fieldName = "ownerUser",
targetFieldName = "ownerUser",
targetTypeId = "user_vo_id",
replace = true
)
}
)
})
public class VOGenerator {
public static final String VO_SUFFIX = "VO";
// public static final String VO_SUFFIX_USER = "_VO";
public static final String PACKAGE_BO = "com.wangjiegulu.rapidooo.depmodule.bll.xbo"; public static String conversionGender(Integer gender) {
if (null == gender) {
return "unknown";
}
switch (gender) {
case 0:
return "female";
case 1:
return "male";
default:
return "unknown";
}
} public static Integer inverseConversionGender(String genderDesc) {
switch (genderDesc) {
case "male":
return 1;
case "female":
return 0;
default:
return -1;
}
} }

还是通过 @OOOs 注解来指定要生成的类,但这里使用了 ooosPackages 来指定哪些包下面的类需要进行转换。

转换源为上面生成的:User_BOPetBO,生成的类名为 UserVOPetVO

UserVO 中扩展了两个字段:

@OOOConversion(
fieldName = "gender",
targetFieldName = "genderDesc",
targetType = String.class,
conversionMethodName = "conversionGender",
inverseConversionMethodName = "inverseConversionGender",
replace = false
)

从转换源的 gender 字段扩展出 genderDesc (用于在 View 上进行展示),类型为 String ,并且 replace = falsegendergenderDesc 共存):

  • conversionMethodName:指定转换方法,从 gender 转换为 genderDesc。默认为不设置。
  • inverseConversionMethodName:指定逆转换方法,从 genderDesc 转换为 gender。默认为不设置。

注意:conversionMethodNameinverseConversionMethodName 方法指定方法名字时,方法签名必须满足以下其一:

  • public static [转换目标类型] conversionXxx([转换源字段类型] param)
  • public static [转换目标类型] conversionXxx([转换源 class 类型] param1, [转换源字段类型] param2)

    如上面 gendergenderDesc 的转换:
  • public static String conversionGender(UserVO userVO, Integer gender)
  • public static Integer inverseConversionGender(String genderDesc)

通过设置以上两个方法,gendergenderDesc 两个字段会实现互相绑定,改变其中一个字段,另一个字段也会自动发生改变。

@OOOConversion(
fieldName = "age",
targetFieldName = "ageDes",
targetType = String.class,
conversionMethodName = "conversionAge",
conversionMethodClass = AgeConversion.class,
replace = true
)

UserVO 中还从转换源的 age 扩展了一个 ageDesc 属性(替换掉 age 字段,不共存),并指定了 conversionMethodName,但是转换方法并不在 VOGenerator 类中,而是在 AgeConversion 类中,所以需要显示地进行指定 conversionMethodClass

  • conversionMethodClass:转换方法所在的 Class,默认不设置则表示在当前的 Generator 类中。

另外 PetVO 扩展了一个 ownerUser

最后编译生成的代码如下:

public class UserVO implements Serializable {
private Long userId;
private String username;
private String nickname;
private String ageDes;
private Integer gender;
private String genderDesc; // getter setter public void setGender(Integer gender) {
this.gender = gender;
this.genderDesc = VOGenerator.conversionGender(gender);
} public void setGenderDesc(String genderDesc) {
this.genderDesc = genderDesc;
this.gender = VOGenerator.inverseConversionGender(genderDesc);
} public static UserVO create(User_BO user_BO) {
UserVO userVO = new UserVO();
userVO.userId = user_BO.getUserId();
userVO.username = user_BO.getUsername();
userVO.nickname = user_BO.getNickname();
userVO.ageDes = AgeConversion.conversionAge(user_BO.getAge());
userVO.gender = user_BO.getGender();
userVO.genderDesc = VOGenerator.conversionGender(user_BO.getGender());
return userVO;
} public User_BO toUser_BO() {
User_BO user_BO = new User_BO();
user_BO.setUserId(userId);
user_BO.setUsername(username);
user_BO.setNickname(nickname);
// Loss field:age, recommend to use `inverseConversionMethodName`.
user_BO.setGender(gender);
return user_BO;
}
}

注意:以上 User_BO,由于 age 属性是 replace,并且只设置了 conversionMethodName,并没有设置 inverseConversionMethodName,所以在 toUser_BO() 方法进行逆转换时会丢失 age 属性,所以推荐使用 inverseConversionMethodName

public class PetVO {
private Long petId;
private String petName;
private boolean isCat;
private boolean delete;
private Boolean isDog;
private Boolean clear;
private UserVO ownerUser; // getter setter public static PetVO create(PetBO petBO) {
PetVO petVO = new PetVO();
petVO.petId = petBO.getPetId();
petVO.petName = petBO.getPetName();
petVO.isCat = petBO.isCat();
petVO.delete = petBO.isDelete();
petVO.isDog = petBO.getDog();
petVO.clear = petBO.getClear();
petVO.ownerUser = UserVO.create(petBO.getOwnerUser());
return petVO;
} public PetBO toPetBO() {
PetBO petBO = new PetBO();
petBO.setPetId(petId);
petBO.setPetName(petName);
petBO.setCat(isCat);
petBO.setDelete(delete);
petBO.setDog(isDog);
petBO.setClear(clear);
petBO.setOwnerUser(ownerUser.toUser_BO());
return petBO;
}
}

对象池的使用

@OOOs(suffix = "BO", ooos = {
@OOO(from = Pet.class, pool = @OOOPool(
acquireMethod = "acquirePetBO",
releaseMethod = "releasePetBO"
))
})
public class ObjectPoolBOGenerator { private static Pools.Pool<PetBO> petBOPool = new Pools.SimplePool<>(3); public static PetBO acquirePetBO() {
PetBO petBO = petBOPool.acquire();
return null == petBO ? new PetBO() : petBO;
} public static void releasePetBO(PetBO petBO) {
petBOPool.release(petBO);
}
}

如上代码,通过添加 @OOOPool 注解,并指定 acquireMethodreleaseMethod 两个方法来创建和回收相应的对象即可(这里使用了 Android Support 包中的 Pools.SimplePool 来实现对象池)。

License

Copyright 2018 Wang Jie

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Android POJO 转换器 —> RapidOOO的更多相关文章

  1. Android开发手记(29) 基于Http的LaTeX数学公式转换器

    本文将讲解如何通过codecogs.com和Google.com提供的API接口来将LaTeX数学函数表达式转化为图片形式.具体思路如下: (1)通过EditText获取用户输入的LaTeX数学表达式 ...

  2. 完全掌握Android Data Binding

    转载:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html 来源 https://github.com/L ...

  3. Android Room使用详解

    使用Room将数据保存在本地数据库 Room提供了SQLite之上的一层抽象, 既允许流畅地访问数据库, 也充分利用了SQLite. 处理大量结构化数据的应用, 能从在本地持久化数据中极大受益. 最常 ...

  4. 这些小工具让你的Android 开发更高效

    在做Android 开发过程中,会遇到一些小的问题.尽管自己动手也能解决.可是有了一些小工具,解决这些问题就得心应手了,今天就为大家推荐一下Android 开发遇到的小工具,来让你的开发更高效. Vy ...

  5. &lt;Android 开源库&gt; GreenDAO 使用方法具体解释&lt;译文&gt;

    简单介绍 greenDAO是一个开源的Android ORM,使SQLite数据库的开发再次变得有趣. 它减轻了开发者处理底层的数据库需求,同一时候节省开发时间. SQLite是一个非常不错的关系型数 ...

  6. 精通 Android Data Binding

    转自:https://github.com/LyndonChin/MasteringAndroidDataBinding 官方虽然已经给出了教程 - Data Binding Guide (中文版 - ...

  7. 用Jersey为Android客户端开发Restful Web Service

    平时在做Android客户端的时候经常要与服务器之间通信,客户端通过服务端提供的接口获取数据,然后再展示在客户端的界面上,作为Android开发者,我们平时更多的是关注客户端的开发,而对服务端开发的关 ...

  8. 用Kotlin创建第一个Android项目(KAD 01)

    原文标题:Create your first Android project using Kotlin (KAD 01) 作者:Antonio Leiva 时间:Nov 21, 2016 原文链接:h ...

  9. Android Retrofit 2.0 使用-补充篇

    推荐阅读,猛戳: 1.Android MVP 实例 2.Android Retrofit 2.0使用 3.RxJava 4.RxBus 5.Android MVP+Retrofit+RxJava实践小 ...

随机推荐

  1. java的分数类

    概述 分数类在算法中非常重要, 而在java中不那么重要,java基础类库提供 了biginteger了,提供类似方式, package 组合数学; public class Fraction { p ...

  2. python flask框架 数据库的使用

    #coding:utf8 from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # ...

  3. Java程序员的情书

    java程序员的情书 我能抽象出整个世界但是我不能抽象出你因为你在我心中是那么的具体所以我的世界并不完整我可以重载甚至覆盖这个世界里的任何一种方法但是我却不能重载对你的思念也许命中注定了 你在我的世界 ...

  4. POJ-1679 The Unique MST---判断最小生成树是否唯一

    题目链接: https://vjudge.net/problem/POJ-1679 题目大意: 给定一个无向连通网,判断最小生成树是否唯一. 思路: (1)对图中的每条边,扫描其他边,如果存在相同权值 ...

  5. python的切片操作

    切片操作符是序列名后跟一个方括号,方括号中有一对可选的数字,并用冒号分割.注意这与你使用的索引操作符十分相似.记住数是可选的,而冒号是必须的. 切片操作符中的第一个数(冒号之前)表示切片开始的位置,第 ...

  6. Docker 基础技术之 Linux namespace 源码分析

    上篇我们从进程 clone 的角度,结合代码简单分析了 Linux 提供的 6 种 namespace,本篇从源码上进一步分析 Linux namespace,让你对 Docker namespace ...

  7. 三.SQL语句实例

    1.查询A表中存在而B表中不存在的数据 1.1 描述:表A中有一tel字段,表B中有一tel字段,两个字段存储的内容部分相同,现要查询A表tel字段中有而B表tel字段中没有的数据 1.2 有三个se ...

  8. 【温故而知新】HTTP 概述

    什么是 HTTP 官方解释是 "因特网的多媒体信使",通俗点说,就是个送信的.电话机出来之前,人与人(有一定距离)之间的沟通基本靠写信,然后由快递员送发.如果把 web 服务器和客 ...

  9. Ajax/XHR/HTTP/jQuery Ajax

    Ajax即通过XHR API使用js发起的异步网络请求,它不会导致页面刷新,因此是现代Web App的关键技术. HTTP协议是Web开发中最重要的网络协议,HTTP协议详细规定了请求和响应报文. 请 ...

  10. [AHOI 2012]树屋阶梯

    Description 暑假期间,小龙报名了一个模拟野外生存作战训练班来锻炼体魄,训练的第一个晚上,教官就给他们出了个难题.由于地上露营湿气重,必须选择在高处的树屋露营.小龙分配的树屋建立在一颗高度为 ...