目录

内省 JavaBeans Introspector

在计算机科学中,内省是指计算机程序在运行时(Runtime)检查对象(Object)类型的一种能力,通常也可以称作“运行时类型检查"。

不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Runtime)可以访问、检测和修改它本身状态或行为的一种能力。

Java内省(Introspector)是Java语言对Bean类属性、方法,事件的一种缺省处理方法。只有符合JavaBean规则的类的成员才可以采用内省API进行操作。其操作方式是通过Introspector的getBeanInfo方法获取BeanInfo对象,再得到属性描述器PropertyDecriptor后再进行各种操作。

JavaBean的get/set方法名要遵循某种规则,即驼峰规则。如果不准寻,内省将无效。

一言以蔽之: Introspector 是操作 javaBean 的 API,用来访问某个属性的 getter/setter 方法。存放于包java.beans中。

Java Introspector API

/images/java/Introspector.png

Introspector

java.beans.Introspector,即内省。它提供了一套标准的访问Java Bean的属性,事件以及方法的处理方法。 对于Java Bean的这3种信息,Introspector会分析Java Bean以及它的父类的显示和隐式的信息,然后构建一个全面描述此Java Bean的BeanInfo对象。

BeanInfo

java.beans.BeanInfo是一个接口,它有几个默认的实现类,我们一般默认生成的BeanInfo对象其实是GenericBeanInfo类的实例。简而言之,BeanInfo对象能提供关于JavaBean的方法,属性,事件以及其他特征的明确信息。 其主要方法如下:

  • getPropertyDescriptors():获得所有属性描述器。
  • getBeanDescriptor():获得对象描述器。
  • getMethodDescriptors():获得所有方法描述器。
  • getEventSetDescriptors():获得所有事件描述器。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Demo {
    public static void main(String[] args) throws IntrospectionException {
        // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省
        BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);
        BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
        EventSetDescriptor[] eventSetDescriptors = beanInfo.getEventSetDescriptors();
    }
}

BeanDescriptor

java.beans.BeanDescriptor,即对象描述器。它提供了一个JavaBean的全局信息,例如JavaBean的类型,类名等信息。

我们一般是从BeanInfo对象获取BeanDescriptor对象,不过也可以直接通过new BeanDescriptor(Class beanClass)构造函数获取。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo {
    public static void main(String[] args) throws IntrospectionException {

        // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省
        BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);
        // 从BeanInfo对象获取BeanDescriptor对象
        BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
        // 通过new BeanDescriptor(Class<?> beanClass)构造函数获取BeanDescriptor对象
        // BeanDescriptor beanDescriptor = new BeanDescriptor(Person.class);

        Class<?> beanClass = beanDescriptor.getBeanClass();
        Class<?> customizerClass = beanDescriptor.getCustomizerClass();
        String displayName = beanDescriptor.getDisplayName();
        String name = beanDescriptor.getName();

        System.out.println("beanClass:" + beanClass);
        System.out.println("customizerClass:" + customizerClass);
        System.out.println("displayName:" + displayName);
        System.out.println("name:" + name);
    }
}

PropertyDescriptor

java.beans.PropertyDescriptor,即属性描述器。描述了Java Bean的一个属性,通过一对读取方法。即PropertyDescriptor里面封装了JavaBean的其中一个属性的相关信息(例如属性名,属性类型,get和set等方法)。其主要方法如下:

  • getName():获得属性名。
  • getPropertyType():获得属性类型。
  • getReadMethod():获得用于读取属性值的方法。
  • getWriteMethod():获得用于写入属性值的方法。
  • setReadMethod(Method readMethod):设置用于读取属性值的方法。
  • setWriteMethod(Method writeMethod):设置用于写入属性值的方法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Demo {
    public static void main(String[] args)
            throws IntrospectionException, InvocationTargetException, IllegalAccessException {

        // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省
        BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);

        Person person = new Person(UUID.randomUUID().toString(), "Mr_nobody", 18);

        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Class<?> propertyType = propertyDescriptor.getPropertyType();
            String propertyName = propertyDescriptor.getName();
            Method readMethod = propertyDescriptor.getReadMethod();
            Method writeMethod = propertyDescriptor.getWriteMethod();

            System.out.println("属性名:" + propertyName);
            System.out.println("属性类型:" + propertyType);
            System.out.println("写方法名:" + writeMethod.getName());
            System.out.println("读方法名:" + readMethod.getName());

            if ("age".equals(propertyName)) {
                writeMethod.invoke(person, 20);
            }
            System.out.println("属性值:" + readMethod.invoke(person));
        }
    }
}

MethodDescriptor

可以获得方法的元信息,比如方法名,参数个数,参数字段类型等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo {
    public static void main(String[] args) throws IntrospectionException {

        // 不内省父类的信息,第二个参数stopClass代表从stopClass开始往上的父类不再内省
        BeanInfo beanInfo = Introspector.getBeanInfo(Person.class, Object.class);

        MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors();
        for (MethodDescriptor methodDescriptor : methodDescriptors) {

            Method method = methodDescriptor.getMethod();
            System.out.println(method);
            System.out.println("方法名:" + method.getName());

            Type[] genericParameterTypes = method.getGenericParameterTypes();
            if (genericParameterTypes != null) {
                for (Type genericParameterType : genericParameterTypes) {
                    System.out.println("方法参数类型:" + genericParameterType.getTypeName());
                }
            }

            Class<?> returnType = method.getReturnType();
            System.out.println("方法返回类型:" + returnType.getTypeName());
        }
    }
}

应用

通过内省可以实现,JavaBean和Map互转,不同JavaBean对象属性拷贝等功能

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
public class BeanUtils {

    public static <T> Map<String, Object> beanToMap(T bean, boolean putIfNull)
            throws IntrospectionException, InvocationTargetException, IllegalAccessException {

        if (bean == null) {
            return new HashMap<>();
        }
        Map<String, Object> returnMap = new HashMap<>();

        // 获取bean的BeanInfo对象
        BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);

        // 获取属性描述器
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 属性名
            String propertyName = propertyDescriptor.getName();
            // 获取该属性的值
            Method readMethod = propertyDescriptor.getReadMethod();
            // 属性的值
            Object value = readMethod.invoke(bean);
            if (value == null && !putIfNull) {
                continue;
            }
            returnMap.put(propertyName, value);
        }
        return returnMap;
    }

    public static <T> List<Map<String, Object>> beansToMaps(List<T> beans, boolean putIfNull)
            throws IllegalAccessException, IntrospectionException, InvocationTargetException {

        if (null == beans || beans.size() == 0) {
            return new ArrayList<>();
        }

        List<Map<String, Object>> result = new ArrayList<>(beans.size() + 1);
        // 转换每一个bean
        for (Object bean : beans) {
            result.add(beanToMap(bean, putIfNull));
        }
        return result;
    }

    public static <T> T mapToBean(Map<String, Object> map, Class<T> clz)
            throws IllegalAccessException, InstantiationException, IntrospectionException,
            InvocationTargetException {

        // 生成bean实例
        T bean = clz.newInstance();
        if (null == map) {
            return bean;
        }

        BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 属性名
            String propertyName = propertyDescriptor.getName();
            // 获取属性值
            Object value = map.get(propertyName);
            // 写入属性值
            Method writeMethod = propertyDescriptor.getWriteMethod();
            writeMethod.invoke(bean, value);
        }
        return bean;
    }

    public static <T> List<T> mapsToBeans(List<Map<String, Object>> maps, Class<T> clz)
            throws InvocationTargetException, IntrospectionException, InstantiationException,
            IllegalAccessException {

        if (null == maps || maps.size() == 0) {
            return new ArrayList<>();
        }

        List<T> result = new ArrayList<>();
        for (Map<String, Object> map : maps) {
            result.add(mapToBean(map, clz));

        }
        return result;
    }

    public static <T1, T2> void copyProperties(T1 origin, T2 dest, boolean setNull,
            String[] excludeFieldNames)
            throws IntrospectionException, InvocationTargetException, IllegalAccessException {

        // 获取源类的BeanInfo对象
        BeanInfo originBeanInfo = Introspector.getBeanInfo(origin.getClass(), Object.class);
        // 获取源类的属性描述器
        PropertyDescriptor[] originPropertyDescriptors = originBeanInfo.getPropertyDescriptors();

        // 获取目标类的BeanInfo对象
        BeanInfo destBeanInfo = Introspector.getBeanInfo(dest.getClass(), Object.class);
        // 获取目标类的属性描述器
        PropertyDescriptor[] destPropertyDescriptors = destBeanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : destPropertyDescriptors) {
            String propertyName = propertyDescriptor.getName();
            // 是否需要排除的属性
            boolean excludeField = false;
            if (excludeFieldNames != null) {
                for (String excludeFieldName : excludeFieldNames) {
                    if (Objects.equals(excludeFieldName, propertyName)) {
                        excludeField = true;
                        break;
                    }
                }
            }
            if (excludeField) {
                continue;
            }
            // 遍历源类的所有属性,如果存在此属性则进行拷贝
            for (PropertyDescriptor originPropertyDescriptor : originPropertyDescriptors) {
                String originPropertyName = originPropertyDescriptor.getName();
                if (Objects.equals(propertyName, originPropertyName)) {
                    // 读取属性值
                    Method readMethod = originPropertyDescriptor.getReadMethod();
                    Object srcValue = readMethod.invoke(origin);
                    if (srcValue != null || setNull) {
                        // 设置属性值
                        Method writeMethod = propertyDescriptor.getWriteMethod();
                        writeMethod.invoke(dest, srcValue);
                    }
                    break;
                }
            }
        }
    }

    public static <T1, T2> void copyProperties(T1 origin, T2 dest)
            throws IllegalAccessException, IntrospectionException, InvocationTargetException {
        copyProperties(origin, dest, false, null);
    }
}

反射 与 内省

  • class 信息

    • 构造器(Contructor)
    • 方法(Method)
    • 字段(Field)
  • BeanInfo

    • Bean描述符(BeanDecriptor)
    • 方法描述符(MethodDescriptor)
    • 属性描述符(PropertyDescriptor)

Spring 中的内省

BeanUtils.copyProperties 使用内省。

在 Spring 的自动注入的源码中,用到了内省(Introspector),Spring 会先找到全部的set和is方法,然后执行这些set和is方法注入属性。

如何找到这些set,is方法,JDK提供了内省(Introspector)API。

Spring Bean 属性处理

beans包里一个非常重要的类是BeanWrapper接口和它的相应实现(BeanWrapperImpl)。引用自java文档,BeanWrapper提供了设置和获取属性值(单独或批量)、获取属性描述符以及查询属性以确定它们是可读还是可写的功能。BeanWrapper还提供对嵌套属性的支持,能够不受嵌套深度的限制启用子属性的属性设置。然后,BeanWrapper提供了无需目标类代码的支持就能够添加标准JavaBeans的PropertyChangeListeners和VetoableChangeListeners的能力。最后然而并非最不重要的是,BeanWrapper提供了对索引属性设置的支持。BeanWrapper通常不会被应用程序的代码直接使用,而是由DataBinder和BeanFactory使用。

  • 属性修改器 (Java.beans.PropertyEditor)
  • 属性修改器注册 (PropertyEditorRegistry)
  • PropertyEditor 注册器 (PropertyEditorRegistrar)
  • 自定义 PropertyEditor 配置器 (CustomEditorConfigurer)

Spring 中 PropertyEditorRegistry 默认实现

  • org.springframework.beans.PropertyEditorRegistrySupport

Spring 对 PropertyEditorRegistry 扩展

  • org.springframework.beans.PropertyEditorRegistrar
  • org.springframework.beans.support.ResourceEditorRegistrar

IntrospectorCleanupListener

在web.xml配置,

1
2
3
4
<!-- 解决java.beans.Introspector导致的内存泄漏的问题(必须放在listener第1个位置) -->
<listener>
   <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>

作用解释: JDK中的java.beans.Introspector类的用途是发现Java类是否符合JavaBean规范, 如果有的框架或程序用到了Introspector类,那么就会启用一个系统级别的缓存,此缓存会存放一些曾加载并分析过的JavaBean的引用。

当Web服务器关闭时,由于此缓存中存放着这些JavaBean的引用,所以垃圾回收器无法回收Web容器中的JavaBean对象,最后导致内存变大。

而org.springframework.web.util.IntrospectorCleanupListener就是专门用来处理Introspector内存泄漏问题的辅助类。

IntrospectorCleanupListener会在 Web服务器停止时清理Introspector缓存,使那些Javabean能被垃圾回收器正确回收。

Spring自身不会出现这种问题,因为Spring在加载并分析完一个类之后会马上刷新JavaBeans Introspector缓存,这就保证Spring中不会出现这种内存泄漏的问题。

但有些程序和框架在使用了JavaBeans Introspector之后,没有进行清理工作(如 Quartz,Struts),最后导致内存泄漏.