资讯专栏INFORMATION COLUMN

Spring BeanUtils源码分析

darkbug / 2396人阅读

摘要:前置知识在分析源码前,我们先温习一下以下的知识点。类在中万物皆对象,而且我们在代码中写的每一个类也都是对象,是类的对象。总结一个看似简单的工具类,其实里面包含的基础的知识点非常多,包括类型信息反射线程安全引用类型类加载器等。

背景

在我们着手一个Java Web项目的时候,经常会遇到DO、VO、DTO对象之间的属性拷贝,若采用get、set的方法来进行赋值的话,代码会相当冗长丑陋,一般我们会采用SpringBeanUtils类来进行属性拷贝,其基本原理就是通过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 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, CachedIntrospectionResults>(64);
        // 即将Class作为key,CachedIntrospectionResults作为value的map,
        // 由于线程安全的需要,使用ConcurrentHashMap作为实现
        CachedIntrospectionResults results = strongClassCache.get(beanClass);
        if (results != null) {
            return results;
        }
        // 若strongClassCache中不存在,则去softClassCache去获取,softClassCache的声明如下
        // softClassCache = new ConcurrentReferenceHashMap, CachedIntrospectionResults>(64);
        // ConcurrentReferenceHashMap是Spring实现的可以指定entry引用级别的ConcurrentHashMap,默认的引用级别是soft,可以防止OOM
        results = softClassCache.get(beanClass);
        if (results != null) {
            return results;
        }

        results = new CachedIntrospectionResults(beanClass);
        ConcurrentMap, 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基础的知识点非常多,包括类型信息、反射、线程安全、引用类型、类加载器等。SpringBeanUtils的实现里使用了ConcurrentHashMap作为缓存,每次去获取PropertyDescriptor时,可以直接去缓存里面获取,而不必每次都去调用native方法,所以SpringBeanUtils的性能还是很不错的。

原文链接

https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71118.html

相关文章

  • 为什么阿里代码规约要求避免使用 Apache BeanUtils 进行属性的拷贝

    摘要:于是我建议这位小伙伴使用了进行属性拷贝,这为我们的程序挖了一个坑阿里代码规约当我们开启阿里代码扫描插件时,如果你使用了进行属性拷贝,它会给你一个非常严重的警告。大名鼎鼎的提供的包,居然会存在性能问题,以致于阿里给出了严重的警告。 声明:本文属原创文章,始发于公号:程序员自学之道,并同步发布于 https://blog.csdn.net/dadiyang,特此,同步发布到 sf,转载请注...

    raledong 评论0 收藏0
  • Apollo源码分析(二): Apollo的代码层次

    摘要:不同与其它中间件框架,中有大量的业务代码,它向我们展示了大神是如何写业务代码的依赖的层次结构,如何进行基础包配置,以及工具类编写,可以称之为之最佳实践。代码参考视图解析器,这里的配置指的是不检查头,而且默认请求为格式。 不同与其它中间件框架,Apollo中有大量的业务代码,它向我们展示了大神是如何写业务代码的:maven依赖的层次结构,如何进行基础包配置,以及工具类编写,可以称之为sp...

    cyqian 评论0 收藏0
  • BeanUtils工具使用细节

    摘要:拷贝操作又一个非常好用的工具类和中分别存在一个,提供了对。除了支持基本类型以及基本类型的数组之外,还支持这些类的对象,其余一概不支持。而且,由于这些类都是采用反射机制实现的,对程序的效率也会有影响。因此,慎用或者使用看效果如何 java bean拷贝操作又一个非常好用的工具类 BeanUitls :spring (org.springframework.beans.BeanUtils)...

    afishhhhh 评论0 收藏0
  • springboot源码分析系列(二)--SpringApplication.run()启动流程

    摘要:众所周知,类上面带有注解的类,即为的启动类。一个项目只能有一个启动类。根据是否是环境创建默认的,通过扫描所有注解类来加载和最后通过实例化上下文对象,并返回。   众所周知,类上面带有@SpringBootApplication注解的类,即为springboot的启动类。一个springboot项目只能有一个启动类。我们来分析一下SpringBoot项目的启动过程,首先看看启动类里面都包...

    adie 评论0 收藏0
  • Spring Boot 中 crud如何优雅的实现-附代码

    摘要:以下内容基于如果你使用的也是相同的技术栈可以继续往下阅读,如果不是可以当作参考。编写的四种方式裸写最简单最粗暴也是使用最多的一种方式,在写的多了之后可以用生成工具生成。 导读 在目前接触过的项目中大多数的项目都会涉及到: crud相关的操作, 哪如何优雅的编写crud操作呢?带着这个问题,我们发现项目中大量的操作多是 创建实体 、删除实例、 修改实体、 查询单个实体、 分页查询多个实体...

    wing324 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<