大多数人不知道的Java知识 - Introspector(内省器)和 PropertyDescriptor(属性描述器)

949 阅读4分钟

聆听 沉淀 传播 … 关注微信公众号【Java之言】,助你放弃编程之路!


一、Introspector 简介

官方介绍:

The Introspector class provides a standard way for tools to learn about the properties, events, and methods supported by a target Java Bean.

For each of those three kinds of information, the Introspector will separately analyze the bean's class and superclasses looking for either explicit or implicit information and use that information to build a BeanInfo object that comprehensively describes the target bean.

java.beans.Introspector,即内省器。为访问Java Bean的属性事件方法提供了标准的处理方法。

对于Java Bean 的这3种信息,Introspector 内省器会分别地寻找分析 Java Bean 以及它的父类的 包括显示和隐式的信息,然后构建一个全面描述此Java Bean 的 BeanInfo 对象。

在Java中,JavaBean 是一种特殊的类,主要用于传递数据信息。例如DTOVO等,我们在业务或者模块之间传递信息,可以将信息封装到JavaBean中。 既然封装到JavaBean中,那就会有设置(setter)读取(getter)JavaBean中属性等操作。Introspector可以帮我们做到这件事,不过要注意,JavaBean中的getter和setter等方法要遵循某种规范。

底层是充分利用了反射的原理操作类属性。

package com.nobody;

/**
 * @Description JavaBean类
 * @Author Mr.nobody
 * @Date 2021/1/24
 * @Version 1.0
 */
public class Person {
    private String id;
    private String name;
    private int age;
    
	public Person(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
// 获取Person类的BeanInfo
BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);

二、BeanInfo 简介

官方介绍:

Use the BeanInfo interface to create a BeanInfo class and provide explicit information about the methods, properties, events, and other features of your beans.

简而言之,java.beans.BeanInfo 接口能够创建一个BeanInfo对象,这个对象能提供关于JavaBean的方法属性事件以及其他特征的明确信息。 其主要方法如下:

  • getPropertyDescriptors():获得属性描述器。
  • getBeanDescriptor():获得对象描述器。
  • getMethodDescriptors:获得方法描述器。

三、PropertyDescriptor 简介

官方介绍:

A PropertyDescriptor describes one property that a Java Bean exports via a pair of accessor methods.

java.beans.PropertyDescriptor,即属性描述器。表示 Java Bean 通过存储器方法导出的一个属性。你可以简单认为PropertyDescriptor里面封装了JavaBean的其中一个属性的相关信息(例如属性名,属性类型,get和set等方法)。其主要方法如下:

  • getName():获得属性名。
  • getPropertyType():获得属性类型。
  • getReadMethod():获得用于读取属性值的方法。
  • getWriteMethod():获得用于写入属性值的方法。
  • setReadMethod(Method readMethod):设置用于读取属性值的方法。
  • setWriteMethod(Method writeMethod):设置用于写入属性值的方法。
Person person = new Person(UUID.randomUUID().toString(), "Mr_nobody", 18);
BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);
PropertyDescriptor namePropertyDescriptor = new PropertyDescriptor("name", Person.class);
System.out.println("属性名:" + namePropertyDescriptor.getName());
System.out.println("属性类型:" + namePropertyDescriptor.getPropertyType());

Method namePropertyDescriptorReadMethod = namePropertyDescriptor.getReadMethod();
Object personName = namePropertyDescriptorReadMethod.invoke(person, null);
System.out.println("before personName:" + personName);

Method namePropertyDescriptorWriteMethod = namePropertyDescriptor.getWriteMethod();
namePropertyDescriptorWriteMethod.invoke(person, "Tony");
personName = namePropertyDescriptorReadMethod.invoke(person, null);
System.out.println("after personName:" + personName);

// 输出结果:
属性名:name
属性类型:class java.lang.String
before personName:Mr_nobody
after personName:Tony

四、BeanDescriptor 简介

官方介绍:

A BeanDescriptor provides global information about a "bean", including its Java class, its displayName, etc.

This is one of the kinds of descriptor returned by a BeanInfo object, which also returns descriptors for properties, method, and events.

java.beans.BeanDescriptor,即对象描述器。它提供了一个JavaBean的全局信息,例如JavaBean的类型,类名等信息。它是BeanInfo对象返回的一种描述器,其他描述器还有属性描述器,方法描述器,事件描述器等。

BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
// BeanDescriptor beanDescriptor = new BeanDescriptor(Person.class);
System.out.println(beanDescriptor.getBeanClass());
System.out.println(beanDescriptor.getName());

// 输出结果:
class com.nobody.Person
Person

五、案例实践

一般,使用最多的是 IntrospectorBeanInfoPropertyDescriptor,这三者结合起来使用。

需求:假设我们有一个Person对象,需要将此对象转换为Map对象。
分析:Map的特征是key-value键值对,则需要遍历Person对象的每个属性名以及对应的值,放入Map中即可。

package com.nobody;

import java.beans.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/1/24
 * @Version 1.0
 */
public class Demo {
    public static void main(String[] args) throws Exception {
        Person person = new Person(UUID.randomUUID().toString(), "Mr_nobody", 18);
        System.out.println(beanToMap(person));

    }

    private static Map<String, Object> beanToMap(Object object)
            throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        // 需要返回的Map
        Map<String, Object> map = new HashMap<>();
        // 获取BeanInfo对象
        BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
        // 获取所有属性描述器
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        // 遍历每一个属性描述器
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            // 属性名
            String name = propertyDescriptor.getName();
            // 过滤class属性
            if (!Objects.equals("class", name)) {
                // 获取属性的get方法
                Method readMethod = propertyDescriptor.getReadMethod();
                // 获取属性的值
                Object value = readMethod.invoke(object);
                map.put(name, value);
            }
        }
        return map;
    }
}
// 输出结果
{name=Mr_nobody, id=57e496c6-87bb-4750-b176-5ef4bfb58b35, age=18}

如果我们将Person类的getId()方法换一个方法名,或者去除此方法,会发生什么呢? 假如我们将getId()方法名改为getUid(),再执行上面的案例。

public String getUid() {
    return id;
}
Exception in thread "main" java.lang.NullPointerException
	at com.nobody.Demo.beanToMap(Demo.java:38)
	at com.nobody.Demo.main(Demo.java:17)

程序报错了,这就是前面我说的JavaBean的get和set方法为什么要遵循某种规则

原因很简单,因为在取得id这个属性的属性描述器时,我们获取到了属性名,但是因为get方法没有遵循规则,所以调用getReadMethod()获取不到方法,所以出现空指针。
所以,在使用内省器和属性描述器的时候,要注意,将属性的get和set方法名写标准即可,即 get + 属性名()get + 属性名()