声明:java
一、DO(业务实体对象),DTO(数据传输对象)。spring
在一个成熟的工程中,尤为是如今的分布式系统中,应用与应用之间,还有单独的应用细分模块以后,DO 通常不会让外部依赖,这时候须要在提供对外接口的模块里放 DTO 用于对象传输,也便是 DO 对象对内,DTO对象对外,DTO 能够根据业务须要变动,并不须要映射 DO 的所有属性。shell
/** * 获取营销信息 * * @param hallId 场馆编号 * @param modelCode 车型代码 * @return */
BrandMarketInfoDTO getBrandMarketInfo(String hallId, String modelCode);
复制代码
/** * 获取水牌营销信息 * * @param hallId * @param modelCode * @return */
HallCarManageDO getBoardMarketInfo(@Param("hallId") String hallId, @Param("modelCode") String modelCode);
复制代码
可是咱们HallCarManageDO和BrandMarketInfoDTO中的属性和属性类型不是相等的。这种 对象与对象之间的互相转换,就须要有一个专门用来解决转换问题的工具,毕竟每个字段都 get/set 会很麻烦。缓存
如下列举被广大开发工程师经常使用的Bean属性复制工具安全
如下选取属性赋值的功能来对比每一个工具类的不一样app
import org.springframework.beans.BeanUtils;
/** * @author james mu * @date 2019/10/22 */
public class PojoUtils {
/** * * @param source 源对象 * @param clazz 目标对象 * * @return 复制属性后的对象 */
public static <T> T copyProperties(Object source, Class<T> clazz) {
if (source == null) {
return null;
}
T target;
try {
target = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("经过反射建立对象失败");
}
BeanUtils.copyProperties(source, target);
return target;
}
}
复制代码
springframework
的BeanUtils也是经过java内省机制
获取getter/setter,而后经过反射调用从而实现属性复制。分布式
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
复制代码
import net.sf.cglib.beans.BeanCopier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** * @author james mu * @date 2019/10/22 */
public class BeanCopierUtils {
/** * BeanCopier拷贝速度快,性能瓶颈出如今建立BeanCopier实例的过程当中。 * 因此,把建立过的BeanCopier实例放到缓存中,下次能够直接获取,提高性能 */
public static Map<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>();
/** * cp 对象赋值 * * @param source 源对象 * @param target 目标对象 */
public static void copyProperties(Object source, Object target) {
String beanKey = generateKey(source.getClass(), target.getClass());
BeanCopier copier = null;
if (!beanCopierMap.containsKey(beanKey)) {
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
beanCopierMap.put(beanKey, copier);
} else {
copier = beanCopierMap.get(beanKey);
}
copier.copy(source, target, null);
}
private static String generateKey(Class<?> class1, Class<?> class2) {
return class1.toString() + class2.toString();
}
}
复制代码
使用动态代理,生成字节码类,再经过Java反射成Class,调用其copy方法。ide
你们能够看到这里用到了ConcurrentHashMap存取copier
,由于BeanCopier.create使用了缓存,该过程也消耗资源,建议全局只初始化一次。工具
支持自定义转换器。post
MapSturct
是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。
抓一下重点:
JavaBean
之间那的映射代码从字面的理解, 咱们能够知道, 该工具能够帮咱们实现 JavaBean
之间的转换, 经过注解的方式。
同时, 做为一个工具类,相比于手写, 其应该具备便捷, 不容易出错的特色。
依赖引用
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
<scope>provided</scope>
</dependency>
复制代码
案例演示
UserDO
import lombok.Data;
import lombok.ToString;
import java.util.Date;
/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDO {
private String name;
private String password;
private Integer age;
private Date birthday;
private String sex;
}
复制代码
UserDTO
import lombok.Data;
import lombok.ToString;
/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDTO {
private String name;
private String age;
private String birthday;
private String gender;
}
复制代码
UserConvertUtils
import com.sanshengshui.javabeanconvert.DO.UserDO;
import com.sanshengshui.javabeanconvert.DTO.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/** * @author james mu * @date 2019/10/22 */
@Mapper
public interface UserConvertUtils {
UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.class);
/** * 类型转换 * * @param userDO UserDO数据持久层类 * @return 数据传输类 */
@Mappings({
@Mapping(target = "gender", source = "sex")
})
UserDTO doToDTO(UserDO userDO);
}
复制代码
@Mapping
明确关系来造成映射(如sex对应gender)结果以下:
UserDO(name=snow, password=123, age=20, birthday=Tue Oct 22 17:10:19 CST 2019, sex=男)
+-+-+-+-+-+-+-+-+-+-+-
UserDTO(name=snow, age=20, birthday=19-10-22 下午5:10, gender=男)
复制代码
上面中, 我写了3个步骤来实现了从 UserDTO
到 UserDO
的转换。
那么, 做为一个注解处理器, 经过MapStruct
生成的代码具备怎么样的优点呢?
Java反射原理和反射低的缘由:juejin.im/post/5da33b…
这是相对反射来讲的, 反射须要去读取字节码的内容, 花销会比较大。 而经过 MapStruct
来生成的代码, 其相似于人手写。 速度上能够获得保证。
前面例子中生成的代码能够在编译后看到。 在 target/generated-sources/annotations 里能够看到。
对应的代码
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-10-22T17:10:17+0800",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Azul Systems, Inc.)"
)
public class UserConvertUtilsImpl implements UserConvertUtils {
@Override
public UserDTO doToDTO(UserDO userDO) {
if ( userDO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setGender( userDO.getSex() );
userDTO.setName( userDO.getName() );
if ( userDO.getAge() != null ) {
userDTO.setAge( String.valueOf( userDO.getAge() ) );
}
if ( userDO.getBirthday() != null ) {
userDTO.setBirthday( new SimpleDateFormat().format( userDO.getBirthday() ) );
}
return userDTO;
}
}
复制代码
能够看到其生成了一个实现类, 而代码也相似于咱们手写, 通俗易懂。
测试在两个简单的Bean之间转换的耗时,执行次数分别为十、100、1k、10k、100k,时间单位为ms。
虽然反射效率低,但这个时间是很小很小的。根据不一样工具的性能及功能维度,我的建议当对象转换操做较少或者应用对性能要求较高时,尽可能不采用工具,而是手写getter/setter;在不考虑性能的状况下,普通的对象转换可使用Cglib.BeanCopier,复杂的对象转换使用MapStruct。