背景
在我们着手一个 Java Web项目的时候,经常会遇到DO、VO、DTO对象之间的属性拷贝,若采用get、set的方法来进行赋值的话,代码会相当冗长丑陋,一般我们会采用Spring的BeanUtils类来进行属性拷贝,其基本原理就是通过Java的反射机制,下面我们来看一下源码的具体实现。
前置知识
在分析源码前,我们先温习一下以下的知识点。
java.lang.Class类
在Java中万物皆对象,而且我们在代码中写的每一个类也都是对象,是java.lang.Class类的对象。所以,每个类都有自己的实例对象,而且它们自己也都是Class类的对象。
基本类型和其对应的包装类的Class对象是不相等的,即long.class != Long.class 。
PropertyDescriptor类
Java中有strong、soft、weak、phantom四种引用类型,下面介绍一下soft引用和weak引用:
总结
在我们着手一个 Java Web项目的时候,经常会遇到DO、VO、DTO对象之间的属性拷贝,若采用get、set的方法来进行赋值的话,代码会相当冗长丑陋,一般我们会采用Spring的BeanUtils类来进行属性拷贝,其基本原理就是通过Java的反射机制,下面我们来看一下源码的具体实现。
前置知识
在分析源码前,我们先温习一下以下的知识点。
java.lang.Class类
在Java中万物皆对象,而且我们在代码中写的每一个类也都是对象,是java.lang.Class类的对象。所以,每个类都有自己的实例对象,而且它们自己也都是Class类的对象。
我们来看一下Class类的构造方法:
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
Class类的构造方法是私有的,只有JVM可以创建该类的对象,因此我们无法在代码中通过new
的方式显示声明一个Class对象。
但是,我们依然有其他方式获得Class类的对象:
1.通过类的静态成员变量
Class clazz = Test.class;
2.通过对象的getClass()方法
Class clazz = test.getClass();
3.通过Class的静态方法forName()
// forName需要传入类的全路径
Class clazz = Class.forName("destiny.iron.api.model.Test");
基本类型和包装类型
基本类型和其对应的包装类的Class对象是不相等的,即long.class != Long.class 。
PropertyDescriptor类
PropertyDescriptor类表示的是标准形式的Java Bean通过存取器(即get set方法)导出的一个属性,比如,我们可以通过以下方式,对对象的属性进行赋值:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
public static void main(String[] args) throws Exception {
Person test1 = new Person();
test1.setName("vvvv");
PropertyDescriptor pd = new PropertyDescriptor("name", test1.getClass());
Method setMethod = pd.getWriteMethod(); // 还有与Wirte方法对应的Read方法
setMethod.invoke(test1, "bbbbb");
System.out.print(test1);
}
}
引用类型
Java中有strong、soft、weak、phantom四种引用类型,下面介绍一下soft引用和weak引用:
- Soft Reference: 当对象是Soft reference可达时,向系统申请更多内存,GC不是直接回收它,而是当内存不足的时候才回收它。因此Soft reference适合用于构建一些缓存系统。
- Weak Reference: 弱引用的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次GC发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
源码分析
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
// 检查source和target对象是否为null,否则抛运行时异常
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
// 获取target对象的类信息
Class<?> actualEditable = target.getClass();
// 若editable不为null,检查target对象是否是editable类的实例,若不是则抛出运行时异常
// 这里的editable类是为了做属性拷贝时限制用的
// 若actualEditable和editable相同,则拷贝actualEditable的所有属性
// 若actualEditable是editable的子类,则只拷贝editable类中的属性
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,getPropertyDescriptors这个方法请看下方
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
// 获取该属性对应的set方法
Method writeMethod = targetPd.getWriteMethod();
// 属性的set方法存在 且 该属性不包含在忽略属性列表中
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
// 获取source类相同名字的PropertyDescriptor, getPropertyDescriptor的具体实现看下方
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
// 获取对应的get方法
Method readMethod = sourcePd.getReadMethod();
// set方法存在 且 target的set方法的入参是source的get方法返回值的父类或父接口或者类型相同
// 具体ClassUtils.isAssignable()的实现方式请看下面详解
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
//get方法是否是public的
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
//暴力反射,取消权限控制检查
readMethod.setAccessible(true);
}
//获取get方法的返回值
Object value = readMethod.invoke(source);
// 原理同上
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 将get方法的返回值 赋值给set方法作为入参
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
getPropertyDescriptors源码:
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
// CachedIntrospectionResults类是对PropertyDescriptor的一个封装实现,看forClass方法的实现
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();
}
@SuppressWarnings("unchecked")
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
// strongClassCache的声明如下:
// strongClassCache = new ConcurrentHashMap<Class<?>, CachedIntrospectionResults>(64);
// 即将Class作为key,CachedIntrospectionResults作为value的map,
// 由于线程安全的需要,使用ConcurrentHashMap作为实现
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
// 若strongClassCache中不存在,则去softClassCache去获取,softClassCache的声明如下
// softClassCache = new ConcurrentReferenceHashMap<Class<?>, CachedIntrospectionResults>(64);
// ConcurrentReferenceHashMap是Spring实现的可以指定entry引用级别的ConcurrentHashMap,默认的引用级别是soft,可以防止OOM
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
// isCacheSafe方法检查给定的beanClass是否由入参中的classloader或者此classloader的祖先加载的(双亲委派的原理)
// isClassLoaderAccepted检查加载beanClass的classloader是否在可以接受的classloader的集合中 或者是集合中classloader的祖先
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;
}
// 根据classloader的结果,将类信息加载到对应的缓存中
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
isAssignable源码:
public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
Assert.notNull(lhsType, "Left-hand side type must not be null");
Assert.notNull(rhsType, "Right-hand side type must not be null");
// 若左边类型 是右边类型的父类、父接口,或者左边类型等于右边类型
if (lhsType.isAssignableFrom(rhsType)) {
return true;
}
// 左边入参是否是基本类型
if (lhsType.isPrimitive()) {
//primitiveWrapperTypeMap是从包装类型到基本类型的map,将右边入参转化为基本类型
Class<?> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType);
if (lhsType == resolvedPrimitive) {
return true;
}
}
else {
// 将右边入参转化为包装类型
Class<?> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType);
if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) {
return true;
}
}
return false;
}
ClassUtils.isAssignable()方法扩展了Class的isAssignableFrom()方法,即将Java的基本类型和包装类型做了兼容。
总结
一个看似简单的BeanUtils工具类,其实里面包含的Java基础的知识点非常多,包括类型信息、反射、线程安全、引用类型、类加载器等。Spring的BeanUtils的实现里使用了ConcurrentHashMap作为缓存,每次去获取PropertyDescriptor时,可以直接去缓存里面获取,而不必每次都去调用native方法,所以Spring的BeanUtils的性能还是很不错的。
文章来源:https://segmentfault.com/a/1190000014833730
相关内容推荐:https://www.roncoo.com/course/list.html?courseName=spring