前言
这篇文章需要依赖于对属性访问器PropertyAccessor的理解,也就是上篇文章的内容:【小家Spring】聊聊Spring中的数据绑定 — 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
如果说上篇文章所说的PropertyAccessor你没有接触过和听过,那么本文即将要说的重点:BeanWrapper你应该多少有所耳闻吧~
BeanWrapper可以简单的把它理解为:一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:
- PropertyEditor:隶属于Java Bean规范。PropertyEditor只提供了String <-> Object的转换。
- ConversionService:Spring自3.0之后提供的替代PropertyEditor的机制(BeanWrapper在Spring的第一个版本就存在了~)
按照Spring官方文档的说法,当容器内没有注册ConversionService的时候,会退回使用PropertyEditor机制。言外之意:首选方案是ConversionService
其实了解的伙伴应该知道,这不是BeanWrapper的内容,而是父接口PropertyAccessor的内容~
BeanWrapper
官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用BeanWrapper,而是借助BeanFactory或者DataBinder来一起使用~
//@since 13 April 2001 很清晰的看到,它也是个`PropertyAccessor`属性访问器 public interface BeanWrapper extends ConfigurablePropertyAccessor { // @since 4.1 void setAutoGrowCollectionLimit(int autoGrowCollectionLimit); int getAutoGrowCollectionLimit(); Object getWrappedInstance(); Class<?> getWrappedClass(); // 获取属性们的PropertyDescriptor 获取属性们 PropertyDescriptor[] getPropertyDescriptors(); // 获取具体某一个属性~ PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException; }
BeanWrapper相当于一个代理器,Spring委托BeanWrapper完成Bean属性的填充工作。关于此接口的实现类,简单的说它只有唯一实现类:BeanWrapperImpl
BeanWrapperImpl
它作为BeanWrapper接口的默认实现,它足以满足所有的典型应用场景,它会缓存Bean的内省结果而提高效率。
在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:PropertyAccessorFactory帮助第三方框架能快速获取到一个实例~
public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements BeanWrapper { // 缓存内省结果~ @Nullable private CachedIntrospectionResults cachedIntrospectionResults; // The security context used for invoking the property methods. @Nullable private AccessControlContext acc; // 构造方法都是沿用父类的~ public BeanWrapperImpl() { this(true); } ... private BeanWrapperImpl(Object object, String nestedPath, BeanWrapperImpl parent) { super(object, nestedPath, parent); setSecurityContext(parent.acc); } // @since 4.3 设置目标对象~~~ public void setBeanInstance(Object object) { this.wrappedObject = object; this.rootObject = object; this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject); // 设置内省的clazz setIntrospectionClass(object.getClass()); } // 复写父类的方法 增加内省逻辑 @Override public void setWrappedInstance(Object object, @Nullable String nestedPath, @Nullable Object rootObject) { super.setWrappedInstance(object, nestedPath, rootObject); setIntrospectionClass(getWrappedClass()); } // 如果cachedIntrospectionResults它持有的BeanClass并不是传入的clazz 那就清空缓存 重新来~~~ protected void setIntrospectionClass(Class<?> clazz) { if (this.cachedIntrospectionResults != null && this.cachedIntrospectionResults.getBeanClass() != clazz) { this.cachedIntrospectionResults = null; } } private CachedIntrospectionResults getCachedIntrospectionResults() { if (this.cachedIntrospectionResults == null) { // forClass此方法:生成此clazz的类型结果,并且缓存了起来~~ this.cachedIntrospectionResults = CachedIntrospectionResults.forClass(getWrappedClass()); } return this.cachedIntrospectionResults; } ... // 获取到此属性的处理器。此处是个BeanPropertyHandler 内部类~ @Override @Nullable protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) { PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName); return (pd != null ? new BeanPropertyHandler(pd) : null); } @Override protected BeanWrapperImpl newNestedPropertyAccessor(Object object, String nestedPath) { return new BeanWrapperImpl(object, nestedPath, this); } @Override public PropertyDescriptor[] getPropertyDescriptors() { return getCachedIntrospectionResults().getPropertyDescriptors(); } // 获取具体某一个属性的PropertyDescriptor @Override public PropertyDescriptor getPropertyDescriptor(String propertyName) throws InvalidPropertyException { BeanWrapperImpl nestedBw = (BeanWrapperImpl) getPropertyAccessorForPropertyPath(propertyName); String finalPath = getFinalPath(nestedBw, propertyName); PropertyDescriptor pd = nestedBw.getCachedIntrospectionResults().getPropertyDescriptor(finalPath); if (pd == null) { throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, "No property '" + propertyName + "' found"); } return pd; } ... // 此处理器处理的是PropertyDescriptor private class BeanPropertyHandler extends PropertyHandler { private final PropertyDescriptor pd; // 是否可读、可写 都是由PropertyDescriptor 去决定了~ // java.beans.PropertyDescriptor~~ public BeanPropertyHandler(PropertyDescriptor pd) { super(pd.getPropertyType(), pd.getReadMethod() != null, pd.getWriteMethod() != null); this.pd = pd; } ... @Override @Nullable public Object getValue() throws Exception { ... ReflectionUtils.makeAccessible(readMethod); return readMethod.invoke(getWrappedInstance(), (Object[]) null); } ... } }
从继承体系上,首先我们应该能看出来BeanWrapperImpl的三重身份:
- Bean包裹器
- 属性访问器(PropertyAccessor)
- 属性编辑器注册表(PropertyEditorRegistry)
从源码中继续分析还能再得出如下两个结论:
- 它给属性赋值调用的是Method方法,如readMethod.invoke和writeMethod.invoke
- 它对Bean的操作,大都委托给CachedIntrospectionResults去完成~
因此若想了解它,必然主要是要先了解java.beans.PropertyDescriptor和org.springframework.beans.CachedIntrospectionResults,首当其冲的自然还有Java内省。
Java内省Introspector
首先可以先了解下JavaBean的概念:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。
因此JavaBean都有如下几个特征:
- 属性都是私有的;
- 有无参的public构造方法;
- 对私有属性根据需要提供公有的getXxx方法以及setXxx方法;
- getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;
符合这些特征的类,被称为JavaBean;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在java.beans中,这就是内省(Introspector)。
内省和反射的区别
反射:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是任意类
内省(Introspector):是Java语言对JavaBean类属性、事件的处理方法
- 反射可以操作各种类的属性,而内省只是通过反射来操作JavaBean的属性
- 内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)
- 反射就像照镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;通过内省可以获取bean的getter/setter
既然反射比内省比内省强大这么多,那内省用在什么时候场景呢?下面给出一个示例来说明它的用武之地:
// 就这样简单几步,就完成了表单到User对象的封装~ public void insertUser(HttpServletRequest request) throws Exception { User user = new User(); // 遍历:根据字段名去拿值即可(此处省略判空、类型转换等细节,不在本文讨论范围) PropertyDescriptor[] pds = Introspector.getBeanInfo(User.class).getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { pd.getWriteMethod().invoke(user, request.getParameter(pd.getName())); } }
通过内省可以很轻松的将form表单的内容填充进对象里面,比反射轻松省力多了。其实像MyBatis这种框架,底层都用到了Java的内省机制。
内省的API主要有Introspector、BeanInfo、PropertyDescriptor等,下面就以他三为例来操作一个JavaBean:
@Getter @Setter @ToString public class Child { private String name; private Integer age; }
使用Introspector + BeanInfo:
public static void main(String[] args) throws IntrospectionException { BeanInfo beanInfo = Introspector.getBeanInfo(Child.class); BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); // 打印 System.out.println(beanDescriptor); System.out.println("------------------------------"); Arrays.stream(methodDescriptors).forEach(x -> System.out.println(x)); System.out.println("------------------------------"); Arrays.stream(propertyDescriptors).forEach(x -> System.out.println(x)); System.out.println("------------------------------"); }
输入内容如下:
java.beans.BeanDescriptor[name=Child; beanClass=class com.fsx.bean.Child] ------------------------------ java.beans.MethodDescriptor[name=getClass; method=public final native java.lang.Class java.lang.Object.getClass()] java.beans.MethodDescriptor[name=getName; method=public java.lang.String com.fsx.bean.Child.getName()] java.beans.MethodDescriptor[name=setAge; method=public void com.fsx.bean.Child.setAge(java.lang.Integer)] java.beans.MethodDescriptor[name=setName; method=public void com.fsx.bean.Child.setName(java.lang.String)] java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.fsx.bean.Child.getAge()] java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait() throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=notifyAll; method=public final native void java.lang.Object.notifyAll()] java.beans.MethodDescriptor[name=notify; method=public final native void java.lang.Object.notify()] java.beans.MethodDescriptor[name=wait; method=public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=hashCode; method=public native int java.lang.Object.hashCode()] java.beans.MethodDescriptor[name=wait; method=public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException] java.beans.MethodDescriptor[name=equals; method=public boolean java.lang.Object.equals(java.lang.Object)] java.beans.MethodDescriptor[name=toString; method=public java.lang.String com.fsx.bean.Child.toString()] ------------------------------ java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.fsx.bean.Child.getAge(); writeMethod=public void com.fsx.bean.Child.setAge(java.lang.Integer)] java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; readMethod=public java.lang.String com.fsx.bean.Child.getName(); writeMethod=public void com.fsx.bean.Child.setName(java.lang.String)] ------------------------------
可以看到getMethodDescriptors()它把父类的MethodDescriptor也拿出来了。
而PropertyDescriptor中比较特殊的是因为有getClass()方法,因此class也算是一个PropertyDescriptor,但是它没有writeMethod哦~
关于BeanInfo,Spring在3.1提供了一个类ExtendedBeanInfo继承自它实现了功能扩展,并且提供了BeanInfoFactory来专门生产它~~~(实现类为:ExtendedBeanInfoFactory)
但是如果只想拿某一个属性的话,使用Introspector就不是那么方便了,下面介绍更为常用的PropertyDescriptor来处理某一个属性~