JAVA从入门到精通之聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor
沉沙 2019-07-17 来源 : 阅读 409 评论 0

摘要:本篇文章探讨了JAVA从入门到精通之聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor,希望阅读本篇文章以后大家有所收获,帮助大家对相关内容的理解更加深入。

本篇文章探讨了JAVA从入门到精通之聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor,希望阅读本篇文章以后大家有所收获,帮助大家对相关内容的理解更加深入。

JAVA从入门到精通之聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

<


#### 前言
这篇文章需要依赖于对属性访问器`PropertyAccessor`的理解,也就是上篇文章的内容:[【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用](https://blog.csdn.net/f641385712/article/details/95481552)
如果说上篇文章所说的`PropertyAccessor`你没有接触过和听过,那么本文即将要说的重点:`BeanWrapper`你应该多少有所耳闻吧~
`BeanWrapper`可以简单的把它理解为:一个方便开发人员**使用字符串**来对`Java Bean`的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:
 1. `PropertyEditor`:隶属于**Java Bean规范**。`PropertyEditor`只提供了`String &lt;-&gt; Object`的转换。
 2. `ConversionService`:Spring自3.0之后提供的替代PropertyEditor的机制(`BeanWrapper`在Spring的第一个版本就存在了~)
&gt; 按照Spring官方文档的说法,当容器内没有注册`ConversionService`的时候,会退回使用`PropertyEditor`机制。言外之意:首选方案是`ConversionService`
&gt; 其实了解的伙伴应该知道,这不是`BeanWrapper`的内容,而是父接口`PropertyAccessor`的内容~
## BeanWrapper
官方解释:Spring低级JavaBeans基础设施的中央接口。通常来说并不直接使用`BeanWrapper`,而是借助`BeanFactory`或者`DataBinder`来一起使用~
```java
//@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的内省结果而提高效率。
&gt; 在Spring2.5之前,此实现类是非public的,但在2.5之后给public了并且还提供了工厂:`PropertyAccessorFactory`帮助第三方框架能快速获取到一个实例~
```java
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 &amp;&amp; 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`的三重身份:
 1. **Bean包裹器**
 2. 属性访问器(PropertyAccessor)
 3. 属性编辑器注册表(PropertyEditorRegistry)
从源码中继续分析还能再得出如下两个结论:
 1. 它给属性赋值调用的是Method方法,如`readMethod.invoke`和`writeMethod.invoke`
 2. 它对Bean的操作,大都委托给`CachedIntrospectionResults`去完成~
因此若想了解它,必然主要是要先了解`java.beans.PropertyDescriptor`和`org.springframework.beans.CachedIntrospectionResults`,首当其冲的自然还有`Java内省`。
---
#### Java内省`Introspector`
首先可以先了解下**JavaBean的概念**:一种特殊的类,主要用于传递数据信息。这种类中的方法主要用于访问私有的字段,且方法名**符合某种命名规则**。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(`Value Object`),或“`VO`”。
**因此JavaBean都有如下几个特征:**
 1. 属性都是私有的;
 2. 有无参的public构造方法;
 3. 对私有属性根据需要提供**公有的**getXxx方法以及setXxx方法;
 4. getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;
**符合这些特征的类,被称为JavaBean**;JDK中提供了一套API用来访问某个属性的getter/setter方法,这些API存放在`java.beans`中,这就是`内省(Introspector)`。
###### ==内省和反射的区别==
**反射**:Java反射机制是在运行中,对任意一个类,能够获取得到这个类的所有属性和方法;它针对的是**任意类**
**内省(Introspector)**:是Java语言对JavaBean类属性、事件的处理方法
 1. 反射可以操作**各种类的属性**,而内省只是通过反射来操作`JavaBean`的属性
 2. 内省设置属性值肯定会调用seter方法,反射可以不用(反射可直接操作属性Field)
 3. **反射就像照镜子,然后能看到.class的所有,是客观的事实。内省更像主观的判断:比如看到getName()内省就会认为这个类中有name字段,但事实上并不一定会有name;通过内省可以获取bean的getter/setter**
既然反射比内省比内省强大这么多,**那内省用在什么时候场景呢**?下面给出一个示例来说明它的用武之地:
```java
 // 就这样简单几步,就完成了表单到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:
```java
@Getter
@Setter
@ToString
public class Child {
    private String name;
    private Integer age;
   
}
```
###### 使用`Introspector` + `BeanInfo`:
```java
    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 -&gt; System.out.println(x));
        System.out.println("------------------------------");
        Arrays.stream(propertyDescriptors).forEach(x -&gt; System.out.println(x));
        System.out.println("------------------------------");
    }
```
输入内容如下:
```java
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`哦~
&gt; 关于`BeanInfo`,Spring在3.1提供了一个类`ExtendedBeanInfo`继承自它实现了功能扩展,并且提供了`BeanInfoFactory`来专门生产它~~~(实现类为:`ExtendedBeanInfoFactory`)
但是如果只想拿某一个属性的话,使用`Introspector`就不是那么方便了,下面介绍更为常用的`PropertyDescriptor`来处理某一个属性~
#### PropertyDescriptor 属性描述器
属性描述符描述了Java bean通过**一对访问器方法导出**的一个属性。上面的示例此处用`PropertyDescriptor`试试:
```java
    public static void main(String[] args) throws IntrospectionException {
        PropertyDescriptor age = new PropertyDescriptor("age", Child.class);
        System.out.println(age.getPropertyType()); //class java.lang.Integer
        System.out.println(age.getDisplayName()); //age
        // 最重要的两个方法~~~
        System.out.println(age.getReadMethod()); //public java.lang.Integer com.fsx.bean.Child.getAge()
        System.out.println(age.getWriteMethod()); //public void com.fsx.bean.Child.setAge(java.lang.Integer)
    }
```
可以看到它可以实现更加细粒度的控制。将`PropertyDescriptor`类的一些主要方法描述如下:
 1. getPropertyType(),获得属性的Class对象;
 2. **getReadMethod(),获得用于读取属性值的方法;**
 3. **getWriteMethod(),获得用于写入属性值的方法;**
 4. setReadMethod(Method readMethod),设置用于读取属性值的方法;
 5. setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
---
#### CachedIntrospectionResults
Spring如果需要依赖注入那么就必须依靠Java内省这个特性了,说到**Spring IOC与JDK内省的结合**那么就不得不说一下Spring中的`CachedIntrospectionResults`这个类了。
它是Spring提供的专门用于缓存JavaBean的`PropertyDescriptor`描述信息的类,不能被应用代码直接使用。
它的缓存信息是被静态存储起来的(应用级别),因此对于**同一个类型的**被操作的`JavaBean`并不会都创建一个新的`CachedIntrospectionResults`,因此,这个类使用了工厂模式,使用私有构造器和一个静态的`forClass`工厂方法来获取实例。
```java
public final class CachedIntrospectionResults {
 
 // 它可以通过在spring.properties里设置这个属性,来关闭内省的缓存~~~
 public static final String IGNORE_BEANINFO_PROPERTY_NAME = "spring.beaninfo.ignore";
 private static final boolean shouldIntrospectorIgnoreBeaninfoClasses = SpringProperties.getFlag(IGNORE_BEANINFO_PROPERTY_NAME);
 // 此处使用了SpringFactoriesLoader这个SPI来加载BeanInfoFactory,唯一实现类是ExtendedBeanInfoFactory
 /** Stores the BeanInfoFactory instances. */
 private static List<beaninfofactory> beanInfoFactories = SpringFactoriesLoader.loadFactories(
   BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader());
 static final Set<classloader> acceptedClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;(16));
 static final ConcurrentMap<class<?>, CachedIntrospectionResults&gt; strongClassCache = new ConcurrentHashMap&lt;&gt;(64);
 static final ConcurrentMap<class<?>, CachedIntrospectionResults&gt; softClassCache = new ConcurrentReferenceHashMap&lt;&gt;(64);
 // 被包裹类的BeanInfo~~~也就是目标类
 private final BeanInfo beanInfo;
 // 它缓存了被包裹类的所有属性的属性描述器PropertyDescriptor。
 private final Map<string, propertydescriptor=""> propertyDescriptorCache;
 ... // 其它的都是静态方法
 // 只有它会返回一个实例,此类是单例的设计~  它保证了每个beanClass都有一个CachedIntrospectionResults 对象,然后被缓存起来~
 static CachedIntrospectionResults forClass(Class<!--?--> beanClass) throws BeansException { ... }
}
```
本处理类的核心内容是Java内省`getBeanInfo()`以及`PropertyDescriptor`~注意:为了使此内省缓存生效,有个前提条件请保证了:
 - 确保将Spring框架的Jar包和你的应用类使用的是同一个`ClassLoader`加载的,这样在**任何情况下**会允许随着应用的生命周期来清楚缓存。
因此对于web应用来说,Spring建议给web容器注册一个`IntrospectorCleanupListener`监听器来防止多`ClassLoader`布局,这样也可以**有效的利用caching**从而提高效率~
---
监听器的配置形如这样(此处以web.xml里配置为例):
```xml
<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
```
&gt; 说明:请保证此监听器配置在第一个位置,比`ContextLoaderListener`还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~)
&gt; 其实流行框架比如`struts, Quartz`等在使用JDK的内省时,存在没有释的内存泄漏问题~
---
##### `DirectFieldAccessFallbackBeanWrapper`
说完了`BeanWrapperImpl`,可以看看它的子类`DirectFieldAccessFallbackBeanWrapper`,他就像`BeanWrapperImpl`和`DirectFieldAccessor`的结合体。它先用`BeanWrapperImpl.getPropertyValue()`,若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用`DirectFieldAccessor`~~~此子类在`JedisClusterConnection`有被使用到过,比较简单没啥太多好说的~
#### PropertyAccessorFactory
`Spring2.5`后提供的快速获取`PropertyAccessor`两个重要实现类的工厂。
```java
public final class PropertyAccessorFactory {
 private PropertyAccessorFactory() {
 }
 // 生产一个BeanWrapperImpl(最为常用)
 public static BeanWrapper forBeanPropertyAccess(Object target) {
  return new BeanWrapperImpl(target);
 }
 // 生产一个DirectFieldAccessor
 public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {
  return new DirectFieldAccessor(target);
 }
}
```
#### BeanWrapper使用Demo
说了这么多,是时候实战一把了~
```java
// 省略Apple类和Size类,有需要的请参照上篇文章(加上@Getter、@Setter即可
    public static void main(String[] args) {
        Apple apple = new Apple();
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(apple);
        // ================当作一个普通的PropertyAccessor来使用  默认情况下字段也都必须有初始值才行~===================
        // 设置普通属性
        beanWrapper.setPropertyValue("color", "红色"); //请保证对应字段有set方法才行,否则抛错:Does the parameter type of the setter match the return type of the getter?
        // 设置嵌套属性(注意:此处能够正常work是因为有= new Size(),
        // 否则报错:Value of nested property 'size' is null 下同~)
        beanWrapper.setPropertyValue("size.height", 10);
        // 设置集合/数组属性
        beanWrapper.setPropertyValue("arrStr[0]", "arrStr");
        beanWrapper.setPropertyValue("arrStr[1]", "arrStr1"); // 注意:虽然初始化时初始化过数组了,但是仍以此处的为准
        // =========打印输出
        System.out.println(apple); //Apple(color=红色, size=Size(height=10, width=null), arrStr=[arrStr, arrStr1], listStr=[], map={}, listList=[[]], listMap=[{}])
        // 当作BeanWrapper使用
        PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
        PropertyDescriptor color = beanWrapper.getPropertyDescriptor("color");
        System.out.println(propertyDescriptors.length); // 8
        System.out.println(color); //org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=color]
        System.out.println(beanWrapper.getWrappedClass()); //class com.fsx.bean.Apple
        System.out.println(beanWrapper.getWrappedInstance()); //Apple(color=红色, size=Size(height=10...
    }
```
上面代码能够清晰的表示了通过`BeanWrapper`来操作JavaBean还是非常之简便的。
---
最后,上一张比较丑的结构图,画一画属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系(此图不喜勿碰):
![在这里插入图片描述]

      本文由职坐标整理发布,学习更多的相关知识,请关注职坐标IT知识库!

本文由 @沉沙 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程