前提

这篇文章主要分析一下Introspector(内省,应该读xing第三声,没有找到很好的翻译,下文暂且这样称呼)的用法。Introspector是一个专门处理JavaBean的工具类,用来获取JavaBean里描述符号,常用的JavaBean的描述符号相关类有BeanInfoPropertyDescriptorMethodDescriptorBeanDescriptorEventSetDescriptorParameterDescriptor。下面会慢慢分析这些类的使用方式,以及Introspector的一些特点。

JavaBean是什么

JavaBean是一种特殊(其实说普通也可以,也不是十分特殊)的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则(字段都是私有,每个字段具备SetterGetter方法,方法和字段命名满足首字母小写驼峰命名规则)。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为值对象(Value Object)或者VO。这些信息储存在类的私有变量中,通过SetterGetter方法获得。JavaBean的信息在Introspector里对应的概念是BeanInfo,它包含了JavaBean所有的Descriptor(描述符),主要有PropertyDescriptorMethodDescriptorMethodDescriptor里面包含ParameterDescriptor)、BeanDescriptorEventSetDescriptor

属性Field和属性描述PropertiesDescriptor的区别

如果是严格的JavaBean(Field名称不重复,并且Field具备SetterGetter方法),它的PropertyDescriptor会通过解析SetterGetter方法,合并解析结果,最终得到对应的PropertyDescriptor实例。所以PropertyDescriptor包含了属性名称和属性的SetterGetter方法(如果存在的话)。

内省Introspector和反射Reflection的区别

  • Reflection:反射就是运行时获取一个类的所有信息,可以获取到类的所有定义的信息(包括成员变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。可以想象为镜面反射或者照镜子,这样的操作是带有客观色彩的,也就是反射获取到的类信息是必定正确的。
  • Introspector:内省基于反射实现,主要用于操作JavaBean,基于JavaBean的规范进行Bean信息描述符的解析,依据于类的SetterGetter方法,可以获取到类的描述符。可以想象为"自我反省",这样的操作带有主观的色彩,不一定是正确的(如果一个类中的属性没有SetterGetter方法,无法使用Introspector)。

常用的Introspector相关类

主要介绍一下几个核心类所提供的方法。

Introspector

Introspector类似于BeanInfo的静态工厂类,主要是提供静态方法通过Class实例获取到BeanInfo,得到BeanInfo之后,就能够获取到其他描述符。主要方法:

  • public static BeanInfo getBeanInfo(Class<?> beanClass):通过Class实例获取到BeanInfo实例。

BeanInfo

BeanInfo是一个接口,具体实现是GenericBeanInfo,通过这个接口可以获取一个类的各种类型的描述符。主要方法:

  • BeanDescriptor getBeanDescriptor():获取JavaBean描述符。
  • EventSetDescriptor[] getEventSetDescriptors():获取JavaBean的所有的EventSetDescriptor
  • PropertyDescriptor[] getPropertyDescriptors():获取JavaBean的所有的PropertyDescriptor
  • MethodDescriptor[] getMethodDescriptors():获取JavaBean的所有的MethodDescriptor

这里要注意一点,通过BeanInfo#getPropertyDescriptors()获取到的PropertyDescriptor数组中,除了Bean属性的之外,还会带有一个属性名为classPropertyDescriptor实例,它的来源是ClassgetClass方法,如果不需要这个属性那么最好判断后过滤,这一点需要紧记,否则容易出现问题。

PropertyDescriptor

PropertyDescriptor类表示JavaBean类通过存储器(SetterGetter)导出一个属性,它应该是内省体系中最常见的类。主要方法:

  • synchronized Class<?> getPropertyType():获得属性的Class对象。
  • synchronized Method getReadMethod():获得用于读取属性值(Getter)的方法;
  • synchronized Method getWriteMethod():获得用于写入属性值(Setter)的方法。
  • int hashCode():获取对象的哈希值。
  • synchronized void setReadMethod(Method readMethod):设置用于读取属性值(Getter)的方法。
  • synchronized void setWriteMethod(Method writeMethod):设置用于写入属性值(Setter)的方法。

举个例子:

public class Main {

    public static void main(String[] args) throws Exception {
BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
if (!"class".equals(propertyDescriptor.getName())) {
System.out.println(propertyDescriptor.getName());
System.out.println(propertyDescriptor.getWriteMethod().getName());
System.out.println(propertyDescriptor.getReadMethod().getName());
System.out.println("=======================");
}
}
} public static class Person { private Long id;
private String name;
private Integer age; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
}
}
}

输出结果:

age
setAge
getAge
=======================
id
setId
getId
=======================
name
setName
getName
=======================

不正当使用Introspector会导致内存溢出

如果框架或者程序用到了JavaBeans Introspector,那么就相当于启用了一个系统级别的缓存,这个缓存会存放一些曾加载并分析过的Javabean的引用,当Web服务器关闭的时候,由于这个缓存中存放着这些Javabean的引用,所以垃圾回收器不能对Web容器中的JavaBean对象进行回收,导致内存越来越大。还有一点值得注意,清除Introspector缓存的唯一方式是刷新整个缓存缓冲区,这是因为JDK没法判断哪些是属于当前的应用的引用,所以刷新整个Introspector缓存缓冲区会导致把服务器的所有应用的Introspector缓存都删掉。Spring中提供的org.springframework.web.util.IntrospectorCleanupListener就是为了解决这个问题,它会在Web服务器停止的时候,清理一下这个Introspector缓存,使那些Javabean能被垃圾回收器正确回收。

也就是说JDKIntrospector缓存管理是有一定缺陷的。但是如果使用在Spring体系则不会出现这种问题,因为SpringIntrospector缓存的管理移交到Spring自身而不是JDK(或者在Web容器销毁后完全不管),在加载并分析完所有类之后,会针对类加载器对Introspector缓存进行清理,避免内存泄漏的问题,详情可以看CachedIntrospectionResultsSpringBoot刷新上下文的方法AbstractApplicationContext#refresh()finally代码块中存在清理缓存的方法AbstractApplicationContext#resetCommonCaches();。但是有很多程序和框架在使用了JavaBeans Introspector之后,都没有进行清理工作,比如QuartzStruts等,这类操作会成为内存泄漏的隐患。

小结

  • 在标准的JavaBean中,可以考虑使用Introspector体系解析JavaBean,主要是方便使用反射之前的时候快速获取到JavaBeanSetterGetter方法。
  • Spring体系中,为了防止JDK对内省信息的缓存无法被垃圾回收机制回收导致内存溢出,主要的操作除了可以通过配置IntrospectorCleanupListener预防,还有另外一种方式,就是通过CachedIntrospectionResults类自行管理Introspector中的缓存(这种方式才是优雅的方式,这样可以避免刷新整个Introspector的缓存缓冲区而导致其他应用的Introspector也被清空),也就是把JDK自行管理的Introspector相关缓存交给Spring自己去管理。在SpringBoot刷新上下文的方法AbstractApplicationContext#refresh()finally代码块中存在清理缓存的方法AbstractApplicationContext#resetCommonCaches();,里面调用到的CachedIntrospectionResults#clearClassLoader(getClassLoader())方法就是清理指定的ClassLoader下的所有Introspector中的缓存的引用。

(本文完 e-a-20200811 c-1-d)

这是公众号《Throwable》发布的原创文章,收录于专辑《Java基础与进阶》。

聊聊Java内省Introspector的更多相关文章

  1. Java 内省(Introspector)深入理解

    Java 内省(Introspector)深入理解 一些概念: 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类 ...

  2. Java 内省(Introspector)和 BeanUtils

    人生若只如初见,何事秋风悲画扇. 概述 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传递数据信息, ...

  3. JAVA内省(Introspector)

    什么是Java内省:内省是Java语言对Bean类属性.事件的一种缺省处理方法. Java内省的作用:一般在开发框架时,当需要操作一个JavaBean时,如果一直用反射来操作,显得很麻烦:所以sun公 ...

  4. Java 内省 Introspector

    操纵类的属性,有两种方法 反射 内省 面向对象的编程中,对于用户提交过来的数据,要封装成一个javaBean,也就是对象 其中Bean的属性不是由字段来决定的,而是由get和Set方法来决定的 pub ...

  5. java内省Introspector

    大纲: JavaBean 规范 内省 一.JavaBean 规范 JavaBean —般需遵循以下规范. 实现 java.io.Serializable 接口. javaBean属性是具有getter ...

  6. 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor

    #### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...

  7. 【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的

    #### 每篇一句 > 具备了技术深度,遇到问题可以快速定位并从根本上解决.有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 #### 相关阅读 [[小家Spring]聊聊Sp ...

  8. 深入理解Java:内省(Introspector)

    深入理解Java:内省(Introspector) 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传 ...

  9. Java:内省(Introspector)

    内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且 ...

随机推荐

  1. Go Pentester - HTTP Servers(2)

    Routing with the gorilla/mux Package A powerful HTTP router and URL matcher for building Go web serv ...

  2. Angular 的前世今生

    目录 序言 AngularJS 简介 Angular 2.0 的动机 现如今的 Angular Angular 的核心概念 参考 序言 Angular 一般意义上是指 Angular v2 及以上版本 ...

  3. 字符串学习总结(Hash & Manacher & KMP)

    前言 终于开始学习新的东西了,总结一下字符串的一些知识. NO.1 字符串哈希(Hash) 定义 即将一个字符串转化成一个整数,并保证字符串不同,得到的哈希值不同,这样就可以用来判断一个该字串是否重复 ...

  4. python 批量重命名文件名字

    import os print(os.path) img_name = os.listdir('./img') for index, temp_name in enumerate(img_name): ...

  5. lua判断字符串包含另一个字符串

    lua判断字符串包含另一个字符串 --string.find("元字符串","模式字符串") 如下: print(string.find("CCBWe ...

  6. expect使用技巧

    1) 获取命令行参数,例如通过./abc.exp a1 a2执行expect脚本 set 变量名1 [lindex $argv 0] 获取第1个参数a1 set 变量名2 [lindex $argv ...

  7. 图解Kubernetes——故障排查指南

    针对越来多的Kubernetes容器云,对Kubernetes集群的故障排查却成了一个棘手问题.本文虫虫给大家以直观图示方式介绍如何排查Kubernetes的故障.该篇是系列文章续——故障排查篇. 概 ...

  8. Linux下给PHP安装redis扩展

    一.下载redis扩展源码包 PHP官网下载地址:http://pecl.php.net/package/redis 二.安装依赖插件 yum -y install wget make gcc gcc ...

  9. Python os.dup2() 方法

    概述 os.dup2() 方法用于将一个文件描述符 fd 复制到另一个 fd2.高佣联盟 www.cgewang.com Unix, Windows 上可用. 语法 dup2()方法语法格式如下: o ...

  10. CSP-J 2019游记

    准备篇 11.16早上,在南校集合后,大巴车开往日照. 在车上颓了一上午 中午到达日照,考场在山东外国语技术大学(SWUV) 到了大学里的餐厅潦草的吃完饭后去学术报告厅继续颓废 一到山外突然想起了暑假 ...