大家好,我是哪吒。
成都创新互联公司专注为客户提供全方位的互联网综合服务,包含不限于成都网站建设、做网站、贵德网络推广、小程序定制开发、贵德网络营销、贵德企业策划、贵德品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;成都创新互联公司为所有大学生创业者提供贵德建站搭建服务,24小时服务热线:18980820575,官方网址:www.cdcxhl.com
今天,通过代码实例、源码解读、四大工具类横向对比的方式,和大家一起聊一聊对象赋值的问题。
在实际的项目开发中,对象间赋值普遍存在,随着双十一、秒杀等电商过程愈加复杂,数据量也在不断攀升,效率问题,浮出水面。
答:想都不用想,直接代码走起来,get、set即可。
问:下图这样?
答:对啊,你怎么能把我的代码放到网上?
问:没,我只是举个例子
答:这涉及到商业机密,是很严重的问题
问:我发现你挺能扯皮啊,直接回答问题行吗?
问:行了,行了,说说,怎么解决吧。
答:很简单啊,可以通过工具类Beanutils直接赋值啊
答:就Apache自带的那个啊,贼简单。我手写一个,给你欣赏一下。
问:你这代码报错啊,避免用Apache Beanutils进行属性的copy。
答:没报错,只是严重警告而已,代码能跑就行,有问题再优化呗
答:拿多少钱,干多少活,我又不是XXX,应该是性能问题吧
问:具体什么原因导致的呢?
答:3000块钱还得手撕一下 apache copyProperties 的源代码呗?
通过单例模式调用copyProperties,但是,每一个方法对应一个BeanUtilsBean.getInstance()实例,每一个类实例对应一个实例,这不算一个真正的单例模式。
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
通过源码可以看到,每一个copyProperties都要进行多次类型检查,还要打印日志。
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
// 类型检查
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 打印日志
if (this.log.isDebugEnabled()) {
this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
}
int var5;
int var6;
String name;
Object value;
// 类型检查
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (orig instanceof DynaBean) {
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 每个属性都调用一次copyProperty
this.copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
...
} else {
...
}
}
}
再通过jvisualvm.exe检测一下运行情况,果然,logging.log4j赫然在列,稳居耗时Top1。
public class Test {
private static void apacheBeanUtilsCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.apache.commons.beanutils.BeanUtils.copyProperties(source, target);
}
}
private static void commonsPropertyCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);
}
}
static BeanCopier copier = BeanCopier.create(User.class, User.class, false);
private static void cglibBeanCopyTest(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.springframework.cglib.beans.BeanCopier.copier.copy(source, target, null);
}
}
private static void springBeanCopy(User source, User target, int sum){
for (int i = 0; i < sum; i++) {
org.springframework.beans.BeanUtils.copyProperties(source, target);
}
}
}
方法 |
1000 |
10000 |
100000 |
1000000 |
apache BeanUtils |
906毫秒 |
807毫秒 |
1892毫秒 |
11049毫秒 |
apache PropertyUtils |
17毫秒 |
96毫秒 |
648毫秒 |
5896毫秒 |
spring cglib BeanCopier |
0毫秒 |
1毫秒 |
3毫秒 |
10毫秒 |
spring copyProperties |
87毫秒 |
90毫秒 |
123毫秒 |
482毫秒 |
不测不知道,一测吓一跳,差的还真的多。
spring cglib BeanCopier性能最好,apache BeanUtils性能最差。
性能走势 --> spring cglib BeanCopier 优于 spring copyProperties 优于 apache PropertyUtils 优于 apache BeanUtils
避免用Apache Beanutils进行属性的copy的问题 上面分析完了,下面再看看其它的方法做了哪些优化。
从源码可以清晰的看到,类型检查变成了非空校验,去掉了每一次copy的日志记录,性能肯定更好了。
DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能。
public void copyProperties(Object dest, Object orig){
// 判断数据源和目标对象不是null
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 删除了org.apache.commons.beanutils.BeanUtils.copyProperties中最为耗时的log日志记录
int var5;
int var6;
String name;
Object value;
// 类型检查
if (orig instanceof DynaBean) {
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 相对于org.apache.commons.beanutils.BeanUtils.copyProperties此处有优化
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
// 每个属性都调用一次copyProperty
this.setSimpleProperty(dest, name, value);
}
}
}
} else if (orig instanceof Map) {
...
} else {
...
}
}
}
再通过jvisualvm.exe检测一下运行情况,果然,logging.log4j没有了,其他的基本不变。
private static void copyProperties(Object source, Object target, @Nullable Class> editable,
@Nullable String... ignoreProperties){
// 判断数据源和目标对象不是null
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
/**
* 若target设置了泛型,则默认使用泛型
* 若是 editable 是 null,则此处忽略
* 一般情况下editable都默认为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;
}
// 获取target中全部的属性描述
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
// 需要忽略的属性
ListignoreList = (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();
/**
* 源对象存在读取方法、数据是可复制的
* writeMethod.getParameterTypes()[0]:获取 writeMethod 的第一个入参类型
* readMethod.getReturnType():获取 readMethod 的返回值类型
* 判断返回值类型和入参类型是否存在继承关系,只有是继承关系或相等的情况下,才会进行注入
*/
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
// 放开读取方法的权限
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);
}
}
}
}
}
阿里的友情提示,避免用Apache Beanutils进行对象的copy,还是很有道理的。
Apache Beanutils 的性能问题出现在类型校验和每一次copy的日志记录。
本文转载自微信公众号「哪吒编程」,可以通过以下二维码关注。转载本文请联系哪吒编程公众号。
分享标题:为什么要避免用ApacheBeanutils进行属性的copy
分享URL:http://www.gawzjz.com/qtweb2/news31/22481.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联