聊聊Java内省Introspector

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 这篇文章主要分析一下Introspector(内省,应该读xing第三声,没有找到很好的翻译,下文暂且这样称呼)的用法。Introspector是一个专门处理JavaBean的工具类,用来获取JavaBean里描述符号,常用的JavaBean的描述符号相关类有BeanInfo、PropertyDescriptor,MethodDescriptor、BeanDescriptor、EventSetDescriptor和ParameterDescriptor。下面会慢慢分析这些类的使用方式,以及Introspector的一些特点。

微信截图_20220513164925.png


前提



这篇文章主要分析一下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)

相关文章
|
5月前
|
存储 安全 Java
Java内省(Introspector)机制:深入理解与应用
Java内省(Introspector)机制:深入理解与应用
java.beans.* 篇(1)Beans,BeanInfo,BeanDescriptor,Introspector,PropertyChangeSupport,EventHandler使用案例
java.beans.* 篇(1)Beans,BeanInfo,BeanDescriptor,Introspector,PropertyChangeSupport,EventHandler使用案例
|
Java API
Java 内省
PropertyDescriptor:属性描述符,能够描述javaBean中的属性,通过属性描述符我们能知道这个属性的类型,获取到操纵属性的方法(getter/setter)
132 0
|
Java Spring 容器
Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(上)
Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(上)
Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(上)
|
存储 缓存 Java
聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(下)
聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(下)
聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(下)
|
Java API
20 Java 使用反射(Reflection) 和内省技术
反射(Reflection)是程序的自我分析能力,通过反射可以确定类有哪些方法、有哪些构造方法以及有哪些成员变量。Java语言提供了反射机制,通过反射机制能够动态读取一个类的信息;能够在运行时动态加载类,而不是在编译期。反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、创建对象,以及调用方法和成员变量。
126 0
|
XML 搜索推荐 Java
Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(下)
Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(下)
|
缓存 Java 数据库连接
聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(上)
聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】(上)
|
Java Shell API
大多数人不知道的Java知识 - Java内省机制
大多数人不知道的Java知识 - Java内省机制
|
6天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?