聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(上)

简介: 聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(上)

前言


这篇文章需要依赖于对属性访问器PropertyAccessor的理解,也就是上篇文章的内容:【小家Spring】聊聊Spring中的数据绑定 — 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用


如果说上篇文章所说的PropertyAccessor你没有接触过和听过,那么本文即将要说的重点:BeanWrapper你应该多少有所耳闻吧~

BeanWrapper可以简单的把它理解为:一个方便开发人员使用字符串来对Java Bean的属性执行get、set操作的工具。关于它的数据转换使用了如下两种机制:


  1. PropertyEditor:隶属于Java Bean规范。PropertyEditor只提供了String <-> Object的转换。
  2. 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的三重身份:


  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


既然反射比内省比内省强大这么多,那内省用在什么时候场景呢?下面给出一个示例来说明它的用武之地:


  // 就这样简单几步,就完成了表单到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来处理某一个属性~

相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
92 2
|
3天前
|
Java Spring
Java Spring Boot监听事件和处理事件
通过上述步骤,我们可以在Java Spring Boot应用中实现事件的发布和监听。事件驱动模型可以帮助我们实现组件间的松耦合,提升系统的可维护性和可扩展性。无论是处理业务逻辑还是系统事件,Spring Boot的事件机制都提供了强大的支持和灵活性。希望本文能为您的开发工作提供实用的指导和帮助。
39 15
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
63 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
250 12
基于开源框架Spring AI Alibaba快速构建Java应用
|
3月前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
2月前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
75 2
|
3月前
|
JSON Java Maven
实现Java Spring Boot FCM推送教程
本指南介绍了如何在Spring Boot项目中集成Firebase云消息服务(FCM),包括创建项目、添加依赖、配置服务账户密钥、编写推送服务类以及发送消息等步骤,帮助开发者快速实现推送通知功能。
145 2
|
2月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
56 0
|
2月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
47 0
|
3天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
39 17