最近在接口重构,因此涉及解耦,但是目前业务对象字段大多相同,因此为了节省时间,考虑使用BeanUtils.copyProperties减轻工作量,因此了解了下此方法。
大多数文章讲的是BeanUtils.copyProperties方法会拷贝相同字段属性和类型的数据,但此情况是在用户遵循Java Bean规范的前提下,但是如果用户不遵循Java Bean命名规范,或者故意利用BeanUtils.copyProperties的原理,就可以达到不一样的映射关系。
直接看源码BeanUtils.copyProperties(Object source, Object target)方法
public static void copyProperties(Object source, Object target) throws BeansException {
//第一个参数为源数据,第二个参数为拷贝后的数据
copyProperties(source, target, null, (String[]) null);
}
继续看下去
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
//source和target必须自己生成
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
//如果指定了editable,则会判断target是否为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;
}
//获取Class的get、set方法
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
//下面的方法其实就是获取readMethod和writeMethod,即getXX方法和setXX方法,先getXX值,再setXX。
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
//判断该属性是否在不被copy的属性集合中
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
//判断目标对象的set方法所需的参数类型和源对象get方法的返回类型是否一致
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
//如果get方法不是public,则取消访问检查,允许调用方法。
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
//如果set方法不是public,则取消访问检查,允许调用方法。
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);
}
}
}
}
}
}
此处简单讲解下PropertyDescriptor ,其实就是读取一个类的setXX方法和getXX方法,具体可以看这篇博客 。因此实际上BeanUtils的方法是根据比较源Class和目标Class的get和set方法。因此以下两个类也是能完成拷贝的。
public class BeanOne {
private String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
public class BeanTwo {
private String test2;
public String getTest() {
return test2;
}
public void setTest(String test2) {
this.test2 = test2;
}
}
此时可以将test拷贝到test2,也可以将test2拷贝到test,但是这其实违反了Java Bean的规范的。
我们再看下Spring是如何获取PropertyDescriptor的,实际上是通过PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable)获取的。
继续看源码:
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();
}
继续看下去
//简单来说就是该类会缓存一份,如果没有就构造。
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
//关键是这里,results的构造。
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);
}
继续看
在这里插入代码片
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
//beanInfo对象即为BeanInfo,JavaBean内省,即上述引用博客中提到的,具体可以看上面博客。
//
this.beanInfo = getBeanInfo(beanClass);
if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
}
this.propertyDescriptorCache = new LinkedHashMap<>();
// This call is slow so we do it once.
//其实就是通过beanInfo.getPropertyDescriptors方法获取
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass &&
("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
// Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("Found bean property '" + pd.getName() + "'" +
(pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") +
(pd.getPropertyEditorClass() != null ?
"; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
}
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd);
}
// Explicitly check implemented interfaces for setter/getter methods as well,
// in particular for Java 8 default methods...
Class<?> currClass = beanClass;
while (currClass != null && currClass != Object.class) {
introspectInterfaces(beanClass, currClass);
currClass = currClass.getSuperclass();
}
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
}
catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
}
}
接下来我们看beanInfo怎么获得,因此BeanInfo是接口,需要有具体实现。继续看 this.beanInfo = getBeanInfo(beanClass);
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
//即遍历beanInfoFactories,如果有对应的能够加载该类的,则返回BeanInfo。
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
if (beanInfo != null) {
return beanInfo;
}
}
return (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
}
再来看beanInfoFactory
//即读取spring.factories的BeanInfoFactory的实现类
private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
点击看spring-beans的jar包下的META-INF中spring.factories可知,即获取到的是ExtendedBeanInfoFactory
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
观看上述对应的源码
//即实际上还是调用JAVA BEAN 内省工具Introspector,相关描述在上述引用的博客中有提到。
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return (supports(beanClass) ? new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null);
}
总结: BeanUtils.copyProperties是浅拷贝的一种,通过调用set和get方法实现拷贝,他们的拷贝并不是根据字段名和字段类型来判断的,而是根据set、get名和属性判断。