内省是基于反射实现的,主要用于操作符合JavaBean规范的类。
JavaBean是一种特殊的Java类,通常用于封装多个属性为一个单一的对象。我们再复习下JavaBean规范:
- 必须要有一个公共无参构造;
- Javabean类不应有公共属性,属性都应该是 private;
- 为私有(private声明)属性提供符合命名规范的getter/setter方法;
- 应该要实现Serializeable接口(内省并不强要求这一条);
- javaBean类必须是一个公共类,将其访问属性设置为public。
内省机制通过反射获取属性描述器(PropertyDescriptor),然后可以方便地获取和设置属性值。
内省操作只针对JavaBean,只有符合JavaBean规则的类的成员才可以采用内省API进行操作。
内省的核心类在java.beans包下,主要类如下:
java.beans包
初识内省
内省就是转为JavaBean准备的,主要包括下面几个类:
- Introspector类:这是内省API的核心类,提供了获取BeanInfo对象的方法,例如Introspector.getBeanInfo()方法。
- BeanInfo类:这个类包含了关于一个对象的所有Bean属性信息,包括属性的描述符(PropertyDescriptor)。
- PropertyDescriptor类:表示一个JavaBean属性的信息,包括getter和setter方法。
以下是一个简单的示例,我们首先获取User的JavaBean信息BeanInfo,然后找到属性描述符PropertyDescriptor列表。
首先定义一个JavaBean:
@Data
public static class User {
private String username;
}
这里借助Lombok的@Data注解,生成标准JavaBean。
首先获取BeanInfo实例:
final BeanInfo beanInfo = Introspector.getBeanInfo(User.class);
此时beanInfo中就可以获取User类的所有定义:
- 通过getMethodDescriptors()可以获取方法描述;
- 通过getPropertyDescriptors()可以获取属性描述;
- 使用PropertyDescriptor可以生成指定属性的描述;
少年,至此你已经学会屠龙技,可以开始屠龙了。
来个小李子
小李子
话说小李子年轻时候真帅,可惜岁月是把杀猪刀~~
咳咳,言归正传,我们用上面学到的直接开始:
final BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class);
final User user = new User();
for (MethodDescriptor methodDescriptor : userBeanInfo.getMethodDescriptors()) {
System.out.println("Method Name: " + methodDescriptor.getName());
final ParameterDescriptor[] parameterDescriptors = methodDescriptor.getParameterDescriptors();
if (parameterDescriptors == null) {
continue;
}
for (ParameterDescriptor parameterDescriptor : parameterDescriptors) {
System.out.println("Parameter Name: " + parameterDescriptor.getName());
System.out.println("Parameter DisplayName: " + parameterDescriptor.getDisplayName());
}
}
// 遍历所有属性描述符
for (PropertyDescriptor prop : userBeanInfo.getPropertyDescriptors()) {
System.out.println("Property Name: " + prop.getName());
// 获取getter方法
final Method readMethod = prop.getReadMethod();
if (readMethod != null) {
Object value = readMethod.invoke(user);
System.out.println("Property Value: " + value);
}
if ("username".equals(prop.getName())) {
// 获取setter方法
final Method writeMethod = prop.getWriteMethod();
if (writeMethod != null) {
Object value = writeMethod.invoke(user, "看山");
System.out.println("Property Value: " + value);
}
}
}
System.out.println(user);
输出结果是:
Method Name: getClass
Method Name: wait
Method Name: notifyAll
Method Name: notify
Method Name: wait
Method Name: getUsername
Method Name: hashCode
Method Name: setUsername
Method Name: wait
Method Name: equals
Method Name: toString
Property Name: class
Property Value: class cn.howardliu.tutorials.core.reflect.IntrospectorDemo$User
Property Name: username
Property Value: null
Property Value: null
IntrospectorDemo.User(username=看山)
我们可以看到,MethodDescriptor方法描述符包含了User类的所有方法,包括父类的方法。
如果不是JavaBean呢?
我们再看一下,如果User不符合规范呢?
public static class UserNoSetter {
private String username;
public void username(String username) {
this.username = username;
}
@Override
public String toString() {
return "UserNoSetter{" +
"username='" + username + '\'' +
'}';
}
}
我们再看看上面的结果:
Method Name: getClass
Method Name: wait
Method Name: notifyAll
Method Name: notify
Method Name: wait
Method Name: hashCode
Method Name: setUsername
Method Name: wait
Method Name: equals
Method Name: toString
Method Name: username
Property Name: class
Property Value: class cn.howardliu.tutorials.core.reflect.IntrospectorDemo$UserNoSetter
UserNoSetter{username='null'}
可以看到,方法除了自定义的username和setUsername,其他都是相同的,但是setUsername我们定义成了builder模式,所以内省描述符也不认识了。