我们在领域驱动设计中经常会在不同层级之间传递数据,例如 VO, PO, DO, DTO, BO等。Android 的开发中也经常会遇到这些情况,比如在 Android-CleanArchitecture 的 UserModelDataMapper::transform, UserEntityDataMapper::transform 等。手工地进行拷贝转换的过程不但繁琐,而且错误的风险比较大,在新增、删除字段时也增加了维护的成本。Dozer 可以很好地解决这个问题,但是在 Android 上可能就不太适用了。
RapidOOO 根据 POJO 类编译时灵活地自动生成支持扩展互相绑定的领域对象。
在 build.gradle 中配置依赖 RapidOOO:
implementation "com.github.wangjiegulu:rapidooo-api:x.x.x"
annotationProcessor "com.github.wangjiegulu:rapidooo-compiler:x.x.x"注意:
x.x.x为版本号,最新版本点这里检查。
存在 Person 类如下:
public class Person {
private String name;
private String country;
// getter / setter
}要自动生成 PersonBO 类,则创建 Generator 类并进行如下配置:
@OOOs(suffix = "BO",
ooos = {
@OOO(from = Person.class),
...
}
)
public class DemoBOGenerator {}注意:
Generator类的类名可以任意。
构建之后,将会生成如下 PersonBO 与 Person 一样的类:
public class PersonBO {
private String name;
private String country;
public PersonBO() {
}
public void fromPerson(Person person) {
this.name = person.getName();
this.country = person.getCountry();
}
public static PersonBO create(Person person) {
PersonBO personBO = new PersonBO();
personBO.fromPerson(person);
return personBO;
}
public void toPerson(Person person) {
person.setName(name);
person.setCountry(country);
}
public Person toPerson() {
Person person = new Person();
toPerson(person);
return person;
}
}假设:通过
UserBO类自动生成UserVO类,则:
UserBO称为:源类UserVO称为:目标类
首先要创建 Generator 类,然后在该类上增加注解配置,该类表示 一次 自动生成转换,一次 自动生成转换中可以生成 多个 领域类。
重要的注解配置如下:
@OOOs用于指定该次自动生成转换的所有配置。
- fromSuffix:
目标类后缀,比如从UserBO自动生成UserVO,则fromSuffix为UserBO的后缀:BO。如果源类没有后缀,则不填。 - suffix:
目标类后缀,比如从UserBO自动生成UserVO,则suffix为UserVO的后缀:VO。 - ooosPackages:
源类所在的包数组,RapidOOO将会把这些包中的类都会进行自动生成。 - ooos:
@OOO注解数组,目标类的自定义配置(后面会讲到)。
@OOOs用于指定目标类的自定义配置。
- id:该
目标类的id,id 必须以#号开头。 - fromSuffix:同
@OOOs中的fromSuffix,此优先级高。 - suffix:同
@OOOs中的suffix,此优先级高。 - from:指定
源类的Class。 - includes:指定该
目标类包含哪些字段(Field),不能与excludes同时使用。 - excludes:指定该
目标类不包含哪些字段(Field),不能与includes同时使用。 - targetSupperType:指定该
目标类的父类的 Class。 - targetSupperTypeId:指定该
目标类的父类的 id 表达式,id 必须以#号开头。 - pool:针对该
目标类类的对象池相关配置(后面会讲到)。 - parcelable:配置该
目标类类是否实现Parcelable接口(前提是源类是实现了Parcelable接口)。 - conversions:
@OOOConversion数组,表示该目标类中需要进行特殊转换的字段配置(后面会讲到)。
@OOOs用于指定目标类中某个字段的自定义配置。
- targetFieldName:该字段的名字。
- targetFieldType:指定该字段的 Class。
- targetFieldTypeId:指定该字段的 id 表达式。
- attachFieldName:表示字段模式(后面会讲到)为
attach,指定与源类的哪个字段 attach。 - bindMethodClass:表示字段模式(后面会讲到)为
bind,指定绑定转换的方法所在的类(后面会讲到)。 - bindMethodName:表示字段模式(后面会讲到)为
bind,指定绑定转换的方法(后面会讲到)。 - inverseBindMethodName:表示字段模式(后面会讲到)为
bind,指定绑定逆转换的方法(后面会讲到)。 - conversionMethodClass:表示字段模式(后面会讲到)为
conversion,指定转换的方法所在的类(后面会讲到)。 - conversionMethodName:表示字段模式(后面会讲到)为
conversion,指定转换的方法(后面会讲到)。 - inverseConversionMethodName:表示字段模式(后面会讲到)为
conversion,指定逆转换的方法(后面会讲到)。 - parcelable:是否可以实现
parcelable接口。 - controlDelegate:配置该字段的控制委托(后面会讲到)。
通过注解配置,在构建后会自动生成如下两个方法:
- create() static 方法:
源类转为目标类,如:UserVO userVO = UserVO.create(userBO) - toXxx() 成员方法:
目标类转为源类,如:userVO.toUserBO()。
通过字符串来表示类型(包括
id)。
- ≈ 的
id必须以#号开头。 - 在
targetFieldTypeId等字段中可以使用 id 表达式来表达复杂类型(后面会提到)。
通过配置支持链式的自动生成:从 Person 生成 PersonBO,从 PersonBO 生成 PersonVO...
// 生成 PersonBO
@OOOs(suffix = "BO", ooos = {@OOO(from = Person.class)})
public class DemoBOGenerator {}
// 生成 PersonVO
@OOOs(suffix = "VO", fromSuffix = "BO", ooos = {@OOO(from = PersonBO.class)})
public class DemoVOGenerator {}如果 Abbey 类继承了 Person 类,如下:
public class Abbey extends Person {
// ...
}可以通过以下配置来生成 AbbeyBO 类,并且继承同样是生成的 PersonBO:
@OOOs(suffix = "BO",
ooos = {
@OOO(id = "#id_bo_person", from = Person.class),
@OOO(from = Abbey.class, targetSupperTypeId = "#id_bo_person")
}
)
public class ExtBOGenerator {
}- 先配置
Person类,然后设置它的id(任意字符,但是必须是#开头),如#id_bo_person(此配置会生成PersonBO类) - 然后配置
Abbey类,设置targetSupperTypeId为#id_bo_person(为Abbey类设置父类)
最终生成如下:
public class AbbeyBO extends PersonBO {
// ...
}默认情况下,源类 实现的接口不会在生成的 目标类 中自动也去实现。
但是以下两个接口除外:
如果 源类 实现了 Serializable 接口,在生成的 目标类 中也会自动默认实现该接口。如下:
// 源类
public class User implements Serializable{
// ...
}
// 配置 generator
@OOOs(suffix = "BO", ooos = {@OOO(from = User.class)})
public class BOGenerator {}
// 目标类(RapidOOO 自动生成)
public class UserBO implements Serializable{
// ...
}如果 源类 实现了 Parcelable 接口,在生成的 目标类 中也会自动默认实现该接口(包括自动生成 构造方法、CREATOR、writeToParcel()、describeContents 等)。如下:
// 源类
public class Message implements Parcelable {
public static final Creator<Message> CREATOR = new Creator<Message>() { ... }
protected Message(Parcel in) { ... }
@Override
public int describeContents() { ... }
@Override
public void writeToParcel(Parcel dest, int flags){ ... }
}
// 配置 generator
@OOOs(suffix = "BO", ooos = {@OOO(from = Message.class)})
public class BOGenerator {}
// 目标类(RapidOOO 自动生成)
public class MessageBO implements Parcelable {
public static final Creator<MessageBO> CREATOR = new Creator<MessageBO>() { ... }
protected MessageBO(Parcel in) { ... }
@Override
public int describeContents() { ... }
@Override
public void writeToParcel(Parcel dest, int flags){ ... }
}但是你可以通过 @OOO 注解中的 parcelable 设置为 false 来禁用所有字段不进行序列化。
如果你只需要生成类中的某个字段不进行 parcelable 序列化,可以通过 @OOOConversion 注解中的 parcelable 设置为 false 来禁用它。
可以通过 @OOO 中的 include 和 excludes 两个属性来指定 源类 中的哪些字段 需要/不需要 生成在 目标类 中。如下:
@OOOs(suffix = "BO", ooos = {
@OOO(from = Message.class, excludes = {"chat", "text"})
})
public class BOGenerator {}以上,在生成的 MessageBO 中不会包含来自 Message 的 chat 和 text 两个字段。
includes 的使用方式也是一样。
注意:
includes和excludes如果都没有设置,则默认为源类中的所有字段 都会 生成在目标类中。includes和excludes不能同时使用,否则编译会报错。
可以在 @OOO 注解中通过 @OOOConversion 注解来对每个 目标类 中的每个字段通过不同的 字段模式(Field Mode) 来进行配置。
具体的配置方式如下:
在 源类 生成 目标类 时,可以在 目标类 中增加一些 源类 中不存在的字段,如下:
// `源类`
public class Message {
private Integer id;
private String content;
// getter / setter
}
// 配置 generator
@OOOs(suffix = "BO",
ooos = {
@OOO(from = Message.class,
conversions = {
@OOOConversion(
targetFieldName = "read",
targetFieldType = boolean.class
)
}
}
)
public class DemoBOGenerator {}
// `目标类`
public class Message {
private Integer id;
private String content;
private boolean read;
// getter / setter
}以上通过 @OOOConversion 注解增加了一个字段名为 read、类型为 boolean 的字段,并生成了 getter/setter 方法。
注意:这个
read字段与Message类中的任何字段都没有任何关系。
如果我需要在 目标类 增加一个字段,并且把这个字段与 源类 中的相似类型的字段 连接 起来。
或者说用新的字段替换掉 源类 的相似类型的字段,比如:
// `源类`
public class Message {
private Integer id;
private String content;
// getter / setter
}
// 配置 generator
@OOOs(suffix = "BO",
ooos = {
@OOO(from = Message.class,
// 排除掉源类中的 content 字段
excludes = {"content"},
conversions = {
@OOOConversion(
targetFieldName = "text",
targetFieldType = String.class,
// 使用 attach 模式把 `text` 字段关联到 `content` 字段
attachFieldName = "content"
)
}
}
)
public class DemoBOGenerator {}
// `目标类`
public class Message {
private Integer id;
// 源类中的 content 被替换成了目标中的 text 字段
private String text;
// getter / setter
}如上注释:首先通过 excludes 排除掉要被关联的 content 字段,然后再用 @OOOConversion 注解配置新的 text 字段,并通过 attachFieldName 连接到 源类 的 content 字段。
最终的结果是,目标类 中的 content 被替换成了 text。
那怎么体现了 连接 这个特点呢?
如果是 连接(attach) 模式,则 目标类 中还会在某些方法中生成以下代码来确保新增字段与原来的字段是处于连接状态的:
public class MessageBO {
public static MessageBO create(Message message) {
MessageBO messageBO = new MessageBO();
messageBO.fromMessage(message);
return messageBO;
}
public void fromMessage(Message message) {
// ...
// 在 Message 类型转 MessageBO 类型时,这里 MessageBO 的 text 被赋值为 Message 的 content
this.text = message.getContent();
// ...
}
public Message toMessage() {
Message message = new Message();
toMessage(message);
return message;
}
public void toMessage(Message message) {
// ...
// 在 MessageBO 类型转 Message 类型时,这里 Message 的 content 被赋值为 MessageBO 的 text
message.setContent(this.text);
// ...
}
}由此,Message 中的 content 与 MessageBO 中的 text 字段达成了连接,于是如下单元测试通过:
final String CONTENT = "message content";
// 构建一个 Message 对象
Message message = new Message();
message.setContent(CONTENT);
// 从 Message 对象转换成一个新的 MessageBO
MessageBO messageBO = MessageBO.create(message);
Assert.assertEquals(CONTENT, messageBO.getText());
// 通过 MessageBO 对象转换成一个新的 Message 对象
Message newMessage = messageBO.toMessage();
Assert.assertEquals(CONTENT, newMessage.getContent());以上的例子是 源类 Message 的 content 与 目标类 MessageBO 的 text 之间的连接,两者都是同一种类型: String。
除了支持同一种类型的连接之外,其实还支持以下的类型的连接:
| 序号 | 源类字段 | 目标类字段 | 举例(源 -> 目标) |
|---|---|---|---|
| 1) | 某种任意类型 | 某种相同的任意类型 | FooBar -> FooBar |
| 2) | 某种 Map 类型 |
另一种相同或不同的 Map 类型(泛型相同) |
HashMap -> LinkedHashMap |
| 3) | 某种 List 类型 |
另一种相同或不同的 List 类型(泛型相同) |
ArrayList -> LinkedList |
| 4) | 某种数组类型 | 数组类型 | Foo[] -> Foo[] |
| 5) | 某种其它源类型 | 某种其它目标类型 | Chat -> ChatBO |
| 6) | 序号2)``3)``4)与5)的组合 |
序号2)``3)``4)与5)的组合 |
Map<String, Chat> -> HashMap<String, ChatBO> 或 ArrayList<Chat> -> List<ChatBO> |
使用 attach 这种 产生连接 以自动在 源 和 目标 字段中转换的方式,在有些实际场景可能无法满足需求,所以 RapidOOO 还提供了更为灵活的 字段转换 模式,你可以通过 @OOOConversion 注解的 conversionMethodName 和 inverseConversionMethodName 来指定转换 / 逆转换的方法,如下例子:
// 源类
public class User{
private String username;
// 1 / 0 / -1
private Integer gender;
}
// 配置 generator
@OOOs(suffix = "VO",
ooos = {
@OOO(from = User.class,
// 排除掉源类中的 gender 字段
excludes = {"gender"},
conversions = {
@OOOConversion(
targetFieldName = "genderDesc",
targetFieldType = String.class,
// 使用 conversion 模式通过 `conversionGenderDesc` 把 `gender` 字段转换为 `genderDesc` 字段
conversionMethodName = "conversionGenderDesc",
// 使用 conversion 模式通过 `inverseConversionGender` 把 `genderDesc` 字段转换为 `gender` 字段
inverseConversionMethodName = "inverseConversionGender"
)
}
}
)
public class VOGenerator {
public static String conversionGenderDesc(Integer gender, String username) {
if (null == gender) {
return "unknown";
}
switch (gender) {
case 0:
return "female";
case 1:
return "male";
default:
return "unknown";
}
}
public static void inverseConversionGender(String genderDesc, UserVO self, User_BO other) {
int gender;
switch (genderDesc) {
case "male":
gender = 1;
break;
case "female":
gender = 0;
break;
default:
gender = -1;
break;
}
other.setGender(gender);
}
}
// 目标类(RapidOOO 自动生成)
public class UserVO{
private String username;
// male / female / unknown
private String genderDesc;
// ...
public void fromUser(User user) {
this.genderDesc = VOGenerator.conversionGenderDesc(user_BO.getGender(), this.username);
}
// ...
public void toUser(User user) {
VOGenerator.inverseConversionGender(this.genderDesc, this, user);
}
}先看 gender 转换为 genderDesc 的配置:
通过指定 @OOOConversion 注解中的 conversionMethodName 方法,此方法可以编写在本次转换的 Generator 类中,也就是上面的 VOGenerator,方法名为 conversionGenderDesc,并且该方法必须满足以下几个条件:
- 方法必须是
public static - 方法的返回值必须为
targetFieldType指定的类型,方法返回值即为转换结果。 - 参数表示你本次转换需要使用到的对象,可以任意多个,但是必须满足以下几个条件之一:
- 参数名为对应的
目标类中的字段名,并且该参数类型与对应的字段要一致,生成的代码在调用时会把对应字段转入到该方法。 - 参数名为
self,类型为目标类,生成的代码在调用时会把当前的目标类对象传入到该方法。 - 参数名为
other,类型为源类,生成的代码在调用时会把当前的源类对象传入到该方法。
- 参数名为对应的
genderDesc 转换为 gender 的配置:
通过指定 @OOOConversion 注解中的 inverseConversionMethodName 方法,此方法可以编写在本次转换的 Generator 类中,也就是上面的 VOGenerator,方法名为 inverseConversionGender,并且该方法必须满足以下几个条件:
- 方法必须是
public static - 方法的返回值必须为
void - 参数表示你本次转换需要使用到的对象,可以任意多个,但是必须满足以下几个条件之一:
- 参数名为对应的
目标类中的字段名,并且该参数类型与对应的字段要一致,生成的代码在调用时会把对应字段转入到该方法。 - 参数名为
self,类型为目标类,生成的代码在调用时会把当前的目标类对象传入到该方法。 - 参数名为
other,类型为源类,生成的代码在调用时会把当前的源类对象传入到该方法。
- 参数名为对应的
转换结果通过调用 other 方法参数去进行设置(setter)。
注意:
conversionMethodName和inverseConversionMethodName也可以只设置其中一个。一般情况下使用 字段转换 模式的同时应该把对应的所有相关的字段(
conversionMethodName和inverseConversionMethodName中参数对应的所有字段)会exclude掉。
conversionMethodName和inverseConversionMethodName的对应的方法默认是在Generator中编写,但是你也可以通过指定@OOOConversion注解的conversionMethodClass来指定转换方法所在的类。
如果需要把转换相应的 源类 字段继续保留,则可以使用 字段绑定(Bind) 模式。使用方式与 字段转换(Conversion Mode) 模式比较类似,同样提供了两个方法 bindMethodName 和 inverseBindMethodName 来指定绑定 / 逆绑定的方法,如下例子:
// 源类
public class User{
private Integer age;
}
// 配置 generator
@OOOs(suffix = "VO",
ooos = {
@OOO(from = User.class,
// 不排除掉源类中的 age 字段
conversions = {
@OOOConversion(
targetFieldName = "ageDesc",
targetFieldType = String.class,
// 指定绑定方法所在的类 AgeBinder
bindMethodClass = AgeBinder.class,
// 使用 bind 模式通过 `bindAgeDesc` 把 `age` 字段转换为 `ageDesc` 字段
bindMethodName = "bindAgeDesc",
// 使用 bind 模式通过 `inverseBindAgeDesc` 把 `ageDesc` 字段转换为 `age` 字段
inverseBindMethodName = "inverseBindAgeDesc"
)
}
}
)
public class VOGenerator {
}
// AgeBinder.java
public class AgeBinder {
public static String bindAgeDesc(Integer age) {
if (null == age || age < 0) {
return "unknown";
}
return age + " years old";
}
public static void inverseBindAgeDesc(String ageDesc, UserVO self) {
Integer age;
if (null == ageDesc) {
age = -1;
}else{
age = Integer.valueOf(ageDesc.split(" ")[0]);
}
self.setAge(age);
}
}
// 目标类(RapidOOO 自动生成)
public class UserVO{
private Integer age;
private String ageDesc;
public void fromUser(User user) {
this.age = user_BO.getAge();
// User 转换为 UserVO 时,通过 bindAge 方法来初始化 ageDesc
this.ageDesc = AgeBinder.bindAge(this.age);
}
public void setAge(Integer age) {
this.age = age;
// 当 age 改变时,通过 bind 方法自动实时更新 ageDesc 的值
this.ageDesc = AgeBinder.bindAge(this.age);
}
public void setAgeDesc(String ageDesc) {
this.ageDesc = ageDesc;
// 当 ageDesc 改变时,通过 inverseBind 方法自动实时更新 age 的值
AgeBinder.inverseBindAge(this.ageDesc, this);
}
}如上,通过设置 bindMethodName 和 inverseBindMethodName 来设置绑定和逆绑定的方法,并设置 bindMethodClass 来指定这些方法所在的类。
生成的 目标类 中,与 字段转换 Conversion Mode 不同的是,当某个字段发生改变时(setter 方法被调用),对应绑定的字段也会被更新(绑定和逆绑定方法被调)。也就是说,字段绑定 Bind Mode 时,字段和该字段对应绑定的字段将会实时双向同步数据。
age 转换为 ageDesc 的配置:
通过指定 @OOOConversion 注解中的 bindMethodName 方法,该方法必须满足以下几个条件:
- 方法必须是
public static - 方法的返回值必须为
targetFieldType指定的类型,方法返回值即为转换结果。 - 参数表示你本次转换需要使用到的对象,可以任意多个,但是必须满足以下几个条件之一:
- 参数名为对应的
目标类中的字段名,并且该参数类型与对应的字段要一致,生成的代码在调用时会把对应字段转入到该方法。 - 参数名为
self,类型为目标类,生成的代码在调用时会把当前的目标类对象传入到该方法。 - 参数名为
other,类型为源类,生成的代码在调用时会把当前的源类对象传入到该方法。
- 参数名为对应的
ageDesc 转换为 age 的配置:
通过指定 @OOOConversion 注解中的 inverseBindMethodName 方法,该方法必须满足以下几个条件:
- 方法必须是
public static - 方法的返回值必须为
void - 参数表示你本次转换需要使用到的对象,可以任意多个,但是必须满足以下几个条件之一:
- 参数名为对应的
目标类中的字段名,并且该参数类型与对应的字段要一致,生成的代码在调用时会把对应字段转入到该方法。 - 参数名为
self,类型为目标类,生成的代码在调用时会把当前的目标类对象传入到该方法。 - 参数名为
other,类型为源类,生成的代码在调用时会把当前的源类对象传入到该方法。
- 参数名为对应的
转换结果通过调用 other 方法参数去进行设置(setter)。
注意:
bindMethodName和inverseBindMethodName也可以只设置其中一个。一般情况下使用 字段绑定 模式的同时应该保留所有相关的字段(
bindMethodName和inverseBindMethodName中参数对应的所有字段)。
bindMethodName和inverseBindMethodName的对应的方法默认是在Generator中编写,但是你也可以通过指定@OOOConversion注解的bindMethodClass来指定转换方法所在的类。
为了解决在不同领域模型中对象频繁地互相转换所带来的性能问题,RapidOOO 也支持使用对象池来构建领域对象,方法如下:
// 源类
@OOOs(suffix = "BO", ooos = {
@OOO(
from = Pet.class,
// ...
pool = @OOOPool(acquireMethod = "acquirePetBO", releaseMethod = "releasePetBO")
)
})
// 配置 generator
public class BOGenerator {
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);
}
}
// 目标类(RapidOOO 自动生成)
public class PetBO{
// ...
public static PetBO create(Pet pet) {
PetBO petBO = BOGenerator.acquirePetBO();
petBO.fromPet(pet);
return petBO;
}
// ...
public void release() {
BOGenerator.releasePetBO(this);
}
// ...
}
// 使用方法
Pet pet = new Pet();
PetBO petBO = PetBO.create(pet);
petBO.release();如上,通过 @OOO 注解中的 pool 来配置对象池的 acquireMethod 和 releaseMethod 方法。
其中 acquireMethod 方法必须满足如下的要求:
- 方法必须是
public static - 方法无参数
- 方法返回值为
目标类类型
其中 releaseMethod 方法必须满足如下的要求:
- 方法必须是
public static - 方法参数必须是只有 1 个,并且类型为
目标类类型 - 方法返回值为
void
使用 @OOOConversion 扩展字段时,目前支持以下多种类型:
使用数组时,需要按照如下设置 targetFieldTypeId:
// ...
@OOOConversion(
targetFieldName = "chatVOs",
targetFieldTypeId = "#id__ChatVO[]"
// ...
),
@OOOConversion(
targetFieldName = "comments",
targetFieldTypeId = "java.lang.String[]"
// ...
),
// ...如上:支持 id 和具体类型的权限定名,后面加上 [],表示数组。
使用 List 时,需要按照如下设置 targetFieldTypeId:
// ...
@OOOConversion(
targetFieldName = "chatVOs",
targetFieldTypeId = "java.util.List<#id__ChatVO>"
// ...
),
@OOOConversion(
targetFieldName = "comments",
targetFieldTypeId = "java.util.ArrayList<java.lang.String>"
// ...
),
// ...如上:泛型支持 id 和具体类型的权限定名,List 支持权限定名的 List, ArrayList, LinkedList 等等。
使用 Map 时,需要按照如下设置 targetFieldTypeId:
// ...
@OOOConversion(
targetFieldName = "chatVOs",
targetFieldTypeId = "java.util.Map<java.lang.String, #id__ChatBO>"
// ...
),
@OOOConversion(
targetFieldName = "comments",
targetFieldTypeId = "java.util.Map<java.lang.String, java.lang.Integer>"
// ...
),
// ...如上:泛型(Key 和 Value)支持 id 和具体类型的权限定名,Map 支持权限定名的 Map, HashMap, TreeMap 等等。
如果你需要对转换的字段做一些额外的处理,你可以通过 控制委托 Control Delegate 来实现,比如通过 OOOLazyControlDelegate 来实现懒初始化字段。
注意:目前
控制委托 Control Delegate只支持bindMethod和conversionMethod,并不支持inverseBindMethod和inverseConversionMethod
什么情况下可能需要使用到懒加载?
比如通过 Message 中的 videoUrl 转换为 MessageVO 中的 MediaPlayer 类,避免资源浪费,MediaPlayer 实例应该作为懒加载的对象,如下:
@OOOConversion(
targetFieldName = "lazyVideoPlayer",
targetFieldType = MediaPlayer.class,
// 设置转换方法
conversionMethodName = "conversionLazyVideo",
// 设置为懒加载的控制委托
controlDelegate = OOOLazyControlDelegate.class
),
public static MediaPlayer conversionLazyVideo(MessageVO self, String videoUrl){
OOOLazy<MediaPlayer> lazyMediaPlayer = self.getLazyVideoPlayer();
if(lazyMediaPlayer.isInitialized()){
lazyMediaPlayer.get().stop();
lazyMediaPlayer.get().release();
}
if(null == videoUrl){
return null;
}
MediaPlayer mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(videoUrl);
mediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
return mediaPlayer;
}以上,通过 @OOOConversion 中的 controlDelegate 配置对应的控制委托的 Class 类型即可,这里配置了 OOOLazyControlDelegate.class。
conversionLazyVideo 方法只会在 OOOLazy<MediaPlayer> 调用 getter 时才会被调用。
ControlDelegate 还支持自定义扩展,继承 OOOControlDelegate 类,实现 invoke 方法即可。
其中 invoke 中的两个参数:
Func0R<T> inputFunc:该方法调用call()就会直接调用bindMethod或conversionMethod方法,返回 T 类型结果Func1<R> outputFunc:该方法调用后,将会把输入的值赋值给目标类中的对应字段。
其中 inputFunc 和 outputFunc 的调用时机可以通过自定义的 ControlDelegate 进行控制。
比如以下是扩展的 OOONewThreadControlDelegate,表示字段在进行 bind / conversion 时,将会在新的线程中执行:
public class OOONewThreadControlDelegate<T> implements OOOControlDelegate<T, T> {
@Override
public final void invoke(Func0R<T> inputFunc, Func1<R> outputFunc) {
invokeSafe(new WeakReference<>(inputFunc), new WeakReference<>(outputFunc));
}
private void invokeSafe(final WeakReference<Func0R<T>> inputFuncWRef, final WeakReference<Func1<T>> outputFuncWRef) {
// TODO: 2019-06-26 wangjie
new Thread(new Runnable() {
@Override
public void run() {
Func0R<T> inputFunc = inputFuncWRef.get();
Func1<T> outputFunc = outputFuncWRef.get();
if (null != inputFunc && null != outputFunc) {
// 子线程中执行,并把结果赋值
outputFunc.call(inputFunc.call());
}
}
}).start();
}
}注意:以上
OOONewThreadControlDelegate只供自定义实现控制委托方式的参考。