聊聊Java内省Introspector
前提
这篇文章主要分析一下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内省Introspector的更多相关文章
- Java 内省(Introspector)深入理解
Java 内省(Introspector)深入理解 一些概念: 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类 ...
- Java 内省(Introspector)和 BeanUtils
人生若只如初见,何事秋风悲画扇. 概述 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传递数据信息, ...
- JAVA内省(Introspector)
什么是Java内省:内省是Java语言对Bean类属性.事件的一种缺省处理方法. Java内省的作用:一般在开发框架时,当需要操作一个JavaBean时,如果一直用反射来操作,显得很麻烦:所以sun公 ...
- Java 内省 Introspector
操纵类的属性,有两种方法 反射 内省 面向对象的编程中,对于用户提交过来的数据,要封装成一个javaBean,也就是对象 其中Bean的属性不是由字段来决定的,而是由get和Set方法来决定的 pub ...
- java内省Introspector
大纲: JavaBean 规范 内省 一.JavaBean 规范 JavaBean —般需遵循以下规范. 实现 java.io.Serializable 接口. javaBean属性是具有getter ...
- 【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及内省Introspector和PropertyDescriptor
#### 每篇一句 > 千古以来要饭的没有要早饭的,知道为什么吗? #### 相关阅读 [[小家Spring]聊聊Spring中的数据转换:Converter.ConversionService ...
- 【小家Spring】Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的
#### 每篇一句 > 具备了技术深度,遇到问题可以快速定位并从根本上解决.有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 #### 相关阅读 [[小家Spring]聊聊Sp ...
- 深入理解Java:内省(Introspector)
深入理解Java:内省(Introspector) 内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传 ...
- Java:内省(Introspector)
内省(Introspector) 是Java 语言对 JavaBean 类属性.事件的一种缺省处理方法. JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且 ...
随机推荐
- SQL数据多条转单条(CONCAT_WS)
一.concat()函数可以连接一个或者多个字符串 concat(str1,str2,…) 返回结果为连接参数产生的字符串.如有任何一个参数为NULL ,则返回值为 NULL. select conc ...
- springboot整合Druid(德鲁伊)配置多数据源数据库连接池
pom.xml <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-ja ...
- C#和 JS的闭包
闭包的概念是内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止.但该 变量提供的值并非变量创建时的值,而是在父函数范围内的最终值. C#闭包可理解为跨作用域访问函数内变量,那么如何 ...
- spring学习(四)使用注解代替xml配置
用的是IDEA的maven工程,pom.xml文件导包依赖省略 一.书写要导入容器的实体类 import org.springframework.beans.factory.annotation.Va ...
- python如何支持并发?
由于GIL(Global Interpreter Lock)的存在使得在同一时刻Python进程只能使用CPU的一个核心,也就是对应操作系统的一个 内核线程,对于一个Python web程序,如果有个 ...
- sed打印包含一个字符串的行到包含另一个字符串的行解答
sed -n '/字符串1/,/字符串2/p' filename 这个命令为什么有时候打印不出来想要的东西,例如:sed -n '/root/,/adm/p' /etc/passwd 我 ...
- Bug--Mybatis报错:There is no getter for property named 'id' in 'class java.lang.Integer'
Mybatis 添加@Param("")注释就可以了
- 你不知道的Java引用
什么是引用 引用就是保存着一块地址(门牌号)的对象,就像C语言的指针那样,引用可以传递某个数据的地址,如果我们想拿到某一条数据,就要先找到他的地址,然后告诉计算机我去拿这个地址的数据,最后计算机就 ...
- 第四章 常用API(上)
4.1.Object类 描述:该类是所有类的最终根类 方法 描述 public boolean equals(Object obj) 表示某个其它对象是否"等于"此对象 publi ...
- PHP juliantojd() 函数
------------恢复内容开始------------ 实例 把儒略历法的日期转换为儒略日计数,然后再转换回儒略历法的日期: <?php$jd=juliantojd(6,20,2007); ...