什么是 内省(Introspector) ?

Java 内省(Introspector)提供了根据 Java 类元信息获取 JavaBean 信息的接口,底层使用了反射实现。

什么是 JavaBean ?

JavaBean 是方法名具备某些命名规范的Java 类,封装了属性,方法和事件, 在 Java 中使用接口 BeanInfo 表示,通过该接口可以轻松获取到 JavaBean 的信息。

通过对 Introspector 源码的阅读可以了解到 JavaBean 方法命名的规范如下。

JavaBean 属性:

JavaBean 的属性使用 Java 类 PropertyDescriptor 描述,一个 JavaBean包含多个 PropertyDescriptor 。 PropertyDescriptor 主要包含属性的类型、读方法、写方法,属性信息根据方法获取,并不要求一定存在和 set/get 方法对应的成员变量,不要求读方法和写方法同时存在。

  • 属性类型:为读方法的返回值类型,写方法参数类型需要和读方法返回值类型一致或者为读方法返回值类型的子类。

JavaBean 属性方法规范

  • 读方法(readMethod)
  • 返回值类型:不能为 void 。
  • 方法名:一般以get开头,返回值类型为 boolean 时以 is开头,只包含一个类型为 int 的参数时必须以 get 开头。
  • 参数:参数一般为空,或包含一个 int 类型的参数用于表示索引位置。
  • 写方法(writeMethod)
  • 返回值类型:必须为void 。
  • 方法名:必须以 set开头 。
  • 参数:可以有1个或者2个参数,有两个参数时第一个参数类型为 int 。

如何使用 Introspector 获取 PropertyDescriptor

创建父类 Person

package com.zuhkp.java.basic.introspector;

/**
 * @author zzuhkp
 * @date 2020-05-29 17:41
 * @since 1.0
 */
public class PersonBean {

    public String getWork() {
        return null;
    }
}2

创建子类 UserBean

package com.zuhkp.java.basic.introspector;

/**
 * Java Bean
 *
 * @author zzuhkp
 * @date 2020-05-28 17:35
 * @since 1.0
 */
public class UserBean extends PersonBean {

    /**
     * 只有 writeMethod 方法的属性 name
     *
     * @param name
     */
    public void setName(String name) {
    }

    /**
     * 只有 readMethod 的属性 age
     *
     * @return
     */
    public Integer getAge() {
        return null;
    }

    /**
     * writeMethod 方法参数类型可以为 writeMethod 方法返回值类型的子类
     *
     * @param age
     */
    public void setAge(Integer age) {

    }

    /**
     * 同时具有 readMethod 和 writeMethod 方法的属性 health
     * <p>
     * 该方法为 readMethod 方法
     * <p>
     * readMethod 方法可以是以 is 开头并且返回值为 boolean 类型的方法或者是 以 get开头的方法
     *
     * @return
     */
    public boolean isHealth() {
        return false;
    }

    /**
     * 同时具有 readMethod 和 writeMethod 方法的属性 health
     * <p>
     * 该方法为 writeMethod 方法
     *
     * @param health
     */
    public void setHealth(boolean health) {

    }

    /**
     * 非表示属性的方法
     */
    public void doWork() {

    }
}

至此,我们的类型为 UserBean 的 JavaBean 已经创建完毕,那么如何获取它的属性信息呢?

先看一个手动获取的方法

package com.zuhkp.java.basic.introspector;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;

/**
 * java内省示例
 *
 * @author zzuhkp
 * @date 2020-05-28 17:33
 * @since 1.0
 */
public class IntrospectorDemo {

    public static void main(String[] args) throws IntrospectionException {
        PropertyDescriptor ageProperty = new PropertyDescriptor("age", UserBean.class, "getAge", "setAge");
        System.out.println(ageProperty);
    }
}

打印结果如下:

java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.zuhkp.java.basic.introspector.UserBean.getAge(); writeMethod=public void com.zuhkp.java.basic.introspector.UserBean.setAge(java.lang.Integer)]

属性名称,属性类型,读方法和写方法一目了然。age属性的描述对象由我们手工创建,如果有几十个属性,创建还是比较累的,手工创建的描述对象读方法的返回值类型和写方法的参数类型必须保持一致,否则将抛出异常。而使用 Introspector 则可以更快捷的获取 JavaBean 的所有属性,并且写方法的参数类型只要为读方法返回值类型的子类即可。下面看如何使用Introspector获取 JavaBean 的所有属性信息。

修改我们的测试类 IntrospectorDemo 为如下代码

package com.zuhkp.java.basic.introspector;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.util.stream.Stream;

/**
 * java内省示例
 *
 * @author zzuhkp
 * @date 2020-05-28 17:33
 * @since 1.0
 */
public class IntrospectorDemo {

    public static void main(String[] args) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(UserBean.class, Object.class);
        Stream.of(beanInfo.getPropertyDescriptors()).forEach(System.out::println);
    }
}

打印结果如下:

java.beans.PropertyDescriptor[name=age; propertyType=class java.lang.Integer; readMethod=public java.lang.Integer com.zuhkp.java.basic.introspector.UserBean.getAge(); writeMethod=public void com.zuhkp.java.basic.introspector.UserBean.setAge(java.lang.Integer)]
java.beans.PropertyDescriptor[name=health; propertyType=boolean; readMethod=public boolean com.zuhkp.java.basic.introspector.UserBean.isHealth(); writeMethod=public void com.zuhkp.java.basic.introspector.UserBean.setHealth(boolean)]
java.beans.PropertyDescriptor[name=name; propertyType=class java.lang.String; writeMethod=public void com.zuhkp.java.basic.introspector.UserBean.setName(java.lang.String)]
java.beans.PropertyDescriptor[name=work; propertyType=class java.lang.String; readMethod=public java.lang.String com.zuhkp.java.basic.introspector.PersonBean.getWork()]

可以看到,我们不仅获取了子类的属性,而且获取了父类中的属性,并且读方法和写方法并不要求同时存在,而不满足属性方法规范的方法doWork则不作为work的读方法或者写方法。

JavaBean 方法:

JavaBean 的公共方法在 Java 中使用类 MethodDescriptor 描述,一个 JavaBean 可以包含多个 MethodDescriptor 。

JavaBean 方法来源

JavaBean的方法为包含父类和自身的公共方法,底层调用了反射中的方法 java.lang.Class#getMethods获取。

如何使用 Introspector获取 MethodDescriptor

修改测试类如下:

package com.zuhkp.java.basic.introspector;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.util.stream.Stream;

/**
 * java内省示例
 *
 * @author zzuhkp
 * @date 2020-05-28 17:33
 * @since 1.0
 */
public class IntrospectorDemo {

    public static void main(String[] args) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(UserBean.class);
        Stream.of(beanInfo.getMethodDescriptors()).forEach(System.out::println);
    }
}

程序执行结果如下:

java.beans.MethodDescriptor[name=getWork; method=public java.lang.String com.zuhkp.java.basic.introspector.PersonBean.getWork()]
java.beans.MethodDescriptor[name=setAge; method=public void com.zuhkp.java.basic.introspector.UserBean.setAge(java.lang.Integer)]
java.beans.MethodDescriptor[name=setName; method=public void com.zuhkp.java.basic.introspector.UserBean.setName(java.lang.String)]
java.beans.MethodDescriptor[name=getAge; method=public java.lang.Integer com.zuhkp.java.basic.introspector.UserBean.getAge()]
java.beans.MethodDescriptor[name=isHealth; method=public boolean com.zuhkp.java.basic.introspector.UserBean.isHealth()]
java.beans.MethodDescriptor[name=doWork; method=public void com.zuhkp.java.basic.introspector.UserBean.doWork()]
java.beans.MethodDescriptor[name=setHealth; method=public void com.zuhkp.java.basic.introspector.UserBean.setHealth(boolean)]

可以看到已经成功打印出所有的公共方法。

JavaBean 事件:

JavaBean 的中的事件信息使用类 EventSetDescriptor 描述,一个 JavaBean 可以包含多个 EventSetDescriptor 。事件信息主要包括添加事件监听器方法、移除事件监听器方法、获取事件监听器列表方法。

JavaBean 中的事件监听器相关方法规范

  • 添加事件监听器方法
  • 方法返回值:必须为 void 。
  • 方法名称:必须以 add 开头,以唯一的方法参数的类型名称结尾。
  • 方法参数:只能有一个参数类型为 EventListener 接口实现类的参数。
  • 其他限制:方法必须有对应的 remove 方法,否则该方法不构成 添加 JavaBean 添加事件监听器的方法。
  • 移除事件监听器方法
  • 方法返回值:必须为 void 。
  • 方法名称:必须以 remove 开头,以唯一的方法参数的类型名称结尾。
  • 方法参数:只能有一个参数类型为 EventListener 接口实现类的参数。
  • 其他限制:方法必须有对应的 add 方法,否则该方法不构成 添加 JavaBean 添加事件监听器的方法。
  • 获取事件监听器列表方法
  • 方法返回值:方法的返回值必须为数组,数组的元素类型必须为 EventListener 接口的实现类。
  • 方法名称:必须以 get 开头,紧跟其后的应为返回值元素类型的名称,然后后面紧跟一位字符,如 getClickedEventListeners 。
  • 方法参数:无参数。

如何使用 Introspector 获取 EventSetDescriptor

为 UserBean 添加方法如下:

package com.zuhkp.java.basic.introspector;

/**
 * Java Bean
 *
 * @author zzuhkp
 * @date 2020-05-28 17:35
 * @since 1.0
 */
public class UserBean extends PersonBean {

    /**
     * Java Bean 添加事件监听器的方法
     * <p>
     * 方法的返回值只能为 void
     * <p>
     * 方法只能有一个类型为EventListener实现类的参数
     * <p>
     * 方法名称以 add 开头,以参数类型的名字结尾
     * <p>
     * 方法必须有对应的remove方法
     *
     * @param userEventListener
     */
    public void addUserEventListener(UserEventListener userEventListener) {

    }

    /**
     * Java Bean 移除事件监听器的方法
     * <p>
     * 方法的返回值只能为 void
     * <p>
     * 方法只能有一个类型为EventListener实现类的参数
     * <p>
     * 方法名称以 remove 开头,以参数类型的名字结尾
     *
     * @param userEventListener
     */
    public void removeUserEventListener(UserEventListener userEventListener) {

    }

    /**
     * Java Bean 获取事件监听器列表的方法
     * <p>
     * 方法的返回值必须为数组,数组的元素类型必须为 EventListener 接口的实现类
     * <p>
     * 方法名必须以 get 开头,紧跟其后的应为返回值元素类型的名称,然后后面紧跟一位字符
     * <p>
     * 方法必须无参数
     *
     * @return
     */
    public UserEventListener[] getUserEventListeners() {
        return null;
    }
    
}

修改测试类如下:

package com.zuhkp.java.basic.introspector;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.util.stream.Stream;

/**
 * java内省示例
 *
 * @author zzuhkp
 * @date 2020-05-28 17:33
 * @since 1.0
 */
public class IntrospectorDemo {

    public static void main(String[] args) throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(UserBean.class, Object.class);
        Stream.of(beanInfo.getEventSetDescriptors()).forEach(System.out::println);
    }
}

执行结果如下:

java.beans.EventSetDescriptor[name=userEvent; inDefaultEventSet; listenerType=class com.zuhkp.java.basic.introspector.UserEventListener; getListenerMethod=public com.zuhkp.java.basic.introspector.UserEventListener[] com.zuhkp.java.basic.introspector.UserBean.getUserEventListeners(); addListenerMethod=public void com.zuhkp.java.basic.introspector.UserBean.addUserEventListener(com.zuhkp.java.basic.introspector.UserEventListener); removeListenerMethod=public void com.zuhkp.java.basic.introspector.UserBean.removeUserEventListener(com.zuhkp.java.basic.introspector.UserEventListener)]

可以看到已经获取到了 JavaBean 中的事件监听器类型,添加、移除、获取事件监听器的方法。