聊聊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来处理某一个属性~

相关文章
|
3月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
290 3
|
2月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
3月前
|
人工智能 Java API
构建基于Java的AI智能体:使用LangChain4j与Spring AI实现RAG应用
当大模型需要处理私有、实时的数据时,检索增强生成(RAG)技术成为了核心解决方案。本文深入探讨如何在Java生态中构建具备RAG能力的AI智能体。我们将介绍新兴的Spring AI项目与成熟的LangChain4j框架,详细演示如何从零开始构建一个能够查询私有知识库的智能问答系统。内容涵盖文档加载与分块、向量数据库集成、语义检索以及与大模型的最终合成,并提供完整的代码实现,为Java开发者开启构建复杂AI智能体的大门。
1459 58
|
2月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
126 8
|
3月前
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
294 3
|
4月前
|
前端开发 Java 开发者
Java新手指南:在Spring MVC中使用查询字符串与参数
通过结合实际的需求和业务逻辑,开发者可以灵活地利用这些机制,为用户提供更丰富而高效的Web应用体验。
159 15
|
5月前
|
JSON 前端开发 Java
Java新手指南:如何在Spring MVC中处理请求参数
处理Spring MVC中的请求参数是通过控制器方法中的注解来完成的。这些注解包括 `@RequestParam`, `@PathVariable`, `@ModelAttribute`, `@RequestBody`, `@RequestHeader`, `@Valid`, 和 `@RequestMapping`。使用这些注解可以轻松从HTTP请求中提取所需信息,例如URL参数、表单数据或者JSON请求体,并将其转换成Java对象以供进一步处理。
475 17
|
5月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
555 0
|
4月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
750 0
|
5月前
|
前端开发 Java API
基于 Spring Boot 3 与 React 的 Java 学生信息管理系统从入门到精通实操指南
本项目基于Spring Boot 3与React 18构建学生信息管理系统,涵盖前后端开发、容器化部署及测试监控,提供完整实操指南与源码,助你掌握Java全栈开发技能。
245 0