Bean属性Copy问题整理汇总
1. bean copy性能对比
- 结论: set > cglib > spring > apache
常用bean copy方法:
org.apache.commons.beanutils.BeanUtils.copyProperties(target, source)
- apache的BeanUtils日志级别是debug的,会打印一堆日志,logback关闭日志方法
<logger name="org.apache.commons.beanutils.BeanUtils" level="ERROR" additivity="false" />
- apache的BeanUtils日志级别是debug的,会打印一堆日志,logback关闭日志方法
org.springframework.beans.BeanUtils.copyProperties(source,target)
BeanCopier beanCopier=org.springframework.cglib.beans.BeanCopier.create(source,target,boolean useConverter); beanCopier.copy(source, target, converter)
- 实质是动态生成一个子类,子类中包含一个方法:
public void copy(Object var1, Object var2, Converter var3){}
其中var1就是source,var2就是target,并且copy()方法中是直接调用的set方法,因此性能和直接java set基本一致
- 实质是动态生成一个子类,子类中包含一个方法:
- java set方法
工具类 | 数量 | 耗时(ms) |
---|---|---|
apache | 1w | 1070 |
spring | 1w | 198 |
cglib 循环内创建BeanCopie | 1w | 20 |
cglib 循环外创建BeanCopie | 1w | 3 |
java set | 1w | 0 |
apache | 10w | 10491 |
spring | 10w | 1994 |
cglib 循环内创建BeanCopie | 10w | 198 |
cglib 循环外创建BeanCopie | 10w | 13 |
java set | 10w | 8 |
- 特别
org.apache.commons.beanutils.BeanUtils.copyProperties
随着数量的增加性能急剧下降(特别是并发时候一定不能使用) - 相对来说cglib的性能会好上很多,但是
org.springframework.cglib.beans.BeanCopier.create()
创建也非常耗时,使用时候可以考虑根据做内存缓存或者在循环外面执行,性能会好上很多 - 在性能要很高情况下可以使用cglib、或者直接set方法
public static BeanDemo createBeanDemo(int pk) {
return new BeanDemo(pk, "a_" + pk, "b_" + pk, "c_" + pk, "d_" + pk, "e_" + pk, "f_" + pk, "g_" + pk, "h_" + pk, "j_" + pk, "k_" + pk);
}
public static void beanCopyCompare( int loopNum) {
try {
// int loopNum = 50000;
List<BeanDemo> dataList = new ArrayList<>();
for (int i = 0; i < loopNum; i++) {
dataList.add(createBeanDemo(i));
}
BeanDemo demo = new BeanDemo();
StopWatch sw = new StopWatch("各个bean copy新能对比");
sw.start("apache-BeanUtils");
for (int i = 0; i < loopNum; i++) {
org.apache.commons.beanutils.BeanUtils.copyProperties(demo, dataList.get(i));
}
sw.stop();
sw.start("spring-BeanUtils");
for (int i = 0; i < loopNum; i++) {
org.springframework.beans.BeanUtils.copyProperties(dataList.get(i), demo);
}
sw.stop();
sw.start("cglib-BeanUtils createBeanCopierInLoop");
for (int i = 0; i < loopNum; i++) {
org.springframework.cglib.beans.BeanCopier beanCopier2 = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
beanCopier2.copy(dataList.get(i), demo, null);
}
sw.stop();
org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
sw.start("cglib-BeanUtils createBeanCopierOutOfLoop");
for (int i = 0; i < loopNum; i++) {
beanCopier.copy(dataList.get(i), demo, null);
}
sw.stop();
sw.start("set");
for (int i = 0; i < loopNum; i++) {
BeanDemo.directSet(dataList.get(i), demo);
}
sw.stop();
System.out.println(sw.prettyPrint());
} catch (Exception e) {
e.printStackTrace();
}
}
- 本地执行效果
-- loopNum=1w
StopWatch '各个bean copy新能对比': running time (millis) = 1291
-----------------------------------------
ms % Task name
-----------------------------------------
01070 083% apache-BeanUtils
00198 015% spring-BeanUtils
00020 002% cglib-BeanUtils createBeanCopierInLoop
00003 000% cglib-BeanUtils createBeanCopierOutOfLoop
00000 000% set
loopNum=1w
StopWatch '各个bean copy新能对比': running time (millis) = 12704
-----------------------------------------
ms % Task name
-----------------------------------------
10491 083% apache-BeanUtils
01994 016% spring-BeanUtils
00198 002% cglib-BeanUtils createBeanCopierInLoop
00013 000% cglib-BeanUtils createBeanCopierOutOfLoop
00008 000% set
- 1.
org.apache.commons.beanutils.BeanUtils.copyProperties()
源码:BeanUtilsBean.getInstance()
中用使用到synchronized
因此有并发要求时候性能会很差
public static void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
public static BeanUtilsBean getInstance() {
return BEANS_BY_CLASSLOADER.get();
}
public synchronized T get() {
// synchronizing the whole method is a bit slower
// but guarantees no subtle threading problems, and there's no
// need to synchronize valueByClassLoader
// make sure that the map is given a change to purge itself
valueByClassLoader.isEmpty();
try {
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
T value = valueByClassLoader.get(contextClassLoader);
if ((value == null)
&& !valueByClassLoader.containsKey(contextClassLoader)) {
value = initialValue();
valueByClassLoader.put(contextClassLoader, value);
}
return value;
}
} catch (final SecurityException e) { /* SWALLOW - should we log this? */ }
// if none or exception, return the globalValue
if (!globalValueInitialized) {
globalValue = initialValue();
globalValueInitialized = true;
}//else already set
return globalValue;
}
- 2.
org.springframework.beans.BeanUtils.copyProperties
源码: - 注意里面有调用
Modifier.isPublic()
方法,只能copy public修饰的属性 - 性能差的原因在for循环
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
private static void copyProperties(Object source, Object target, Class<?> editable, 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循环导致性能下降
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);
}
}
}
}
}
}
- 3.
org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
生成子类源码:
public static void main(String[] args) {
//查生成的源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "d:/66");
org.springframework.cglib.beans.BeanCopier beanCopier = org.springframework.cglib.beans.BeanCopier.create(BeanDemo.class, BeanDemo.class, false);
BeanDemo createBeanDemo =createBeanDemo(1);
BeanDemo demo = new BeanDemo();
beanCopier.copy(createBeanDemo, demo, null);
}
public class Object$$BeanCopierByCGLIB$$59d4a388 extends BeanCopier {
public Object$$BeanCopierByCGLIB$$59d4a388() {
}
public void copy(Object var1, Object var2, Converter var3) {
BeanDemo var10000 = (BeanDemo)var2;
BeanDemo var10001 = (BeanDemo)var1;
var10000.setF1(((BeanDemo)var1).getF1());
var10000.setF10(var10001.getF10());
var10000.setF2(var10001.getF2());
var10000.setF3(var10001.getF3());
var10000.setF4(var10001.getF4());
var10000.setF5(var10001.getF5());
var10000.setF6(var10001.getF6());
var10000.setF7(var10001.getF7());
var10000.setF8(var10001.getF8());
var10000.setF9(var10001.getF9());
var10000.setPk(var10001.getPk());
}
}
2. bean copy部分属性
应用场景:由于性能要求在内存缓存了新闻实体的全部属性信息,每次拿新闻实体信息时候先去内存缓存拿,拿不到在去db取(reids或者mysql等),由于业务逻辑原因需要只copy缓存bean中部分属性到目标bean,业务场景:
Bean b1=new Bean
b1.setF1(123);
//从缓存获取bean
Bean orgBean=BeanCache.get(pk)
// 此处需要把orgBean中除了f1之外信息赋值给b1,因此不能使用全bean copy
//传统写法一堆setter()方法...
- org.apache.commons.beanutils.BeanUtils.copyProperty(final Object bean, final String name, final Object value) 效率太慢
- 核心代码解析通过jdk自带的Introspector(内省)类,
Introspector.getBeanInfo(currentClass).getPropertyDescriptors()
获取类中属性对应的getter、setter方法信息- Method writeMethod=propertyDescriptor.getWriteMethod()对应setter方法
- Method readMethod=propertyDescriptor.getReadMethod()对应getter方法
- 调用 writeMethod.invoke(Object obj, Object… args),对bean的属性进行赋值
PropertyDescriptor[] propertyDescriptorList = Introspector.getBeanInfo(currentClass).getPropertyDescriptors();
- 完全证代码实现
/**
* @Author lts
* @Date 2021/1/18 19:56
* @Description
*/
public class BeanUtilsMy {
static final Map<String, Map<String, Method>> classWriteMethodCache = new HashMap<>();
static final Map<String, Map<String, Method>> classReadMethodCache = new HashMap<>();
/**
* 方法描述: 设置obj中属性名为name的字段值为value
*
* @param obj
* @param name
* @param value
* @return
* @author: lts
* @date: 2021-01-21
*/
public static void copyProperty(Object obj, String name, Object value) {
if (name == null || name.equals("")) {
return;
}
String className = obj.getClass().getName();
Map<String, Method> writeMethodCache;
if (!classWriteMethodCache.containsKey(className)) {
writeMethodCache = getWriteMethod(obj.getClass());
classWriteMethodCache.put(className, writeMethodCache);
} else {
writeMethodCache = classWriteMethodCache.get(className);
}
// cacheKey必须要加上value Type
Method method = writeMethodCache.get(name);
if (method != null) {
try {
method.invoke(obj, value);
} catch (Exception e) {
}
}
}
public static Map<String, Method> getWriteMethod(final Class cls) {
Map<String, Method> rsMap = new HashMap<>();
try {
Class<?> currentClass = cls;
while (currentClass != null) {
//Introspector.getBeanInfo 获取的数据仅包含本类的信息,不包含继承的父类数据,所以后面需要递归取父类数据
BeanInfo sourceBeanInfo = Introspector.getBeanInfo(currentClass);
PropertyDescriptor[] propertyDescriptorList = sourceBeanInfo.getPropertyDescriptors();
for (PropertyDescriptor item : propertyDescriptorList) {
Method m = item.getWriteMethod();
if (!rsMap.containsKey(item.getName()) && m != null && Modifier.isPublic(m.getModifiers())) {
rsMap.put(item.getName(), m);
}
}
//取父类方法
currentClass = currentClass.getSuperclass();
}
} catch (Exception e) {
}
return rsMap;
}
}
- 性能对比
- apache的属性copy很慢,不推荐使用,自己实现的BeanUtilsMy.copyProperty性能很接近直接java set(当然如果少了对属性的循环,直接使用java对象的set方法性能会好很多)
工具类 | 数量 | 耗时(ms) |
---|---|---|
org.apache.commons.beanutils.BeanUtils.copyProperty | 10w | 500 |
BeanUtilsMy.copyProperty | 10w | 18 |
java set | 10w | 23 |
org.apache.commons.beanutils.BeanUtils.copyProperty | 100w | 3434 |
BeanUtilsMy.copyProperty | 100w | 188 |
java set | 100w | 56 |
- 测试代码:
public static void copyBeanProperties(int loopNum) {
//
try {
Map<String, String> fieldValueMap = new HashMap<>();
fieldValueMap.put("f1", "h~1");
fieldValueMap.put("f2", "h~2");
fieldValueMap.put("f3", "h~3");
fieldValueMap.put("f4", "h~4");
BeanDemo beanDemo = new BeanDemo();
StopWatch sw = new StopWatch("设置Bean属性,loopNum:" + loopNum);
sw.start("org.apache.commons.beanutils.BeanUtils.copyProperty");
for (int i = 0; i < loopNum; i++) {
Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
org.apache.commons.beanutils.BeanUtils.copyProperty(beanDemo, entry.getKey(), entry.getValue());
}
}
sw.stop();
beanDemo = new BeanDemo();
sw.start("BeanUtilsMy.copyProperty");
for (int i = 0; i < loopNum; i++) {
Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
BeanUtilsMy.copyProperty(beanDemo, entry.getKey(), entry.getValue());
}
}
sw.stop();
beanDemo = new BeanDemo();
sw.start("java set");
for (int i = 0; i < loopNum; i++) {
Iterator<Map.Entry<String, String>> iterator = fieldValueMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
if("f1".equals(entry.getKey())){
beanDemo.setF1(entry.getValue());
}else if("f2".equals(entry.getKey())){
beanDemo.setF2(entry.getValue());
}else if("f3".equals(entry.getKey())){
beanDemo.setF3(entry.getValue());
}else if("f4".equals(entry.getKey())){
beanDemo.setF4(entry.getValue());
}
}
}
sw.stop();
System.out.println(sw.prettyPrint());
} catch (Exception e) {
e.printStackTrace();
}
}
测试结果:
StopWatch '设置Bean属性,loopNum:100000': running time (millis) = 541
-----------------------------------------
ms % Task name
-----------------------------------------
00500 092% org.apache.commons.beanutils.BeanUtils.copyProperty
00018 003% BeanUtilsMy.copyProperty
00023 004% java set
StopWatch '设置Bean属性,loopNum:1000000': running time (millis) = 3678
-----------------------------------------
ms % Task name
-----------------------------------------
03434 093% org.apache.commons.beanutils.BeanUtils.copyProperty
00188 005% BeanUtilsMy.copyProperty
00056 002% java set