您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息
三六零分类信息网 > 阿里分类信息网,免费分类信息发布

为什么阿里代码规约要求避免使用 Apache BeanUtils 进行属性复制

2019/6/20 12:44:07发布108次查看
缘起有一次开发过程中,刚好看到小伙伴在调用 set 方法,将数据库中查询出来的 po 对象的属性拷贝到 vo 对象中,类似这样:
可以看出,po 和 vo 两个类的字段绝大部分是一样的,我们一个个地调用 set 方法只是做了一些重复的冗长的操作。这种操作非常容易出错,因为对象的属性太多,有可能会漏掉一两个,而且肉眼很难察觉。
类似这样的操作,我们很容易想到可以通过反射来解决。其实,如此普遍通用的功能,一个 beanutils 工具类就可以搞定了。
于是我建议这位小伙伴了解一下 beanutils,后来他使用了 apache beanutils.copyproperties 进行属性拷贝,这为程序挖了一个坑!
阿里代码规约当我们开启阿里代码扫描插件时,如果你使用了 apache beanutils.copyproperties 进行属性拷贝,它会给你一个非常严重的警告。因为,apache beanutils性能较差,可以使用 spring beanutils 或者 cglib beancopier 来代替。
看到这样的警告,有点让人有点不爽。大名鼎鼎的 apache 提供的包,居然会存在性能问题,以致于阿里给出了严重的警告。
那么,这个性能问题究竟是有多严重呢?毕竟,在我们的应用场景中,如果只是很微小的性能损耗,但是能带来非常大的便利性,还是可以接受的。
带着这个问题。我们来做一个实验,验证一下。
如果对具体的测试方式没有兴趣,可以跳过直接看结果哦~
测试方法接口和实现定义
首先,为了测试方便,让我们来定义一个接口,并提供各种实现:
public interface
propertiescopier
{
void copyproperties(
object
source,
object
target) throws
exception
;
}
public class
cglibbeancopierpropertiescopier
implements
propertiescopier
{
@override
public void copyproperties(
object
source,
object
target) throws
exception
{
beancopier
copier =
beancopier
.create(source.getclass(), target.getclass(), false);
copier.copy(source, target, null);
}
}
// 全局静态 beancopier,避免每次都生成新的对象
public class
staticcglibbeancopierpropertiescopier
implements
propertiescopier
{
private static
beancopier
copier =
beancopier
.create(
account
.class,
account
.class, false);
@override
public void copyproperties(
object
source,
object
target) throws
exception
{
copier.copy(source, target, null);
}
}
public class
springbeanutilspropertiescopier
implements
propertiescopier
{
@override
public void copyproperties(
object
source,
object
target) throws
exception
{
org.springframework.beans.
beanutils
.copyproperties(source, target);
}
}
public class
commonsbeanutilspropertiescopier
implements
propertiescopier
{
@override
public void copyproperties(
object
source,
object
target) throws
exception
{
org.apache.commons.beanutils.
beanutils
.copyproperties(target, source);
}
}
public class
commonspropertyutilspropertiescopier
implements
propertiescopier
{
@override
public void copyproperties(
object
source,
object
target) throws
exception
{
org.apache.commons.beanutils.
propertyutils
.copyproperties(target, source);
}
}
单元测试
然后写一个参数化的单元测试:
@runwith
(
parameterized
.class)
public class
propertiescopiertest
{
@parameterized
.
parameter
(
0
)
public
propertiescopier
propertiescopier;
// 测试次数
private static
list
testtimes =
arrays
.aslist(
100
,
1000
,
10
_000,
100
_000,
1_000_000
);
// 测试结果以 markdown 表格的形式输出
private static
stringbuilder
resultbuilder = new
stringbuilder
(
|实现|100|1,000|10,000|100,000|1,000,000|\n
).append(
|----|----|----|----|----|----|\n
);
@parameterized
.
parameters
public static
collection
data() {
collection
params = new
arraylist
();
params.add(new
object
[]{new
staticcglibbeancopierpropertiescopier
()});
params.add(new
object
[]{new
cglibbeancopierpropertiescopier
()});
params.add(new
object
[]{new
springbeanutilspropertiescopier
()});
params.add(new
object
[]{new
commonspropertyutilspropertiescopier
()});
params.add(new
object
[]{new
commonsbeanutilspropertiescopier
()});
return params;
}
@before
public void setup() throws
exception
{
string
name = propertiescopier.getclass().getsimplename().replace(
propertiescopier
,
);
resultbuilder.append(
|
).append(name).append(
|
);
}
@test
public void copyproperties() throws
exception
{
account
source = new
account
(
1
,
test1
,
30d
);
account
target = new
account
();
// 预热一次
propertiescopier.copyproperties(source, target);
for (
integer
time : testtimes) {
long start =
system
.nanotime();
for (int i =
0
; i < time; i++) {
propertiescopier.copyproperties(source, target);
}
resultbuilder.append((
system
.nanotime() - start) /
1_000_000d
).append(
|
);
}
resultbuilder.append(
\n
);
}
@afterclass
public static void teardown() throws
exception
{
system
.out.println(
测试结果:
);
system
.out.println(resultbuilder);
}
}
测试结果
结果表明,cglib 的 beancopier 的拷贝速度是最快的,即使是百万次的拷贝也只需要 10 毫秒! 相比而言,最差的是 commons 包的 beanutils.copyproperties 方法,100 次拷贝测试与表现最好的 cglib 相差 400 倍之多。百万次拷贝更是出现了 2600 倍的性能差异!
结果真是让人大跌眼镜。
但是它们为什么会有这么大的差异呢?
原因分析
查看源码,我们会发现 commonsbeanutils 主要有以下几个耗时的地方:
输出了大量的日志调试信息重复的对象类型检查类型转换 public void copyproperties(final
object
dest, final
object
orig)
throws
illegalaccessexception
,
invocationtargetexception
{
// 类型检查
if (orig instanceof
dynabean
) {
...
} else if (orig instanceof
map
) {
...
} else {
final
propertydescriptor
[] origdescriptors = ...
for (
propertydescriptor
origdescriptor : origdescriptors) {
...
// 这里每个属性都调一次 copyproperty
copyproperty(dest, name, value);
}
}
}
public void copyproperty(final
object
bean,
string
name,
object
value)
throws
illegalaccessexception
,
invocationtargetexception
{
...
// 这里又进行一次类型检查
if (target instanceof
dynabean
) {
...
}
...
// 需要将属性转换为目标类型
value = convertforcopy(value, type);
...
}
// 而这个 convert 方法在日志级别为 debug 的时候有很多的字符串拼接
public t convert(final
class
type,
object
value) {
if (log().isdebugenabled()) {
log().debug(
converting
+ (value == null
:
+ tostring(sourcetype) +
) +
value
+ value +
to type
+ tostring(targettype) +
);
}
...
if (targettype.equals(
string
.class)) {
return targettype.cast(converttostring(value));
} else if (targettype.equals(sourcetype)) {
if (log().isdebugenabled()) {
log().debug(
no conversion required, value is already a
+ tostring(targettype));
}
return targettype.cast(value);
} else {
// 这个 converttotype 方法里也需要做类型检查
final
object
result = converttotype(targettype, value);
if (log().isdebugenabled()) {
log().debug(
converted to
+ tostring(targettype) +
value
+ result +
);
}
return targettype.cast(result);
}
}
具体的性能和源码分析,可以参考这几篇文章:
几种copyproperties工具类性能比较:/p/bcbacab3b89e
cglib中beancopier源码实现:/p/f8b892e08d26
java bean copy框架性能对比:/articles/392185
one more thing除了性能问题之外,在使用 commonsbeanutils 时还有其他的坑需要特别小心!
包装类默认值
在进行属性拷贝时,低版本commonsbeanutils 为了解决date为空的问题会导致为目标对象的原始类型的包装类属性赋予初始值,如 integer 属性默认赋值为 0,尽管你的来源对象该字段的值为 null。
这个在我们的包装类属性为 null 值时有特殊含义的场景,非常容易踩坑!例如搜索条件对象,一般 null 值表示该字段不做限制,而 0 表示该字段的值必须为0。
改用其他工具时
当我们看到阿里的提示,或者你看了这篇文章之后,知道了 commonsbeanutils 的性能问题,想要改用 spring 的 beanutils 时,要特别小心:
org.apache.commons.beanutils.
beanutils
.copyproperties(
object
target,
object
source);
org.springframework.beans.
beanutils
.copyproperties(
object
source,
object
target);
从方法签名上可以看出,这两个工具类的名称相同,方法名也相同,甚至连参数个数、类型、名称都相同。但是参数的位置是相反的。因此,如果你想更改的时候,千万要记得,将 target 和 source 两个参数也调换过来!

阿里分类信息网,免费分类信息发布

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录