java的内省相当于反射的一个特例,他专门用来操作java的Bean对象的属性。
在“容器的应用”笔记里,我们已经了解了javaBean是什么,首先我们再来学习什么是javaBean的属性:
一个类,里面有自己的private字段,只能叫做字段,如果他对外提供了set或get方法,他才能叫做属性。也就是说,javaBean的属性是由set和get方法决定是不是有这个属性的,而不是由这个类里面的字段决定的。
举个例子,在一个叫做Person的javaBean里,没有nation这个字段,但我们提供了一个方法:
public String getNation() {
return null;
}
这样,这个javaBean对外就拥有nation这个属性,虽然为null。
而且,如果再加入另一个一模一样的get方法:
public String getNation() {
return null;
}
这样,这个javaBean对外就又有了另外一个属性。
接下来我们就来了解内省类:
我们用Person这个javaBean来做内省测试的来源,person类的代码如下:
public class Person {
private String name;
private String ID;
private int age;
public String getNation() {
return null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getID() {
return ID;
}
public void setID(String iD) {
ID = iD;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
内省的核心api就是Introspector类,这个类里有一个getBeanInfo方法,可以内省javaBean并了解他的所有属性,暴露的方法和事件,内省完成,封装起来返回值就是一个BeanInfo对象。
那么拿到的属性,这个BeanInfo对象里,有对应的方法,比如getPropertyDescriptors(),就可以获取每一个属性的属性描述器,返回值就是一个PropertyDescriptor[]这样一个数组,里面就是保存着属性的描述。
进而,PropertyDescriptor类里有对应的:getReadMethod()方法和getWriteMethod()方法,可以获取到应该用于读和写属性值的方法,返回值是一个Method,也就是获得到get和set方法。
针对上面创建好的javaBean,我们写测试类来测试内省
@Test
public void test1() throws Exception {
//用getBeanInfo方法获得一个JavaBean的BeanInfo,参数是一个Bean类
BeanInfo info=Introspector.getBeanInfo(Person.class);
//接着调用getPropertyDescriptors获取所有属性描述器
//返回值是数组,获取了所有属性描述器,要到具体的某一个需要for循环
PropertyDescriptor[] descriptors=info.getPropertyDescriptors();
//先用getName输出所有的属性名
for(PropertyDescriptor des:descriptors) {
System.out.println(des.getName());
}
}
可以看到输出结果:
对比上面的JavaBean类Person,可以看到确实属性的个数是由set和get方法决定的,因为确实getName到了一个nation属性。
同时我们也发现,多出了一个class属性,这是什么呢,是从父类Object类里继承来的一个属性,虽然我们并没有写。如果想要得到的属性描述器只有我们自己写进去的,可以这样进行操作:
我们打开getPropertyDescriptors方法的源码,可以看到有很多重载的方法,里面有这样一个:
public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
输入两个参数的时候,可以有一个stop的Class,我们stop掉父类Object的BeanInfo:
BeanInfo info=Introspector.getBeanInfo(Person.class,Object.class);
然后重新运行一下这个测试方法:
可以看到只剩下我们自己写进这个Bean类里的四个属性了。
接下来我们对获取到的属性进行操作,在上面我们用getPropertyDescriptors方法,获取到的是所有属性包含在内的一个PropertyDescriptor数组,但是如果我们已经知道了里面有哪些属性,可以针对具体的一个属性进行操作,我们只用创建一个新的PropertyDescriptor对象,用构造器传入对应的参数包括属性名和Bean类:
PropertyDescriptor descriptors=new PropertyDescriptor("age", Person.class);
然后我们就可以获得对应的写方法或者读方法,我们针对age属性获得写方法为例:
Method method=descriptors.getWriteMethod();
接着invoke调用一下这个方法(因为invoke的参数里要针对一个对象调用,我们要new一个Bean对象):
Person person=new Person();
method.invoke(person, 22);
这时候已经成功内省了个属性的写set方法,然后我们输出看看是不是写进去了:
System.out.println(person.getAge());
此时测试就会输出结果是22.
同理,对于get方法,我们也可以通过内省来获得,然后用invoke调用,下一个测试方法以属性name为例:
@Test
public void test3() throws Exception {
BeanInfo info=Introspector.getBeanInfo(Person.class,Object.class);
PropertyDescriptor descriptors=new PropertyDescriptor("name", Person.class);
//接着获得对应的 写 方法 ,和 读 方法
Method method=descriptors.getWriteMethod();
Method method2=descriptors.getReadMethod();
Person person=new Person();
method.invoke(person, "john");
//把get方法也进行引用
System.out.println(method2.invoke(person, null));
}
同样,像反射的时候我们可以获得某个方法的返回值的类型,在内省的时候也一样可以获得这个属性的类型:
@Test
public void test4() throws Exception {
BeanInfo info=Introspector.getBeanInfo(Person.class,Object.class);
PropertyDescriptor descriptors=new PropertyDescriptor("name", Person.class);
//getPropertyType方法获得这个属性的类型
System.out.println(descriptors.getPropertyType());
}
这个测试方法输出结果就是,这个name属性的类型: