内省机制
一、什么是内省
1、内省和反射有什么区别?
反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。内省(IntroSpector)是Java 语言对 Bean 类属性、事件的一种缺省处理方法。
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。内省机制是通过反射来实现的,BeanInfo用来暴露一个bean的属性、方法和事件,以后我们就可以操纵该JavaBean的属性。
2、内省中的核心类
在Java内省中,用到的基本上就是上述几个类。 通过BeanInfo这个类就可以获取到类中的方法和属性。例如类 A 中有属性 name, 那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性,这就是默认的规则。 Java 中提供了一套 API 用来访问某个属性的 getter/setter 方法,通过这些 API 可以使你不需要了解这个规则(但你最好还是要搞清楚),这些 API 存放于包 java.beans 中,一般的做法是通过类 Introspector 的 getBeanInfo方法 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后我们就可以通过反射机制来调用这些方法,这就是内省机制。
JDK内省类库
- java.beans.Introspector:Introspector 类为通过工具学习有关受目标 Java Bean 支持的属性、事件和方法的知识提供了一个标准方法。
- java.beans.BeanInfo接口:希望提供有关其 bean 的显式信息的 bean 实现者可以提供某个 BeanInfo 类,该类实现此 BeanInfo 接口并提供有关其 bean 的方法、属性、事件等显式信息。
- java.beans.PropertyDescriptor:PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性。
二、示例一
使用内省,把注解中各个JavaBean中的字段值注入到方法的JavaBean对象中。
(1)JavaBean类(Person)
package StuIntrospector.StuAnnoIntro;
public class Person {
private String name;
private int age;
private String phone;
public Person(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
public Person() {
}
//省略setter和getter
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
'}';
}
}
(2)自定义的注解
package StuIntrospector.StuAnnoIntro;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age();
String phone();
}
(3)使用注解的类
package StuIntrospector.StuAnnoIntro;
public class TestAnnotation {
@MyAnnotation(name = "suxing",age = 21,phone = "19876545676")
public static void getPerson(Person person){
System.out.println(person);
}
}
到这里再明确一下问题,就是如何使用内省和反射技术,把注解中的值,自动装入到方法的形参person中去。
(4)主类(测试类)
package StuIntrospector.StuAnnoIntro;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
try {
Class testAnnoClazz = Class.forName("StuIntrospector.StuAnnoIntro.TestAnnotation");
Method method1 = testAnnoClazz.getMethod("getPerson", Person.class);
MyAnnotation myAnnotation = method1.getAnnotation(MyAnnotation.class);
Class personClazz = Class.forName("StuIntrospector.StuAnnoIntro.Person");
Person person = (Person)personClazz.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(personClazz);
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for (int i = 0;i<descriptors.length;i++){
if("name".equals(descriptors[i].getName())){
Method method2 = descriptors[i].getWriteMethod();
method2.invoke(person,myAnnotation.name());
}else if("age".equals(descriptors[i].getName())){
Method method2 = descriptors[i].getWriteMethod();
method2.invoke(person,myAnnotation.age());
}else if("phone".equals(descriptors[i].getName())){
Method method2 = descriptors[i].getWriteMethod();
method2.invoke(person,myAnnotation.phone());
}
}
//System.out.println(person);
method1.invoke(testAnnoClazz,person);
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码分析:
上述代码,第一步:首先使用反射机制获取使用我们自定义注解的类的Class对象,然后获取我们自定义注解所在的方法,再根据方法获取注解在该方法上的注解。
第二步:使用反射机制获取JavaBean类的Class类对象,然后实例化一个JavaBean对象。
第三步:使用内省类(Introspector)获取Bean的信息类(BeanInfo),然后根据BeanInfo类获取该Bean的所有属性(getter和setter方法),然后遍历这些属性,根据属性的名称,把注解中的各个字段通过反射调用设置到person对象中去,最后通过反射调用被注解修饰的方法,并传入person对象。
(5)测试结果
Person{name='suxing', age=21, phone='19876545676'}
三、示例二
读取配置文件中类的信息,并根据这些信息使用内省机制,构造该类的实例。
(1)配置文件(student.properties)
student = StuIntrospector.StudentFactory.Student
name = suxing
age = 21
gender = male
(2)JavaBean(Student类)
package StuIntrospector.StudentFactory;
public class Student {
private String name;
private int age;
private String gender;
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Student() {}
//getter和setter
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
(3)主类(生成Student对象的工厂类StudentFactory)
package StuIntrospector.StudentFactory;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class StudentFactory {
public static Properties stuProperties = new Properties();
static {
InputStream inputStream =Thread.currentThread().getContextClassLoader().getResourceAsStream("student.properties");
try {
stuProperties.load(inputStream);
//stuProperties.load(new BufferedInputStream(new FileInputStream(new File("student.properties"))));
} catch (Exception e) {
e.printStackTrace();
}
}
public static Student getStudent(String name){
String className = stuProperties.getProperty(name);
System.out.println(className);
try {
Class classStudent = Class.forName(className);
Student student = (Student) classStudent.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(classStudent);
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
for (int i = 0;i<descriptors.length;i++){
if ("name".equals(descriptors[i].getName())){
String nameValue = stuProperties.getProperty("name");
Method method = descriptors[i].getWriteMethod();
method.invoke(student,nameValue);
}else if("age".equals(descriptors[i].getName())){
String ageValue = stuProperties.getProperty("age");
Integer age = Integer.parseInt(ageValue);
Method method = descriptors[i].getWriteMethod();
method.invoke(student,age);
}else if("gender".equals(descriptors[i].getName())){
String genderValue = stuProperties.getProperty("gender");
Method method = descriptors[i].getWriteMethod();
method.invoke(student,genderValue);
}
}
return student;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
上述代码,
第一步,使用当前线程的类加载器把配置文件加载到内存,然后根据配置文件上的类名,获取该类(Student)的Class类对象,并创建一个该对象的实例,
第二步:根据第一步获取的Class类对象,获取该Bean的信息BeanInfo类,然后根据BeanInfo类获取Bean中的所有属性,遍历这些属性,根据属性名称,为student对象设置与配置文件中对应的属性值,然后返回该student对象。
(4)测试类
package StuIntrospector.StudentFactory;
public class TestStudent {
public static void main(String[] args) {
Student student = StudentFactory.getStudent("student");
System.out.println(student);
}
}
测试结果:
StuIntrospector.StudentFactory.Student
Student{name='suxing', age=21, gender='male'}