我们使用的是Hutool工具包的cn.hutool.core.bean.BeanUtil解决对象拷贝复制场景。
工作中我们经常做这样工作:比如说将VO复制成DO。 VO、DTO、DTO、BO,RequestDTO互相转化。
我们服务作为系统的开放平台应用,统一维护管理第三方平台API接口。比如企业微信接口。而我们使用开源项目 wxJava 方便我们调用企业微信API。 我们需要将wxJava 的接口入参类复制一份作为项目的RequestDTO,做到业务隔离避免其他项目直接依赖。所以牵扯到到大量的对象拷贝工作。
WxCpWelcomeMsg
/**
* 消息文本消息.
*
* @author Binary Wang
* @date 2020-08-16
*/········· ·
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WxCpWelcomeMsg implements Serializable {
private static final long serialVersionUID = 4170843890468921757L;
@SerializedName("welcome_code")
private String welcomeCode;
private Text text;
private List attachments;
public String toJson() {
return WxCpGsonBuilder.create().toJson(this);
}
}
/**
* 消息文本消息.
*
* @author Binary Wang
* @date 2020-08-16
*/
@Data
@Accessors(chain = true)
public class Text implements Serializable {
private static final long serialVersionUID = 6608288753719551600L;
private String content;
}
package me.chanjar.weixin.cp.bean.external.msg;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import me.chanjar.weixin.cp.constant.WxCpConsts;
import java.io.Serializable;
/**
* @author chutian0124
*/
@Data
public class Attachment implements Serializable {
private static final long serialVersionUID = -8078748379570640198L;
@SerializedName("msgtype")
private String msgType;
private Image image;
private Link link;
@SerializedName("miniprogram")
private MiniProgram miniProgram;
private Video video;
private File file;
public void setImage(Image image) {
this.image = image;
this.msgType = WxCpConsts.WelcomeMsgType.IMAGE;
}
public void setLink(Link link) {
this.link = link;
this.msgType = WxCpConsts.WelcomeMsgType.LINK;
}
public void setMiniProgram(MiniProgram miniProgram) {
this.miniProgram = miniProgram;
this.msgType = WxCpConsts.WelcomeMsgType.MINIPROGRAM;
}
public void setVideo(Video video) {
this.video = video;
this.msgType = WxCpConsts.WelcomeMsgType.VIDEO;
}
public void setFile(File file) {
this.file = file;
this.msgType = WxCpConsts.WelcomeMsgType.FILE;
}
}
/**
* 图片消息.
*
* @author Binary Wang
* @date 2020-08-16
*/
@Data
public class Image implements Serializable {
private static final long serialVersionUID = -606286372867787121L;
@SerializedName("media_id")
private String mediaId;
@SerializedName("pic_url")
private String picUrl;
}
/**
* 图文消息.
*
* @author Binary Wang
* @date 2020-08-16
*/
@Data
public class Link implements Serializable {
private static final long serialVersionUID = -8041816740881163875L;
private String title;
@SerializedName("picurl")
private String picUrl;
private String desc;
private String url;
@SerializedName("media_id")
private String mediaId;
}
/**
* 小程序消息.
*
* @author Binary Wang
* @date 2020-08-16
*/
@Data
public class MiniProgram implements Serializable {
private static final long serialVersionUID = 4242074162638170679L;
private String title;
@SerializedName("pic_media_id")
private String picMediaId;
private String appid;
private String page;
}
/**
* 视频消息
*
* @author pg
* @date 2021-6-21
*/
@Data
public class Video implements Serializable {
private static final long serialVersionUID = -6048642921382867138L;
@SerializedName("media_id")
private String mediaId;
@SerializedName("thumb_media_id")
private String thumbMediaId;
}
/**
* @author Binary Wang
* @date 2021-08-23
*/
@Data
public class File implements Serializable {
private static final long serialVersionUID = 2794189478198329090L;
@SerializedName("media_id")
private String mediaId;
}
WxCpWelcomeMsg 是我们自定义RequestDTO
String content = "{\"attachments\":[{\"image\":{},\"msgType\":\"image\"}],\"platformCode\":\"corp_wx\",\"responseClass\":\"java.lang.Void\",\"responseType\":\"java.lang.Void\",\"text\":{\"content\":\"22\"},\"welcomeCode\":\"Eu8O9rXwWoaPRTXGmNT-F1_aDQevOWjI6FyVEyBnZLk\",\"wxApiEnum\":\"ExternalContact\"}";
WxCpWelcomeMsgRequest request = JSON.parseObject(content, WxCpWelcomeMsgRequest.class);
BeanUtil.copyProperties(request,WxCpWelcomeMsg.class);
msgType值竟然是file ,不是image。这是什么奇葩现象!
BeanUtil工具会调用目标类每个setter方法,哪怕入参是null,导致msgType等于file
WxCpWelcomeMsg wxCpWelcomeMsg1 = new WxCpWelcomeMsg();
BeanUtil.copyProperties(request, wxCpWelcomeMsg1,CopyOptions.create().setIgnoreNullValue(true));
配置拷贝策略,忽略null但还是不能解决。
过程我就不贴出来。直接给出最终定位的方法
/**
* 转换值为指定类型
*
* @param 转换的目标类型(转换器转换到的类型)
* @param type 类型目标
* @param value 被转换值
* @param defaultValue 默认值
* @param isCustomFirst 是否自定义转换器优先
* @return 转换后的值
* @throws ConvertException 转换器不存在
*/
@SuppressWarnings("unchecked")
public T convert(Type type, Object value, T defaultValue, boolean isCustomFirst) throws ConvertException {
if (TypeUtil.isUnknown(type) && null == defaultValue) {
// 对于用户不指定目标类型的情况,返回原值
return (T) value;
}
if (ObjectUtil.isNull(value)) {
return defaultValue;
}
if (TypeUtil.isUnknown(type)) {
type = defaultValue.getClass();
}
if (type instanceof TypeReference) {
type = ((TypeReference>) type).getType();
}
// 标准转换器
final Converter converter = getConverter(type, isCustomFirst);
if (null != converter) {
return converter.convert(value, defaultValue);
}
Class rowType = (Class) TypeUtil.getClass(type);
if (null == rowType) {
if (null != defaultValue) {
rowType = (Class) defaultValue.getClass();
} else {
// 无法识别的泛型类型,按照Object处理
return (T) value;
}
}
// 特殊类型转换,包括Collection、Map、强转、Array等
final T result = convertSpecial(type, rowType, value, defaultValue);
if (null != result) {
return result;
}
// 尝试转Bean
if (BeanUtil.isBean(rowType)) {
return new BeanConverter(type).convert(value, defaultValue);
}
// 无法转换
throw new ConvertException("Can not Converter from [{}] to [{}]", value.getClass().getName(), type.getTypeName());
}
// 尝试转Bean
if (BeanUtil.isBean(rowType)) {
return new BeanConverter(type).convert(value, defaultValue);
}
/**
* 构造,默认转换选项,注入失败的字段忽略
*
* @param beanType 转换成的目标Bean类型
*/
public BeanConverter(Type beanType) {
this(beanType, CopyOptions.create().setIgnoreError(true));
}
这块使用new BeanConverter(type) .构造器。 没有调用使用者传入的CopyOptions拷贝选项。
看起来Hutool 这块设计比较差!
WxCpWelcomeMsg wxCpWelcomeMsg = new WxCpWelcomeMsg();
BeanCopier beanCopier = BeanCopier.create(WxCpWelcomeMsgRequest.class, WxCpWelcomeMsg.class, false);
beanCopier.copy(request,wxCpWelcomeMsg,null);
由于Text类定义使用Accessors 注解
/**
* 消息文本消息.
*
* @author Binary Wang
* @date 2020-08-16
*/
@Data
@Accessors(chain = true)
public class Text implements Serializable {
private static final long serialVersionUID = 6608288753719551600L;
private String content;
}
翻看beanCopirer源码,无法获取包含返回值不为void的set方法。
DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.registerFilter(new MyFilter<>());
mapperFactory.getMapperFacade().map(request,wxCpWelcomeMsg);
/**
* 配置过滤器,若入参对象是空,则不注入
**/
public class MyFilter extends NullFilter {
@Override
public boolean shouldMap(Type sourceType, String sourceName, S source, Type destType, String destName, D dest, MappingContext mappingContext) {
return source != null;
}
}
按预期结果拷贝参数。
Orika 组件是兼容Lombok的Accessors 配置的。