Springframework的BeanUtils.copyProperties原理源码浅层解析

新手原创,不喜轻喷,欢迎交流。       

       最近项目交付了,想着项目中遇到的一个问题,在传递返回的包装类给app端的时候有的字段位是null,如果这个字段app端需要其中的值,如果获取的值是null的时候会导致app的闪退,为了解决这个问题,我不得不把所有需要传递给app端且有可能为空的字段一个一个赋“”或者0等,为了简便,我把返回的包装类的数据类型只保留String和Integer.写出了以下方法:

@SuppressWarnings("unused")
public class ParameterInit {
public static void inChange2(Object o) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
Class c = o.getClass();                //获取要返回的类的class
Method[] methods = c.getMethods();                     //获取其中的方法(自定义的方法都是public)
for (Method m : methods) {
String methodName = m.getName();                //获取方法名
if(methodName.startsWith("set")) {                    //判断方法前缀(整个操作只有set/get有关)
String getMethod = methodName.substring(3);        //截取除set外的其余部分
Method method = c.getMethod("get"+getMethod );       //拼成get就变成了这个属性的get方法
Object invoke = method.invoke(o);             //因为get方法是没有参数的,所以直接传obj进去运行方法

if( invoke == null ) {                //判断当前方法的返回值

Type[] genericParameterTypes = m.getGenericParameterTypes();    //获取set方法的参数类型

if( genericParameterTypes[0] == String.class) {            //匹配参数类型
m.invoke(o, "");                        //如果是String类型就赋值“”
}else if( genericParameterTypes[0] == Integer.class) {    
m.invoke(o, 0);                        //如果是Integer类型就赋值0
}
}
}
}
System.out.println(o.toString());   //测试用,打印看是否所有字段都不为null
}

}

注:以上操作的参数Object是经过springframework的BeanUtils.copyProperties赋值过后的类

做完这些之后高兴的和领导说了一下我做了一个工具类,然后领导说就这个问题把copyProperties研究一下,做一个类似的吧

下面就说说对copyProperties的研究心得,做为一个新手,写的不好大家不要喷,谢谢

先说一下spring源在下载,用git下的方式是

git clone https://github.com/spring-projects/spring-framework.git

spring源码是用gradle写的,所以需要用gradle编译才能放到eclipse中,gradle安装这个大家百度吧

然后用gradle编译的命令是 gradle cleanidea eclipse

以下是copyProperties的源码

        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 ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

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);
}
}
}
}
}
}

首先Assert.notNull这是spring-core包中的一个类,用于判断对象是否为null,为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;
}

这一段是获取目标bean的class和对第三个参数的判断,第三个参数字面意思是editable是target的实例,不是就抛异常

平时没用过,这次没有深入研究、

  PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);

getPropertyDescriptors是BeanUtils中的另一个静态方法

public static PropertyDescriptor[] getPropertyDescriptors(Class clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();

}

CachedIntrospectionResults.forClass调用的是CachedIntrospectionResults类的方法

        /**
* Create CachedIntrospectionResults for the given bean class.
* @param beanClass the bean class to analyze
* @return the corresponding CachedIntrospectionResults
* @throws BeansException in case of introspection failure
*/
@SuppressWarnings("unchecked")
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 = new CachedIntrospectionResults(beanClass);
ConcurrentMap, 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);
}

CachedIntrospectionResults.forClass这个方法我找了很多资料都没有很明白,综合这些资料和这个类上面的注解,初步了解到这个类是一个”自省缓存结果集“

补充一些资料说明就是,这里copyProperties用到的是javaBean中的自省,自省和反射有什么区别呢?

====================================================================================

摘自    https://blog.csdn.net/zhanjixun/article/details/68256724

  • 反射
反射就是运行时获取一个类的所有信息,可以获取到.class的任何定义的信息(包括成员 变量,成员方法,构造器等)
可以操纵类的字段、方法、构造器等部分。
  • 内省
内省基于反射实现,主要用于操作JavaBean,通过内省 可以获取bean的getter/setter

生活中 反射就像我们照镜子,照镜子时候 你的所有信息会毫无出错毫无保留的反射到镜子中,而java中反射就像是运行时用一把镜子去照.class字节码 将这个类的所有信息照出来,‘照’出的结果是客观的,是正确的;
内省就像我们反省自己,通常我们是针对犯错而进行反省,根据所犯错误反省总结出结论,这个结论是主观的,不一定正确的,有时候你觉得你自己做错了,但可能事实上自己无可厚非。java中内省,是针对javaBean进行的,目的是为了找出bean的getter和setter以便操作这个bean。只要看到有getter或者setter 就认为这个类有那么一个字段,比如看到getName() 内省就会认为这个类中有name字段,但事实上并不一定会有name;
=======================================================================================


而自省技术会用到classLoader类加载器,当使用JavaBean的内省时,使用Introspector,jdk会自动缓存内省的信息(BeanInfo),这一点可以理解,因为内省通过反射的代价是高昂的。当ClassLoader关闭的时候,Introspector的缓存持有BeanInfo的信息,而BeanInfo持有Class的强引用,这将导致ClassLoader和它引用的Class等对象不能被垃圾收集器回收,因此在关闭前,需要手工清除Introspector中的缓存,调用Introspector.flushFromCaches。
虽然在copyProperties中并没有调用Introspector.flushFromCaches,但是spring框架有一个listen是用来监听这些关闭的classLoader的。
再回到getPropertyDescriptors方法上,这个方法的作用就是内省获取javaBean中的字段,通过获取强引用或者软引用或者新建立一个安全的缓存内省,最后返回的就是javaBean中的有get/set方法的字段。类似于class.getFields

  List ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

这里分析的是忽略的字段,就是那些你不想操作的字段!

  for (PropertyDescriptor targetPd : targetPds) {

        //获取字段的写方法,--set

Method writeMethod = targetPd.getWriteMethod();

        //如果方法不为空,且没有忽略

if (writeMethod != null && (ignoreList == null || (!ignoreList.contains(targetPd.getName())))) {

        //获取原对象的字段

PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());

if (sourcePd != null) {

                //如果原对象的字段是存在的,获取原对象的这个字段的get方法

Method readMethod = sourcePd.getReadMethod();

if (readMethod != null &&

                //判断源对象的get的返回值类型和目标对象的set参数是否一致

ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {

try {

                        //如果类型一致再判断方法是不是public修饰的

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);

这中间还穿插了好多个方法,没有详细贴上来,在文章最后我会贴一张大图,有兴趣的可以看看

一段代码跑下来感觉脑细胞死了不少,似乎头发掉了几根。。。好复杂的样子,所以我决定使用普通反射,代码如下:

基于目标类中的字段全是String类型的来做的

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import com.financial.core.SuperStringUtils;
import com.financial.core.date.DateUtil;
import com.financial.core.exception.ServiceException;

public class BeanCopyUtils {

public static void copyProperties(Object source,Object target) {
try {
copyProperties(source, target,(String[]) null);
} catch (ServiceException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void copyProperties(Object source,Object target,String... ignoreProperties) throws ServiceException, Exception {
if(SuperStringUtils.isBlank(source) || SuperStringUtils.isBlank(target)) {
throw new ServiceException("500","源类或目标类不能为空");
}
Class targetClass = target.getClass();
Class sourceClass = source.getClass();
Field[] targetFields = targetClass.getDeclaredFields();
Field[] sourceFields = sourceClass.getDeclaredFields();
ArrayList fieldList = change(sourceFields);
for (Field field : targetFields) {
String targetMethod = obtainSetMethod(field);
Method method = targetClass.getMethod(targetMethod, String.class);
//如果源类中有这个字段,赋值源类中的值给目标类
if(checkField(field,fieldList)){

Method fieldMethod = sourceClass.getMethod(obtainGetMethod(field)); 

                                 //获取源对象中get方法的返回值

Object sourceResult = fieldMethod.invoke(source);
if(sourceResult != null ) {
if(sourceResult.getClass() == java.util.Date.class){
method.invoke(target, DateUtil.formatDate((java.util.Date)sourceResult));
}else{
method.invoke(target, sourceResult+"");
}
}else {
method.invoke(target, "");
}
}else {
//如果源类中没有这个字段,则把目标类中的值赋为""或者0
method.invoke(target, "");
}
}
}
//将字段数组转换为StringList
private static ArrayList change(Field[] sourceFields) {
ArrayList fieldList = new ArrayList<>();
for(int i = 0;i fieldList.add(sourceFields[i].getName());
}
return fieldList;
}
//比较源类中是否含有这个字段
private static Boolean checkField(Field targetField,ArrayList fieldList) {
if(fieldList.contains(targetField.getName())) {     //很遗憾不能拿Field[]直接用的比较
return true;
}
return false;
}
//生成这个字段的get方法
private static String obtainGetMethod(Field field) {
String fieldName = field.getName();
String upperCase = "get" + fieldName.substring(0, 1).toUpperCase();
return upperCase+fieldName.substring(1);
}
//生成这个字段的set方法
private static String obtainSetMethod(Field field) {
String fieldName = field.getName();
String upperCase = "set" + fieldName.substring(0, 1).toUpperCase();
return upperCase+fieldName.substring(1);
}

}

Springframework的BeanUtils.copyProperties原理源码浅层解析_第1张图片

你可能感兴趣的:(study,tools)