Spring BeanUtils源码分析

我们写java项目的时候经常会遇到DO、VO、DTO对象之间的属性拷贝,以前可能会使用get和set方法来进行赋值,但是这样缺点是非常明显的,代码又丑又长,Spring提供了BeanUtils来帮助我们简化这个过程,其基本原理就是通过Java的反射机制(内省机制),我们一起来分析,顺便学习一下。

 

1.内省机制(Introspection和PropertyDescriptor类)

因为其实现原理是通过内省实现的,所以我们先简单的回顾一下java的内省机制

PropertyDescriptor类表示的是标准形式的Java Bean的一个属性,既一个带有get和set方法的属性。

先来一个标准的javaBean

public class Member {
    private Long id;
    private String name;
    private String phone;
    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

获取该javaBean每个属性的PropertyDescriptor的方式有两种

1.第一种,单独获取一个属性的描述器

@Test
public void test() throws IntrospectionException {
    Class<Member> memberClass = Member.class;
    Member member = new Member();
    member.setName("张三");
    // 指定一个类和该类的标准化的属性
    PropertyDescriptor p = new PropertyDescriptor("name", memberClass);
    // 获取该属性的get和set方法
    Method readMethod = p.getReadMethod();
    Method writeMethod = p.getWriteMethod();
}

2.第二种,直接获取一个类所有的标准化的属性的描述器,大多数时候我们还是使用的这种方法

@Test
public void test() throws IntrospectionException {
    Class<Member> memberClass = Member.class;
    Member member = new Member();
    member.setName("张三");
    // 获取该bean的属性描述器
    BeanInfo beanInfo = Introspector.getBeanInfo(memberClass);
    // 获取该bean所有标准化的属性的描述器
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
}

2.Class类

我们知道jvm会为每一个被加载的类创建一个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对象

1.通过java关键字

Class<Member> memberClass = Member.class;

需要注意的是:。<class-name>.class 是 Java 中的某个特定语法。不是Member中的静态属性。

2.通过对象的getClass()方法

Class<? extends Member> aClass = member.getClass();

3.通过Class的静态方法forName()

Class clazz = Class.forName("com.Member"); 

 

差不多了进入主题吧

1.copyProperties方法

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");
        // 获取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);
                        }
                    }
                }
            }
        }
    }

2.getPropertyDescriptors方法

    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
        // CachedIntrospectionResults类是对PropertyDescriptor的一个封装实现,提供了对属性描述器的缓存作用,就不用每一次都去用反射,因为频繁使用反射会影响效率
        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);
    }

3.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的基本类型和包装类型做了兼容。

isAssignableFrom(Class<?> cls)方法的作用是判断当前class对象表示的类或者接口与给定的class对象所表示的类或者接口是相同的,或者当前class表示的类对象或者接口是给定的class对象表示的类或接口的超类或者超接口

 

总结

这么简单的一个小功能,实现还挺赋复杂,使用了缓存,所以在效率上来说,这个工具还是非常不错的。

关于对缓存的管理,这里使用到了Soft Reference去避免内存泄漏,避免一个Class类不能被回收。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值