前提

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

JavaBean是什么

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

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

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

内省Introspector和反射Reflection的区别

Reflection:反射就是运行时获取一个类的所有信息,可以获取到类的所有定义的信息(包括成员变量,成员方法,构造器等)可以操纵类的字段、方法、构造器等部分。可以想象为镜面反射或者照镜子,这样的操作是带有客观色彩的,也就是反射获取到的类信息是必定正确的。

Introspector:内省基于反射实现,主要用于操作JavaBean,基于JavaBean的规范进行Bean信息描述符的解析,依据于类的Setter和Getter方法,可以获取到类的描述符。可以想象为"自我反省",这样的操作带有主观的色彩,不一定是正确的(如果一个类中的属性没有Setter和Getter方法,无法使用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属性的之外,还会带有一个属性名为class的PropertyDescriptor实例,它的来源是Class的getClass方法,如果不需要这个属性那么最好判断后过滤,这一点需要紧记,否则容易出现问题。

PropertyDescriptor

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

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能被垃圾回收器正确回收。

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

小结

在标准的JavaBean中,可以考虑使用Introspector体系解析JavaBean,主要是方便使用反射之前的时候快速获取到JavaBean的Setter和Getter方法。

在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内省_聊聊Java内省Introspector相关推荐

  1. java异常_聊聊Java中的异常及处理

    前言 在编程中异常报错是不可避免的.特别是在学习某个语言初期,看到异常报错就抓耳挠腮,常常开玩笑说编程1分钟,改bug1小时.今天就让我们来看看什么是异常和怎么合理的处理异常吧! 异常与error介绍 ...

  2. java 物理内存_聊聊Java中的内存

    JVM的内存 先放一张JVM的内存划分图,总体上可以分为堆和非堆(粗略划分,基于java8) 那么一个Java进程最大占用的物理内存为: Max Memory = eden + survivor + ...

  3. java 异步_聊聊java高并发系统之异步非阻塞

    作者:孙伟,目前负责京东商品详情页统一服务系统,写过java,写过ngx_lua,还写过storm等,喜欢学习研究新事物. 在做电商系统时,流量入口如首页.活动页.商品详情页等系统承载了网站的大部分流 ...

  4. java整段标记_聊聊JAVA GC系列(7) - 标记整理算法

    在介绍"平平无奇"的标记清除算法时, 还留下了另一个问题, 就是内存碎片的问题. 内存碎片的问题是指, 每次回收的内存都是比较分散的, 可以加起来是一个比较大的数值, 但是由于可用 ...

  5. java 获取泛型_聊聊Java泛型擦除那些事

    >版权申明]非商业目的注明出处可自由转载 博文地址:https://blog.csdn.net/ShuSheng0007/article/details/89789849 出自:shushen ...

  6. java gc机制 优点_聊聊Java的GC机制

    作者 某人Valar 如需转载请保留原文链接 部分图片来自百度,如有侵权请联系删除 本文目录 什么是GC JVM内存结构简单介绍 可达性分析与GC Roots 常见的垃圾收集算法 1. 什么是GC G ...

  7. java final 内存_聊聊 Java 内存模型

    原标题:聊聊 Java 内存模型 *作者:青芒@有赞 本文目录 Java内存模型 重排序 内存屏障 volatitle的内存语义 final的内存语义 一.Java内存模型 硬件处理 电脑硬件,我们知 ...

  8. 易语言 java支持_开源Java客户端可以连接易语言服务器

    我们的服务端处理客户端的连接请求是同步进行的, 每次接收到来自客户端的连接请求后, 都要先跟当前的客户端通信完之后才能再处理下一个连接请求. 这在并发比较多的情况下会严重影响程序的性能, 为此,我们可 ...

  9. 尚学堂java培训_送给 Java 自学者或者初学者的最全知识清单,2020 年 Java 就该这么学...

    最近逛知乎,发现有很多想自学 Java 或者 Java 初学者提问,不知道如何学习 Java?我接触 Java 快 8 年的时间了,一直从事 Java 开发工作,自己一直升级打怪,对于如何更好的学习 ...

  10. java 模板方法_设计模式(java实现)_模板方法模式(Template method)

    设计模式(java实现)_模板方法模式(Template method) 模板方法模式是编程中经常用到到的模式.它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现.这样,新的子类可以在不改变一个 ...

最新文章

  1. php explore im,浏栏器器-explore.class.php_php
  2. 前端开发中好用的 chrome 插件
  3. Day 09 学校应该鼓励学生做社区服务吗
  4. JavaScript实现跳跃游戏的动态编程自上而下的方法算法(附完整源码)
  5. 如何给对方邮箱发照片_朋友圈如何发心形拼图九宫格照片?
  6. java -UDP通信
  7. eclipse出现String错误,问题已解决
  8. 出去转了一转,便利店......
  9. 深大自考本科所需课程
  10. 红队免杀培训第二章-使用系统调用http 协议下载恶意载荷
  11. C 语言绝对值函数abs实现技巧
  12. 计算机对未来和生活,计算机的发展对人类生活的影响
  13. 移动通信网络规划:5G业务解析
  14. 荣耀智慧屏和荣耀智慧屏PRO的配置
  15. 国行Surface Laptop 3评测:微软最佳轻薄本之作
  16. 9月有哪些程序员新书与您相约?
  17. 计算机一级access教程,计算机一级考试Access傻瓜教程.doc
  18. 基于rt-thread系统的探索者开发板综合测试实验(一)
  19. Hibernate:could not execute query 列名无效
  20. python字典、列表、元祖使用场景_(三)PYTHON字典 元祖 列表尝试应用

热门文章

  1. Java开发 明华usbkey_UsbKey开发文档
  2. python对比php开发web_django学习系列——python和php对比
  3. python入门教程基础语法_python入门教程13-02 (python语法入门之库相关操作)
  4. 实现对span标签的多选单选功能_如何在Notion中做多级标签?-Notion102
  5. 大数据要如何提高 才能满足智慧城市需求?
  6. 在 Linux 中使用 SSD(固态驱动器):启用 TRIM
  7. zabbix安装配置
  8. tcp/ip 协议的传输过程
  9. 至今为止碰到的非常妖怪的计算机问题
  10. Visual C++ 2005 系列课程学习笔记-6