在日常编码中,经常会遇到DO、DTO对象之间的转换,如果对象本身的属性比较少的时候,那么我们采用硬编码手工setter也还ok,但如果对象的属性比较多的情况下,手工setter就显得又low又效率又低。这个时候我们就考虑采用一些工具类来进行对象属性的拷贝了。
我们常用的对象属性拷贝的方式有:
结论:从测试结果中很明显可以看出采用Hard Code方式进行对象属性Copy性能最佳;采用net.sf.cglib.beans.BeanCopier#copy方式进行对象属性copy性能最稳定;而org.apache.commons.beanutils.BeanUtils.copyProperties 方式在数据量大时性能下降最厉害。所以在日常编程中遇到具有较多属性的对象进行属性复制时优先考虑采用net.sf.cglib.beans.BeanCopier#copy。
以上的数据之所产生巨大差距的原因在于其实现原理与方式的不同而导致的,Hard Code直接调用getter & setter方法值,cglib采用的是字节码技术,而后三种均采用反射的方式。前两者性能优异众所周知,但为何同样采用反射的方式进行属性Copy时产生的差异如此巨大呢? 这正是本文我们想要去探究的内容。
BeanUtils 位于spring-beans模块中,暴露出静态方法copyProperties用以进行属性copy,每个copyProperties最终均调用一个私有静态方法实现属性copy:
spring.BeanUtils中对于bean的PropertyDescriptor处理以及缓存均是由CachedIntrospectionResults类来进行处理。 CacheIntrospectionResults将数据缓存在静态集合中,使用了工厂方法的设计模式,通过forClass(Class)方法暴露缓存:
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
// 从缓存中获取CachedIntrospectionResults
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
// 从缓存中获取CachedIntrospectionResults
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
// 构造CachedIntrospectionResults
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
// 选取对应的缓存
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
classCacheToUse = softClassCache;
}
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
我们可以看到此处具有两个缓存:strongClassCache与softClassCache,那他俩什么区别呢?
首先我们看他们的定义:
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache =
new ConcurrentHashMap<Class<?>, CachedIntrospectionResults>(64);
static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache =
new ConcurrentReferenceHashMap<Class<?>, CachedIntrospectionResults>(64);
可以看出来都是静态属性,类加载时就被创建的缓存实例,如果类加载器关闭,加载器对应的资源也会被释放
ConcurrentReferenceHashMap可以指定对应的引用级别,其内部采用分段锁实现,与jdk1.7的ConcurrentMap的实现原理类似。
strongClassCache中持有的缓存是强引用,而softClassCache持有的缓存是软引用 (JDK有4中引用级别,分别是强引用,软引用,弱引用以及虚引用,引用级别体现在决定GC的时候持有的实例被回收的时机)。
strongClassCache用于缓存cache-safe的bean class数据,而softClassCache用于缓存none-cache-safe bean class数据;strongClassCache中的数据与spring application的生命周期一致,而softClassCache的生命周期则不由spring进行管理,因此为了防止因classloader提前关闭导致内存泄漏,此处采用软引用进行缓存.
那什么样的数据会被cache在strongClassCache中呢?beanClass的ClassLoader与当前相同时或者与程序指定的ClassLoader相同时会被存储于strongClassCache,其余均为存储于softClassCache中。
如果从以上cache中没有拿到数据,那么会new CachedIntrospectionResults(Class),相应的调用Java Introspector的相关API均在此构造函数中:
// omit some logger code ...
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
BeanInfo beanInfo = null;
// 对一些特殊的set方法(setA(int index, Object a))或者list的set方法进行处理
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
beanInfo = beanInfoFactory.getBeanInfo(beanClass);
if (beanInfo != null) {
break;
}
}
if (beanInfo == null) {
// fall back到默认获取BeanInfo的方式
beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) : Introspector.getBeanInfo(beanClass));
}
this.beanInfo = beanInfo;
// propertyDescriptor缓存
this.propertyDescriptorCache = new LinkedHashMap<String, PropertyDescriptor>();
// 考虑到性能原因,对于每个PropertyDescriptor只处理一次
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass &&
("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
continue;
}
// 重新包装为GenericTypeAwarePropertyDescriptor
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd);
}
// 检查Java 8在接口中的默认实现方法
Class<?> clazz = beanClass;
while (clazz != null) {
Class<?>[] ifcs = clazz.getInterfaces();
for (Class<?> ifc : ifcs) {
BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO);
PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : ifcPds) {
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd);
}
}
}
clazz = clazz.getSuperclass();
}
this.typeDescriptorCache = new ConcurrentReferenceHashMap<PropertyDescriptor, TypeDescriptor>();
}
catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
}
}
这段代码主要的作用就是通过内省接口得到BeanInfo,然后将PropertyDescriptor缓存起来。具体流程:
public static boolean isCandidateWriteMethod(Method method) {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
int nParams = parameterTypes.length;
return (methodName.length() > 3 && methodName.startsWith("set") && Modifier.isPublic(method.getModifiers()) &&
(!void.class.isAssignableFrom(method.getReturnType()) || Modifier.isStatic(method.getModifiers())) &&
(nParams == 1 || (nParams == 2 && int.class == parameterTypes[0])));
}
如果一个bean中包含这么一个方法:以set开头 &&(返回值不为void || 是静态方法) && (具有一个参数 || 有两个参数其中第一个参数是int), 形如:
// void.class.isAssignableFrom(method.getReturnType()) 方法返回值不为void
public Bean setFoo(Foo foo) {
this.foo = foo;
return this;
}
public static void setFoos(Foo foo) {
Bean.foo = foo;
}
public Bean setFoos(int index, Foo foo) {
this.foos.set(index, foo);
return this;
}
Bridge Method: 桥接方法是jdk引入泛型后为了与之前的jdk版本兼容,在编译时自动生成的方法。桥接方法的字节码Flag会被标记为ACC_BRIDGE (桥接方法)和ACC_SYNTHETIC (由编译器生成)。通过Method.isBridge()来判断一个方法是否为BridgeMethod。如果一个方法覆写了泛型父类或者实现了泛型接口则会生成bridge method.
在java1.5以前,比如声明一个集合类型:
List list = new ArrayList();
那么往list中可以添加任何类型的对象,但是在从集合中获取对象时,无法确定获取到的对象是什么具体的类型,所以在1.5的时候引入了泛型,在声明集合的时候就指定集合中存放的是什么类型的对象:
List list = new ArrayList();
使用泛型之后,在获取时就不必担心类型的问题,因为泛型在编译时编译器会检查往集合中添加的对象的类型是否匹配泛型类型,如果不正确会在编译时就会发现错误,而不必等到运行时才发现错误。
因为泛型是在1.5引入的,为了向前兼容,所以会在编译时去掉泛型(泛型擦除),产生桥接方法。
桥接方法的产生:
为什么需要生成桥接方法?
Consumer 是 JDK 1.8 出现的接口方法:
public interface Consumer { void accept(T t); }
我们来实现该接口
public class StringConsumer implements Consumer { @Override public void accept(String s) { System.out.println("i consumed " + s); } }
编译源代码后,查看字节码。
我们发现:
StringConsumer 类的字节码中包含 桥接方法
tringConsumer 类的字节码中还包含我们覆写后的 public void accept(String s) 方法
从缓存中获取到了目标类的PropertyDescriptor后,就要轮询其每一个PropertyDescriptor赋值了。
赋值的过程相对比较简单一点:
至此,类的属性copy完成。
注意点
int a = 123;
Integer b = 11;
System.out.println(b.getClass().isInstance(a));//true
apache.BeanUtils是一个包含了很多静态方法的工具类,而几乎所有的静态方法均是BeanUtilsBean的单例对象提供的实现。BeanUtilsBean是进行JavaBean属性操作的入口方法,它以单实例对外提供功能。但这里有一个不同于普通单例的地方:不同的类加载器拥有不同的实例,每一个类加载器只有一个实例 ,所以这里的单例其实是一个伪单例pseudo-singletion 。
// ContextClassLoaderLocal对象管理了BeanUtilsBean的所有实例
private static final ContextClassLoaderLocal<BeanUtilsBean>
BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal<BeanUtilsBean>() {
@Override
protected BeanUtilsBean initialValue() {
return new BeanUtilsBean();
}
};
public static BeanUtilsBean getInstance() {
return BEANS_BY_CLASSLOADER.get();
}
// {@link ContextClassLoaderLocal#get}
public synchronized T get() {
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(); // 初始化BeanUtilsBean,即 new BeanUtilsBean();
valueByClassLoader.put(contextClassLoader, value);
}
return value;
}
} catch (final SecurityException e) { /* SWALLOW - should we log this? */ }
if (!globalValueInitialized) {
globalValue = initialValue();
globalValueInitialized = true;
}
return globalValue;
}
当获取到了BeanUtilsBean的实例之后,接下来就是我们进行对象属性拷贝的时候了
// omit exception
public static void copyProperties(final Object dest, final Object orig){
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
在copyProperties方法中,针对原始对象的类型分别采用了不同的逻辑:
public void copyProperties(final Object dest, final Object orig) {
// omit some code (省略一部分代码) ...
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
}
}
}
}
final PropertyDescriptor[] origDescriptors =
getPropertyUtils().getPropertyDescriptors(orig);
获取PropertyDescriptor委托给PropertyUtilsBean对象来实现:
public BeanUtilsBean() {
this(new ConvertUtilsBean(), new PropertyUtilsBean());
}
PropertyUtilsBean 是用于使用java 反射API来操作Java Bean上getter和setter方法的,此类中的代码原先是位于BeanUtilsBean中的,但是考虑到代码量的原因进行了分离(Much of this code was originally included in BeanUtils, but has been separated because of the volume of code involved)。
在PropertyUtilsBean中,每个Bean的PropertyDescriptor 会存储于BeanIntrospectionData对象中,当每次需要获取PropertyDescriptor时,会先从cahche中获取BeanIntrospectionData ;如果不存在,则通过内省API获取BeanIntrospectionData并将其置于缓存中:
private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {
// omit some check code ...
BeanIntrospectionData data = descriptorsCache.get(beanClass);
if (data == null) {
data = fetchIntrospectionData(beanClass);
descriptorsCache.put(beanClass, data);
}
return data;
}
private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {
final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
for (final BeanIntrospector bi : introspectors) {
try {
bi.introspect(ictx);
} catch (final IntrospectionException iex) {
log.error("Exception during introspection", iex);
}
}
return new BeanIntrospectionData(ictx.getPropertyDescriptors());
}
在fetchIntrospectionData()方法中,通过内置的内省器DefaultBeanIntrospector使用java的内省API将获取的信息传递给DefaultIntrospectionContext, 在通过DefaultIntrospectionContext构造BeanIntrospectionData。DefaultBeanIntrospector具体的代码:
public void introspect(final IntrospectionContext icontext) {
BeanInfo beanInfo = null;
try {
// JAVA 的 Instrospector
beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
} catch (final IntrospectionException e) {
return;
}
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
if (descriptors == null) {
descriptors = new PropertyDescriptor[0];
}
// 解决IndexedPropertyDescriptor在不同版本的JDK下的差异
handleIndexedPropertyDescriptors(icontext.getTargetClass(), descriptors);
icontext.addPropertyDescriptors(descriptors);
}
Introspector java.beans下的包
** Introspector 是操作 javaBean 的 API,用来访问某个属性的 getter/setter 方法。**
对于一个标准的 javaBean 来说,它包括属性、get 方法和 set 方法,这是一个约定俗成的规范。为此 sun 提供了 Introspector 工具包,来使开发者更好或者更灵活的操作 javaBean。
要进行属性copy,那么首先得确保原始对象的属性可读、目标对象属性可写。在PropertyUtilsBean中通过isWriteable(); isReadable()方法,这两个方法看上去比较长,我们把关于exception的处理省略掉拿出来看下:
public boolean isReadable(Object bean, String name) {
// Omit Validate method parameters
// Resolve nested references, 解析内嵌的属性,形如 student.name
while (resolver.hasNested(name)) {
final String next = resolver.next(name);
Object nestedBean = nestedBean = getProperty(bean, next);
if (nestedBean == null) {
throw new NestedNullException("Null property value for);
}
bean = nestedBean;
name = resolver.remove(name);
}
// Remove any subscript from the final name value, 在最终的方法名中移除所有的下标
name = resolver.getProperty(name);
if (bean instanceof WrapDynaBean) {
bean = ((WrapDynaBean)bean).getInstance();
}
if (bean instanceof DynaBean) {
// All DynaBean properties are readable,所有DynaBean的属性均是可读的
return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
} else {
final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
if (desc != null) {
Method readMethod = getReadMethod(bean.getClass(), desc);
if (readMethod == null) {
if (desc instanceof IndexedPropertyDescriptor) {
readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
} else if (desc instanceof MappedPropertyDescriptor) {
readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
}
readMethod = MethodUtils.getAccessibleMethod(bean.getClass(), readMethod);
}
return (readMethod != null);
} else {
return (false);
}
}
}
从以上代码我们可以得知,每个属性的可读、可写在每次使用时都需要获取Method,然后进行判断,并且还需要处理DynaBean、Nested的逻辑;当我们进行批量的属性copy时,依然需要执行以上步骤,并未将method的判断结果进行缓存,这也是其相比于其他的jar低效的原因.
我们还是省略掉其中的有效性判断和异常的代码:
public Object getSimpleProperty(final Object bean, final String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
// omit check null code ...
// 校验属性
if (resolver.hasNested(name)) {
throw new IllegalArgumentException
("Nested property names are not allowed: Property '" +
name + "' on bean class '" + bean.getClass() + "'");
} else if (resolver.isIndexed(name)) {
throw new IllegalArgumentException
("Indexed property names are not allowed: Property '" +
name + "' on bean class '" + bean.getClass() + "'");
} else if (resolver.isMapped(name)) {
throw new IllegalArgumentException
("Mapped property names are not allowed: Property '" +
name + "' on bean class '" + bean.getClass() + "'");
}
// DynaBean的特殊逻辑
if (bean instanceof DynaBean) {
final DynaProperty descriptor =
((DynaBean) bean).getDynaClass().getDynaProperty(name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" +
name + "' on dynaclass '" +
((DynaBean) bean).getDynaClass() + "'" );
}
return (((DynaBean) bean).get(name));
}
final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" +
name + "' on class '" + bean.getClass() + "'" );
}
// 获取getter方法
final Method readMethod = getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name +
"' has no getter method in class '" + bean.getClass() + "'");
}
// 调用getter方法读取值
final Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return (value);
}
以上是读取属性值的方法。 读取到属性值之后,就是设置值到目标bean上了。 在BeanUtilsBean的实现中,又重复的处理了属性的内嵌逻辑与DynaBean逻辑,最终获取到其setter方法将值赋予目标Bean.
protected Object convert(final Object value, final Class<?> type) {
final Converter converter = getConvertUtils().lookup(type);
if (converter != null) {
log.trace(" USING CONVERTER " + converter);
return converter.convert(type, value);
} else {
return value;
}
}
可见,对于字段名称相同,类型却不同,提供了类型的转换方案
看下这个Convert接口
public interface Converter {
public <T> T convert(Class<T> type, Object value);
}
重要的实现类
AbstractConverter
看些类型转换的过程
private Object convertForCopy(final Object value, final Class<?> type) {
return (value != null) ? convert(value, type) : value;
}
如果为空,不再进行类型转换。不为空,进入类型convert
public <T> T convert(final Class<T> type, Object value) {
if (type == null) {
return convertToDefaultType(type, value);
}
Class<?> sourceType = value == null ? null : value.getClass();
final Class<T> targetType = ConvertUtils.primitiveToWrapper(type);
if (log().isDebugEnabled()) {
log().debug("Converting"
+ (value == null ? "" : " '" + toString(sourceType) + "'")
+ " value '" + value + "' to type '" + toString(targetType) + "'");
}
value = convertArray(value);
// Missing Value
if (value == null) {
return handleMissing(targetType);
}
sourceType = value.getClass();
try {
// Convert --> String
if (targetType.equals(String.class)) {
return targetType.cast(convertToString(value));
// No conversion necessary
} else if (targetType.equals(sourceType)) {
if (log().isDebugEnabled()) {
log().debug(" No conversion required, value is already a "
+ toString(targetType));
}
return targetType.cast(value);
// Convert --> Type
} else {
final Object result = convertToType(targetType, value);
if (log().isDebugEnabled()) {
log().debug(" Converted to " + toString(targetType) +
" value '" + result + "'");
}
return targetType.cast(result);
}
} catch (final Throwable t) {
return handleError(targetType, value, t);
}
}
首先进入抽象类的convert
首先处理了数组情况,convertArray
convertArray有两种实现
protected Object convertArray(final Object value) {
if (value == null) {
return null;
}
if (value.getClass().isArray()) {
if (Array.getLength(value) > 0) {
return Array.get(value, 0);
} else {
return null;
}
}
if (value instanceof Collection) {
final Collection<?> collection = (Collection<?>)value;
if (collection.size() > 0) {
return collection.iterator().next();
} else {
return null;
}
}
return value;
}
@Override
protected Object convertArray(final Object value) {
return value;
}
使用的是ConvertUtilsBeans
public void register(final boolean throwException, final boolean defaultNull, final int defaultArraySize) {
//基本类型
registerPrimitives(throwException);
//数字处理类型
registerStandard(throwException, defaultNull);
//日期等类型
registerOther(throwException);
//数组类型
registerArrays(throwException, defaultArraySize);
}
可以看出来注册了很多convert
后面继续,如果目标类型是String,直接调用toString
目标和原类型相同,无需处理
如果不同那么需要调用对应的converType方法进行转换,得到结果
那么可以转换那些类型
private void registerArrays(final boolean throwException, final int defaultArraySize) {
// Primitives
registerArrayConverter(Boolean.TYPE, new BooleanConverter(), throwException, defaultArraySize);
registerArrayConverter(Byte.TYPE, new ByteConverter(), throwException, defaultArraySize);
registerArrayConverter(Character.TYPE, new CharacterConverter(), throwException, defaultArraySize);
registerArrayConverter(Double.TYPE, new DoubleConverter(), throwException, defaultArraySize);
registerArrayConverter(Float.TYPE, new FloatConverter(), throwException, defaultArraySize);
registerArrayConverter(Integer.TYPE, new IntegerConverter(), throwException, defaultArraySize);
registerArrayConverter(Long.TYPE, new LongConverter(), throwException, defaultArraySize);
registerArrayConverter(Short.TYPE, new ShortConverter(), throwException, defaultArraySize);
// Standard
registerArrayConverter(BigDecimal.class, new BigDecimalConverter(), throwException, defaultArraySize);
registerArrayConverter(BigInteger.class, new BigIntegerConverter(), throwException, defaultArraySize);
registerArrayConverter(Boolean.class, new BooleanConverter(), throwException, defaultArraySize);
registerArrayConverter(Byte.class, new ByteConverter(), throwException, defaultArraySize);
registerArrayConverter(Character.class, new CharacterConverter(), throwException, defaultArraySize);
registerArrayConverter(Double.class, new DoubleConverter(), throwException, defaultArraySize);
registerArrayConverter(Float.class, new FloatConverter(), throwException, defaultArraySize);
registerArrayConverter(Integer.class, new IntegerConverter(), throwException, defaultArraySize);
registerArrayConverter(Long.class, new LongConverter(), throwException, defaultArraySize);
registerArrayConverter(Short.class, new ShortConverter(), throwException, defaultArraySize);
registerArrayConverter(String.class, new StringConverter(), throwException, defaultArraySize);
// Other
registerArrayConverter(Class.class, new ClassConverter(), throwException, defaultArraySize);
registerArrayConverter(java.util.Date.class, new DateConverter(), throwException, defaultArraySize);
registerArrayConverter(Calendar.class, new DateConverter(), throwException, defaultArraySize);
registerArrayConverter(File.class, new FileConverter(), throwException, defaultArraySize);
registerArrayConverter(java.sql.Date.class, new SqlDateConverter(), throwException, defaultArraySize);
registerArrayConverter(java.sql.Time.class, new SqlTimeConverter(), throwException, defaultArraySize);
registerArrayConverter(Timestamp.class, new SqlTimestampConverter(), throwException, defaultArraySize);
registerArrayConverter(URL.class, new URLConverter(), throwException, defaultArraySize);
}
对于以上类型的数组作为目标类型时如果和源类型不同,同样可以处理
那么对类型转换进行自定义吗?比如实现一个List的转换,当然是可以的,例如默认没有对list进行的转换,可以,自定义一个进行处理,这个实现比较简单
BeanUtilsBean.getInstance().getConvertUtils().register(
new Converter() {
@Override
public Object convert(Class type, Object value) {
return Arrays.asList(value);
}
},
List.class
);
看起来dynamicBean和map并没什么区别,那么为什么还需要使用
ps:在上面的代码中用到了org.apache.commons.beanutils.DynaProperty,这个类很重要,要创建一个真正有用的BasicDynaClass,肯定需要使用DynaProperty,但是用法非常简单。
总结一下:要使用BasicDynaBean,就首先要构造一个BasicDynaClass来包含期望的数据结构。
LazyDynaBean和BasicDynaBean相比,LazyDynaBean不需要首先创建一个包含期望的数据结构的DynaClass,就能向LazyDynaBean中填充我们任意想填充的数据。LazyDynaBean内部会根据我们填充进的数据(即使是一个map中的一个key-value pair,LazyDynaBean也会创建一个map的metadata),创建metadata的。
另外,在我练习的过程中,发现向LazyDynaBean中填充一个含indexed的数据时,LazyDynaBean创建的metadata的数据类型是list。
以一个存在的Javabean为基础,创建一个WrapDynaBean,可以用WrapDynaBean中的setter和getter方法访问这个基础javabean。说白了就是用一个业已存在的javabean构造一个WrapDynaBean并初始化数据,然后我们就认为它是WrapDynaBean,可以向其中添加新的数据结构。注意,这个不是decorator模式的应用,decorator是对已经存在的组件添加功能,而WrapDynaBean是添加数据结构,是有区别的。
可以看出来,dynaBean对数据结构做了要求,也就是说,dynaBean种的数据可以做到总是符合预期
例如
LazyDynaClass dynaClass = new LazyDynaClass();
dynaClass.getDynaProperty("list").getContentType();
(dynaClass).add("list",List.class);
LazyDynaBean dynaBean = new LazyDynaBean(dynaClass);
dynaBean.set("list",111);
会抛出ConversionException
org.apache.commons.beanutils.ConversionException: Cannot assign value of type 'java.lang.Integer' to property 'list' of type 'java.util.List'
也因此,apach的beanUtil在copy时目标为map时,不会拷贝,知会拷贝DynaBean,因为dynaBean对类型有预期,可以进行响应类型的处理。可以作为copy的目标实例
使用示例
Person1 p1 = new Person1();
Person2 p2 = new Person2();
// 给p1赋值
p1.setName("鲁智深");
p1.setBirthday(new Date(834012360000L));
BeanCopier copier = BeanCopier.create(Person1.class, Person2.class, true);
// 根据需求编写的DateConverter类, 需要实现Converter接口.
copier.copy(p1, p2, new DateConverter());
注意到:
第三个参数userConverter,是否开启Convert,默认BeanCopier只会做同名,同类型属性的copier,否则就会报错.
举个栗子:在两对象复制过程中,难免会有某些字段属性名相同,但类型不同的情况,例如 Date time,String time, long time.这三种,在转换的时候如何处理。就需要自己写方法对相应的类型进行修改.
简单案例:
public class Test02 {
public static void main(String[] args) {
Person1 p1 = new Person1();
Person2 p2 = new Person2();
// 给p1赋值
p1.setName("鲁智深");
p1.setBirthday(new Date(834012360000L));
BeanCopier copier = BeanCopier.create(Person1.class, Person2.class, true);
// 根据需求编写的DateConverter类, 需要实现Converter接口.
copier.copy(p1, p2, new DateConverter());
System.out.println(p1);
System.out.println(p2);
}
}
----------
import org.springframework.cglib.core.Converter;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateConverter implements Converter {
@Override
public Object convert(Object value, Class target, Object context) {
// a.isAssignableFrom(b), 查资料:即a是b的父类或接口 || a和b为同一个类或同一个接口 则返回true.
// SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value), 设置pattern并转换时间格式.
return value.getClass().isAssignableFrom(Date.class) ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value) : value;
}
}
----------
import lombok.Data;
@Data
public class Person1 {
private String name;
private Date birthday;
}
----------
import lombok.Data;
@Data
public class Person2 {
private String name;
private String birthday;
}
实现原理,利用cglib生成字节码文件,查看其生成字节码文件就知道了
不适用自定义转换器
public class Object$$BeanCopierByCGLIB$$cdf980b3 extends BeanCopier {
public Object$$BeanCopierByCGLIB$$cdf980b3() {
}
public void copy(Object var1, Object var2, Converter var3) {
Person2 var10000 = (Person2)var2;
Person1 var10001 = (Person1)var1;
var10000.setAaa(((Person1)var1).getAaa());
var10000.setName(var10001.getName());
}
}
使用自定义转换器
public class Object$$BeanCopierByCGLIB$$cdf980b2 extends BeanCopier {
private static final Class CGLIB$load_class$java$2Elang$2EInteger;
private static final Class CGLIB$load_class$java$2Elang$2EString;
public Object$$BeanCopierByCGLIB$$cdf980b2() {
}
public void copy(Object var1, Object var2, Converter var3) {
Person2 var4 = (Person2)var2;
Person1 var5 = (Person1)var1;
var4.setAaa((Integer)var3.convert(var5.getAaa(), CGLIB$load_class$java$2Elang$2EInteger, "setAaa"));
var4.setBirthday((String)var3.convert(var5.getBirthday(), CGLIB$load_class$java$2Elang$2EString, "setBirthday"));
var4.setName((String)var3.convert(var5.getName(), CGLIB$load_class$java$2Elang$2EString, "setName"));
}
static void CGLIB$STATICHOOK1() {
CGLIB$load_class$java$2Elang$2EInteger = Class.forName("java.lang.Integer");
CGLIB$load_class$java$2Elang$2EString = Class.forName("java.lang.String");
}
static {
CGLIB$STATICHOOK1();
}
}
可见cglib直接生成了字节码进行copy操作,但是生成字节码比较耗时因此,cglib对生成的copy对象进行缓存,再次调用create,直接从缓冲中取出
protected Object create(Object key) {
Class gen = null;
synchronized (source) {
ClassLoader loader = getClassLoader();
Map cache2 = null;
cache2 = (Map) source.cache.get(loader);
/** 1.尝试加载缓存 **/
// 如果缓存不存在,则新建空的缓存
if (cache2 == null) {
cache2 = new HashMap();
cache2.put(NAME_KEY, new HashSet()); // NAME_KEY对应的Set集合用于去重
source.cache.put(loader, cache2);
}
// 如果缓存存在,且要求使用缓存
else if (useCache) {
// 通过key获取缓存中的生成类(拿到的是引用[WeakReference],调用ref.get()拿到类本身)
Reference ref = (Reference) cache2.get(key);
gen = (Class) ((ref == null) ? null : ref.get());
}
this.key = key;
/** 2.如果不能从缓存中查找到生成类,则新建类 **/
if (gen == null) {
// strategy.generate中调用了子类里的generateClass函数
// 并返回生成的字节码
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
// 将className放入NAME_KEY对应的Set中
getClassNameCache(loader).add(className);
// 根据返回的字节码生成类
gen = ReflectUtils.defineClass(className, b, loader);
}
if (useCache) {
// 在缓存中放入新生成的类
cache2.put(key, new WeakReference(gen));
}
/** 3.根据生成类,创建实例并返回 **/
return firstInstance(gen);
}
/** 3.根据生成类,创建实例并返回 **/
return firstInstance(gen);
}
结合代码,分析下具体流程。
效率堪比硬编码
MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射类似内容。
与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:
maven,需要和lombok一起使用
<!--mapStruct依赖 高性能对象映射-->
<!--mapstruct核心-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.0.Beta1</version>
</dependency>
<!--mapstruct编译-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.0.Beta1</version>
</dependency>
使用实例
@Data
public class Person {
String describe;
private String id;
private String name;
private int age;
private BigDecimal source;
private double height;
private Date createTime;
}
@Data
public class PersonDTO {
String describe;
private Long id;
private String personName;
private String age;
private String source;
private String height;
}
// mapper
@Mapper
public interface PersonMapper {
PersonMapper INSTANCT = Mappers.getMapper(PersonMapper.class);
@Mapping(target = "name", source = "personName")
@Mapping(target = "id", ignore = true) // 忽略id,不进行映射
PersonDTO conver(Person person);
}
编译后自动生成转换类
public class PersonMapperImpl implements PersonMapper {
public PersonMapperImpl() {
}
public PersonDTO conver(Person person) {
if (person == null) {
return null;
} else {
PersonDTO personDTO = new PersonDTO();
personDTO.setDescribe(person.getDescribe());
if (person.getId() != null) {
personDTO.setId(Long.parseLong(person.getId()));
}
personDTO.setPersonName(person.getName());
personDTO.setAge(String.valueOf(person.getAge()));
if (person.getSource() != null) {
personDTO.setSource(person.getSource().toString());
}
personDTO.setHeight(String.valueOf(person.getHeight()));
return personDTO;
}
}
}
调用
Person person = new Person();
person.setDescribe("测试");
person.setAge(18);
person.setName("张三");
person.setHeight(170.5);
person.setSource(new BigDecimal("100"));
PersonDTO dto = PersonMapper.INSTANCT.conver(person);
System.out.println(dto);