引言
日常开发中经常遇到PO、VO、DTO等对象之间的转换,Java中beanUtil包下则替我们封装实现了类似的功能,可以简化代码,但性能会有所差距。
对项目中经常使用的属性拷贝工具类进行总结:
- org.apache.commons.beanutils.BeanUtils
- org.apache.commons.beanutils.PropertyUtils
- org.springframework.beans.BeanUtils
字段与属性
public class UserTest{
private String userName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getHello() {
return "hello";
}
public void setHello(String str) {
}
}
上面 private String userName;private String password。准确的来说它们应该称为:字段,而不是本次要讲的属性。
下面简述一下:什么是Java中的属性?
Java中的属性(property),通常可以理解为get和set方法,而字段(field),通常叫做“类成员”,或“类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。
换句话讲Java中的属性是指:设置和读取字段的方法,也就是平常见到的set和get方法。只要是set和get开头的方法在Java里都认为它是属性。
这里再次提醒:字段和属性不是同一个东西。
JDK 中有个API Introspector,获取的是java.beans.BeanInfo 类。
这个类可以通过java.beans.BeanInfo#getPropertyDescriptors : 获取java bean 所有的属性。
public static void main(String[] args) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(UserTest.class);
// 得到类中的所有的属性描述器
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
System.out.println("属性的个数:" + pds.length);
for (PropertyDescriptor pd : pds) {
System.out.println("属性:" + pd.getName());
}
}
result:
属性的个数:4
属性:class
属性:hello
属性:password
属性:userName
上面多了一个 class ,原因很简单,因为Object类是所有类的父类,Object类里有个方法叫 getClass();所以这也验证了咱们刚才说的: “只要是set或者get开头的方法都叫属性”
使用说明
- default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
- public : 对所有类可见。使用对象:类、接口、变量、方法
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
org.springframework.beans.BeanUtils#copyProperties:
- 基本类型和包装类型会自动转换, 方法名称相同,返回值类型和参数类型不同,不进行复制,也不报错;
- 支持指定忽略某些属性不复制;
- 支持类的修饰符 default 、 public。
org.apache.commons.beanutils.PropertyUtils#copyProperties:
- 基本类型和包装类型会自动转换;
- 方法名称相同,返回值类型和参数类型不同,复制失败,会报错,如下:argument type mismatch - had objects of type “java.lang.Double” but expected signature “java.lang.String”;
- 只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public);
org.apache.commons.beanutils.BeanUtils#copyProperties:
- 基本类型和包装类型会自动转换;
- 方法名称相同,返回值类型和参数类型不同,不复制,不报错;
- 只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public);
tips: Spring和apache的copyProperties属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下。
性能实验比较结果如下:
摘要总结:Spring是在次数增多的情况下,性能较好,在数据较少的时候,性能比PropertyUtils的性能差一些。PropertyUtils的性能相对稳定,表现是呈现线性增长的趋势。而Apache的BeanUtil的性能最差,无论是单次Copy还是大数量的多次Copy性能都不是很好。
综合上面的性能分析还是使用 :org.springframework.beans.BeanUtils#copyProperties
原因:
1.这个方法在复制的时候不会因为属性的不同而报错,影响代码执行
2.性能方面也相对较好
其他Apache的两个,
1、org.apache.commons.beanutils.PropertyUtils#copyProperties 复制会直接报错
2、org.apache.commons.beanutils.BeanUtils#copyProperties 性能相对较差
原理剖析
核心本质都是使用反射实现。具体的实现代码稍有不同。
- org.springframework.beans.BeanUtils#copyProperties:
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
-
获取 目标对象 所有的属性 targetPds:
PropertyDescriptor [] targetPds = getPropertyDescriptors(actualEditable); -
循环 targetPds ,并在源对象取出对应的属性:
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(),targetPd.getName()); -
r如果不是修饰不是public,暴力反射 ,然后使用对属性进行设值:
setAccessible(true);// 暴力反射
writeMethod.invoke(target, value);
- org.apache.commons.beanutils.BeanUtilsBean#copyProperties:
// org.apache.commons.beanutils.BeanUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if ("class".equals(name)) {
continue; // No point in trying to set an object's class
}
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
try {
final Object value = getPropertyUtils().getSimpleProperty(orig, name);
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
// org.apache.commons.beanutils.BeanUtilsBean#copyProperty
getPropertyUtils().setSimpleProperty(target, propName, value);
// org.apache.commons.beanutils.PropertyUtilsBean#setSimpleProperty
invokeMethod(writeMethod, bean, values);
-
获取的是源对象的所有的属性:
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig); -
如果属性是class,不复制
if (“class”.equals_(name)) { continue; // No point in trying to set an object’s class} -
循环源对象的属性,做一些检验
copyProperty(dest, name, value);
1.会检验目标对象是否有源对象的属性,没有跳过
2.获取属性的名称类型 -
然后给目标对象设置,最终还是使用反射
method.invoke_(bean, values);
- org.apache.commons.beanutils.PropertyUtilsBean#copyProperties:
// org.apache.commons.beanutils.PropertyUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors =
getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if (isReadable(orig, name) && isWriteable(dest, name)) {
try {
final Object value = getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
((DynaBean) dest).set(name, value);
} else {
setSimpleProperty(dest, name, value);
}
} catch (final NoSuchMethodException e) {
if (log.isDebugEnabled()) {
log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
}
}
}
}
// org.apache.commons.beanutils.PropertyUtilsBean#invokeMethod
method.invoke(bean, values);
-
获取的是源对象的所有的属性:
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig); -
循环源对象的属性,然后给目标对象设置,最终还是使用反射
MapStruct
https://github.com/mapstruct/mapstruct
最后推荐一下MapStruct,性能更优的属性拷贝工具!
使用步骤如下:
1.引入pom依赖:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
2.新建对象类:
public class CarDo {
private int num;
private String name;
public CarDo(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CarDto {
private int num;
private String carName;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getCarName() {
return carName;
}
public void setCarName(String carName) {
this.carName = carName;
}
public CarDto(int num, String carName) {
this.num = num;
this.carName = carName;
}
}
3.创建映射接口mapper
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "name", target = "carName")
CarDto carToCarDto(CarDo car);
}
4.测试:
public static void shouldMapCarToDto() {
//given
CarDo car = new CarDo(5, "bens");
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
//then
Assert.notNull(carDto.getCarName(), "carDto is null");
System.out.println("carName:" + carDto.getCarName());
}
MapStruct自动生成了对象属性转换的代码:
//自动生成代码
public class CarMapperImpl implements CarMapper {
public CarMapperImpl() {
}
public CarDto carToCarDto(CarDo car) {
if (car == null) {
return null;
} else {
String carName = null;
int num = false;
carName = car.getName();
int num = car.getNum();
CarDto carDto = new CarDto(num, carName);
return carDto;
}
}
}
总结:
觉得有用的客官可以点赞、关注下!感谢支持🙏谢谢!