为什么要学习内省
开发框架时,经常需要使用java对象的属性来封装程序的数据,每次都使用反射技术
完整此类操作过于麻烦,所以sun公司开发了一套API专门用于操作java对象的属性
什么是java对象的属性和属性的读写方法?
内省访问javaBean属性的两种方式?
1. 通过PropertyDescriptor类操作Bean的属性
2. 通过Intorspector类获得Bean对象的BeanInfo,然后用过BeanInfo来获取属性的描述器
(PropertyDescriptor),通过这个描述器就可以获取某个属性对应的getter、setter方法,
然后通过反射机制来调用这些方法
JavaBean是一种方法符合某种特定规则的特殊java类。
javaBean类里面的变量为私有,通过公共方法setX、getX;设置和拿到里面的方法
如下图:就是一个javaBean类
package cn.itcast.day1;
public class ReflectPoint {
private int x;
private int y; //这个只是字段,只有对外提供了get,set方法才能成为属性
//javabean有对应的get、set方法就是bean方法 如果单独getAB()那么Bean就有AB属性 由于是从Object类继承 那么还有一个属性getClass类
public int getAB() {
return x;
}
public ReflectPoint(int x, int y){
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
javaBean的内省操作
new一个ReflectPoint对象
ReflectPoint pt1 = new ReflectPoint(3, 5);
//定义一个名字属性
String propertyName = "x";
//在pt1上调用拿到属性的方法
Object retVal = getProperty(pt1,propertyName);
定义一个getProperty(ReflectPointpt1, String propertyName);
带参的方法里面传两个参数,ReflectPointpt1和String propertyName
Introspector类为通过工具学习有关目标Java Bean支持的属性、事件和方法的知识提供了一个标准的方法。
是内省的入口类 这个类到底省谁?就要调用BeanInfo方法,然后把传入的bean类省出来,把所有的属性封装到BeanInfo方法中,拿到BeanInfo就相当于拿到了Bean的所有属性虽然拿到的是接口,这个接口负责返回接口的实例,相当于拿到对象了,就相当于叨叨所有的属性了,然后调用getPropertyDescriptors()方法,就可以拿到每一个属性的属性描述器,然后返回一个数组,数组里面装的是每一个属性的属性描述器,进而就可以调用属性描述其的getReadMethd获取应该属性的读取方法,相当于get方法,进而调用getWriteMethd就可以调用属性的写方法,得到属性的set方法就可以设置属性
使用内省API操作Bean属性
public static void test2() throws Exception {
Person p = new Person();
//创建一个属性构造器,第一个参数传要修改的属性名,第二个传Bean类的字节码
PropertyDescriptor pd = new PropertyDescriptor("age",Person.class);
//调用属性构造器的getWriteMethod()对其属性进行set操作。。。得到属性的写方法对其进行赋值
Method md = pd.getWriteMethod(); //public void setAge()
//调用invoke方法,第一个参数传Bean类,第二传要设置的值
md.invoke(p, 45);
//System.out.println("p.age:="+p.getAge());
//通过调用获取read方法
md = pd.getReadMethod();
System.out.println(md.invoke(p, null));
}
//高级点的内容
public static void test3() throws Exception {
Person p = new Person();
//创建一个属性构造器
PropertyDescriptor pd = new PropertyDescriptor("age",Person.class);
Class<?> cls = pd.getPropertyType();
System.out.println(cls);
}
//高级点的内容
public static void test3() throws Exception {
Person p = new Person();
//创建一个属性构造器
PropertyDescriptor pd = new PropertyDescriptor("age",Person.class);
Class<?> cls = pd.getPropertyType();
System.out.println(cls);
}
对于这三种信息,Instrospector将分别分析bean的类和超类,寻找显示和隐式信息,使用这些信息构建一个全面描述目标bean和beanInfo对象。
对于每个“Foo”类,如果存在相应的“FooBeanInfo”类,显式信息可能是可用的,查询这些信息时,FooBeanInfo类会提供一个非Null值。通过获得目标bean类的完全受限定包名称并追加“BeanInfo”形成一个新类,首先查找BeanInfo类。如果此操作失败,则采用此名称的最终类名称组件,在BeanInfo包搜索路径中指定的每个包中搜索该类。
因此对于某个类,比如说“sun.xyz.OurButton”,首先需要查找称为
“sun.xyz.OurButtonInfo”的BeanInfo类,如果失败,则查找BeanInfo搜索路径,这意味着将查找“sun.beans.infos.OurButtonBeanInfo”.
如果没有再某个类提供有关其自身的显式BeanInfo,则将从分析其他派生类得到的信息添加到BeanInfo信息中,并将显式信息视为当前类和基类的权威信息,无需进一步深入超类链接进行分析。
如果没有再某个类上发现显式BeanInfo,则使用低层次的反射来研究类的方法,并标准设计模式来标识属性储存器,事件源或公共方法。然后深入分析的超类,从他那里(可能超类链的顶部添加信息)
PropertyDescriptor
public class PropertyDescriptor extends FeatureDescriptor
描述 Java Bean 通过一对存储器方法导出的一个属性。
使用Introspector类中的静态方法getBeanInfo()方法
getBeanInfo(Class<?>beanClass) beanClass –是将要分析的 bean 类。
FeatureDescriptor类是 PropertyDescriptor、EventSetDescriptor 和MethodDescriptor 等的公共基类。
它支持一些可以设置和检索任意的内省描述符(introspection descriptor)的公共信息。
此外,它还提供了一种扩展机制,从而任意属性/值对都可以与设计特性相关联。
内省(IntroSpector)是Java语言对JavaBean类属性、事件的一种缺省处理方法。
例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。
通过getName/setName来访问name属性,这就是默认的规则。
Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中。
一般的做法是通过类Introspector的getBeanInfo方法获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。
我们又通常把javabean的实例对象称之为值对象(Value Object),因为这些bean中通常只有一些信息字段和存储方法,没有功能性方法。
一个JavaBean类可以不当JavaBean用,而当成普通类用。JavaBean实际就是一种规范,当一个类满足这个规范,这个类就能被其它特定的类调用。一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。去掉set前缀,然后取剩余部分,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
除了反射用到的类需要引入外,内省需要引入的类如下所示,它们都属于java.beans包中的类,自己写程序的时候也不能忘了引入相应的包或者类。下面代码片断是设置某个JavaBean类某个属性的关键代码:
private static Object getProperty(ReflectPoint pt1, String propertyName) throws Exception {
// Introspector为学习javaBean支持的属性、事件、方法提供了一套标准的方法
// 在Java Bean上进行内省,了解其所有属性、公开的方法和事件。
// getBeanInfo(Class<?> beanClass)里面传入的参数是 beanClass –是将要分析的 bean类
BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());
//得到所有属性的描述
// PropertyDescriptor类是用来描述Java Bean通过一对存储器方法导出的一个属性。
// Property属性PropertyDescriptor属性描述器
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
Object retVal = null;
for(PropertyDescriptor pd : pds){
//在数组中查找有没有与之相同的方法,如果有获取方法的对象,则调用此方法
// pd.getName()是PropertyDescriptor从父类FeatureDescriptor继承来的方法
//获得此特性的编程名称。 返回:属性/方法/事件的编程名称
if(pd.getName().equals(propertyName)){
// Method getReadMethod() 获得应该用于读取属性值的方法。
//应该用于读取属性值的方法。如果无法读取该属性,则可能返回。
Method methodGetX = pd.getReadMethod();
//调用此方法
retVal = methodGetX.invoke(pt1);
}
}
return retVal;
}
}
通过set Property(pt1,propertyName,value);对其属性进行设置
Object value = 7;
setProperty(pt1,propertyName,value);
System.out.println(pt1.getX());
set Property ReflectPoint pt1, StringpropertyName,Object value)
传入三个参数
1. 是要拿到的javaBean对象ReflectPoint pt1
2. 拿到属性name名字String propertyName 3.传入设置的value值
通过PropertyDescriptor的构造方法PropertyDescriptor(StringpropertyName, Class<?> beanClass) 新建一个对象
再设置写入的方法
最后调用方法
private static void setProperty(ReflectPoint pt1, String propertyName,
Object value) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
//构造方法PropertyDescriptor(String propertyName, Class<?> beanClass)
//属性名字,和bean类的字节码
//通过调用 getFoo 和 setFoo 存储器方法,为符合标准 Java 约定的属性构造一个 PropertyDescriptor
PropertyDescriptor pd2 = new PropertyDescriptor(propertyName,pt1.getClass());
//获得应该用于写入属性值的方法。
Method methodSetX = pd2.getWriteMethod();
methodSetX.invoke(pt1, value);
}
使用BeanUtils工具,直接可以设置里面的属性
public static void test1() throws Exception {
Person p = new Person();
//(Object bean,String name,Object value)
//第一个是bean 第二个是bean属性的名字,第三个是值
BeanUtils.setProperty(p, "age", 35);
System.out.println(p.getAge());
}
在设置表单的情况下接受过来的String类型,但是可以通过BeanUtils工具直接进行转化
转化只支持八种基本数据类型
public static void test2() throws Exception {
String name = "aaaa";
String age = "34";
Person p = new Person();
BeanUtils.setProperty(p, "name", name);
//由于age是int类型,那么在BeanUtils后面做了很多的工作
//自动把String转化成了整数赋值上去了
BeanUtils.setProperty(p, "age", age);
System.out.println(p.getName());
System.out.println(p.getAge());
}
在里面添加转化器,由于BeanUtils只支持8种基本数据类型转化,所以对Date类型格式的转换要写进去一个构造器,Converter();由于是一个接口,写一个匿名内部类直接实例化里面的对象。然后对字符串进行三次检查,
第一是value值是否存在,
第二是通过instanceof 判断value是否是String的子类或者实例对象,如果是可以进行转换,不是则抛异常。
第三对字符串进行转换并去掉空格判断是否为空字符串
public static void test3() throws Exception {
String name = "aaaa";
String age = "34";
String birthday = "1980-09-09";
/**
* 为了让日期赋值到bean的birthday属性上。我们给BeanUtils
* 上注册一个转化器。ConvertUtils工具
* ConvertUtils.register(converter, clazz);
* 第二个属性告诉转化器是转什么类型的 这里是Date类型
* 第一个写一个转化器Converter converter是一个接口,
* new 并实例化其对象
*
*/
ConvertUtils.register(new Converter(){
public Object convert(Class type, Object value) {
//数据要先检查后使用
if(value==null){
return null;
}
/**
* java中的instanceof运算符是用来在运行时指出对象是否
* 是特定类的一个实例。instanceof通过返回一个布尔值来指出,
* 这个对象是否是这个特定类或者是它的子类的一个实例。
* result = object instanceof class
* Result:布尔类型。
* Object:必选项。任意对象表达式。
* Class:必选项。任意已定义的对象类。
*/
if(!(value instanceof String)){
throw new ConversionException("爷爷只支持String类型的转换");
}
String str = (String) value;
if(str.trim().equals("")){
return null;
}
//只有value闯过这三关就能转
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
return df.parse(str);
} catch (ParseException e) {
throw new RuntimeException();//异常链不能断
}
}
}, Date.class);
Person p = new Person();
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "birthday", birthday);
System.out.println(p.getName());
System.out.println(p.getAge());
System.out.println(p.getBirthday());
}
//也可以使用直接写好的转化器,但是这个转化器有bug,如果传入的格式不对就会出现异常
public static void test4() throws Exception {
String name = "aaaa";
String age = "34";
String birthday = "1980-09-09";
ConvertUtils.register(new DateLocaleConverter(), Date.class);
Person p = new Person();
BeanUtils.setProperty(p, "name", name);
BeanUtils.setProperty(p, "age", age);
BeanUtils.setProperty(p, "birthday", birthday);
System.out.println(p.getName());
System.out.println(p.getAge());
System.out.println(p.getBirthday());
}
public static void test5() throws Exception{
Map map = new HashMap();
map.put("name", "aaa");
map.put("age", "23");
map.put("birthday", "1980-09-09");
//这里也要提前注册转化器
ConvertUtils.register(new DateLocaleConverter(), Date.class);
Person bean = new Person();
//用map集合的值,填充到bean属性
BeanUtils.populate(bean, map);
//修改名字的时候ctrl+1 rname
System.out.println(bean.getName());
System.out.println(bean.getAge());
System.out.println(bean.getBirthday());