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

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

PropertyDescriptor 属性描述器


属性描述符描述了Java bean通过一对访问器方法导出的一个属性。上面的示例此处用PropertyDescriptor试试:


    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工厂方法来获取实例。


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<>(16));
  static final ConcurrentMap<Class<?>, CachedIntrospectionResults> strongClassCache = new ConcurrentHashMap<>(64);
  static final ConcurrentMap<Class<?>, CachedIntrospectionResults> softClassCache = new ConcurrentReferenceHashMap<>(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里配置为例)


<listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>


说明:请保证此监听器配置在第一个位置,比ContextLoaderListener还靠前~ 此监听器能有效的防止内存泄漏问题~~~(因为内省的缓存是应用级别的全局缓存,很容易造成泄漏的~)

其实流行框架比如struts, Quartz等在使用JDK的内省时,存在没有释的内存泄漏问题~


DirectFieldAccessFallbackBeanWrapper


说完了BeanWrapperImpl,可以看看它的子类DirectFieldAccessFallbackBeanWrapper,他就像BeanWrapperImpl和DirectFieldAccessor的结合体。它先用BeanWrapperImpl.getPropertyValue(),若抛出异常了(毕竟内省不是十分靠谱,哈哈)再用DirectFieldAccessor~~~此子类在JedisClusterConnection有被使用到过,比较简单没啥太多好说的~


PropertyAccessorFactory


Spring2.5后提供的快速获取PropertyAccessor两个重要实现类的工厂。

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

说了这么多,是时候实战一把了~


// 省略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还是非常之简便的。


最后,上一张比较丑的结构图,画一画属性编辑器、类型转换器、属性解析器、属性访问器大致的一个关系(此图不喜勿碰):


image.png



总结


BeanWrapper接口,作为Spring内部的一个核心接口,正如其名,它是bean的包裹类,即在内部中将会保存该bean的实例,提供其它一些扩展功能。


Spring对Bean的属性存取都是通过BeanWrapperImpl实现的,BeanWrapperImpl和Bean是一对一的关系,BeanWrapperImpl通过属性的读方法和写方法来存取Bean属性的。为了更加深刻的了解BeanWrapper,下篇文章会深入分析Spring BeanFactory对它的应用~

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

热门文章

最新文章