内省

来源:互联网 发布:微博数据集下载 编辑:程序博客网 时间:2024/05/02 03:32

内省

了解JavaBean

  • 内省对应的英文单词为IntroSpector,它主要用于对JavaBean进行操作,JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。
    问:一个JavaBean可以当做普通Java类来使用吗?一个普通Java类可以当做JavaBean来使用吗?
    答:一个JavaBean可以当做普通Java类来使用,但一个普通Java类不一定可以当做JavaBean来使用。
  • 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问,大家觉得这些方法的名称叫什么好呢?JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。如果方法名为setId,中文意思即为设置id,至于你把它存到哪个变量上,用管吗?如果方法名为getId,中文意思即为获取id,至于你从哪个变量上取,用管吗?去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的,如果剩余部分的第二个字母是大写的,则把剩余部分的首字母保持原样
    例,

    setId()的属性名→idisLast()的属性名→lastsetCPU的属性名是什么?→CPUgetUPS的属性名是什么?→UPS

    总之,一个类被当作javaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

  • 一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:

    • 在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
    • 开发框架时,经常需要使用java对象的属性来封装程序的数据,每次都使用反射技术完成此类操作过于麻烦,所以sun公司开发了一套API,专门用于操作java对象的属性。
    • JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套API操作JavaBean比用普通类的方式更方便。

内省综合案例

内省访问JavaBean属性的两种方式:

  • 通过PropertyDescriptor类操作Bean的属性。
  • 通过Introspector类获得Bean对象的BeanInfo,然后通过BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后通过反射机制来调用这些方法。

案例一:
有类ReflectPoint如下:

public class ReflectPoint {    private int x;    public int y;    public String str1 = "ball";    public String str2 = "basketball";    public String str3 = "itcast";    public ReflectPoint(int x, int y) { // 快捷键——Alt+Shift+S  + O        super();        this.x = x;        this.y = y;    }    @Override    public String toString() {        return str1 + ":" + str2 + ":" + str3;    }    @Override    public int hashCode() {        final int prime = 31;        int result = 1;        result = prime * result + x;        result = prime * result + y;        return result;    }    @Override    public boolean equals(Object obj) {        if (this == obj)            return true;        if (obj == null)            return false;        if (getClass() != obj.getClass())            return false;        ReflectPoint other = (ReflectPoint) obj;        if (x != other.x)            return false;        if (y != other.y)            return false;        return true;    }    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;    }}
  • 直接new一个PropertyDescriptor(属性描述器)对象的方式来让大家了解JavaBean API的价值,先用一段代码读取JavaBean的属性,然后再用一段代码设置JavaBean的属性。

    public class IntroSpectorTest1 {    public static void main(String[] args) throws Exception {        ReflectPoint pt = new ReflectPoint(3, 6);        // 获得x属性的值        String propertyName = "x";        // "x"-->"X"-->"getX"-->MethodGetX-->        PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt.getClass());        Method methodGetX =  pd.getReadMethod();        Object retVal = methodGetX.invoke(pt);        System.out.println(retVal);        // 设置x属性的值        PropertyDescriptor pd1 = new PropertyDescriptor(propertyName, pt.getClass());        Method methodSetX = pd1.getWriteMethod();        methodSetX.invoke(pt, 90);        // 通过对象的getX()方法获取设置的x的属性值        System.out.println(pt.getX());    }}

    用eclipse将读取属性和设置属性的流水帐代码分别抽取成方法。
    步骤为:选中要抽取的代码RefactorExtract Method...设置Method name:

    public class IntroSpectorTest1 {    public static void main(String[] args) throws Exception {        ReflectPoint pt = new ReflectPoint(3, 6);        // 获得x属性的值        String propertyName = "x";        // "x"-->"X"-->"getX"-->MethodGetX-->        Object retVal = getProperty(pt, propertyName);        System.out.println(retVal);        // 设置x属性的值        Object value = 90;        setProperty(pt, propertyName, value);        System.out.println(pt.getX());    }    // 只要调用这个方法,并给这个方法传递了一个对象、属性名和设置值,它就能完成属性修改的功能。    private static void setProperty(ReflectPoint pt, String propertyName, Object value)            throws IntrospectionException, IllegalAccessException, InvocationTargetException {        PropertyDescriptor pd1 = new PropertyDescriptor(propertyName, pt.getClass());        Method methodSetX = pd1.getWriteMethod();        methodSetX.invoke(pt, value);    }    private static Object getProperty(ReflectPoint pt, String propertyName)            throws IntrospectionException, IllegalAccessException, InvocationTargetException {        PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt.getClass());        Method methodGetX =  pd.getReadMethod();        Object retVal = methodGetX.invoke(pt);        return retVal;    }}
  • 采用遍历BeanInfo的所有属性方式来查找和设置某个RefectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法,得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。
    这里只关注getProperty()方法:

    private static Object getProperty(ReflectPoint pt, String propertyName)        throws IntrospectionException, IllegalAccessException, InvocationTargetException {    BeanInfo beanInfo = Introspector.getBeanInfo(pt.getClass());    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();    Object retVal = null;    for(PropertyDescriptor pd : pds) {        if(pd.getName().equals(propertyName)) {            Method methodGetX = pd.getReadMethod();            retVal = methodGetX.invoke(pt);            break;        }    }    return retVal;}

    得到BeanInfo最好采用obj.getClass()方式,而不要采用类名.class方式,这样程序更通用。

案例二:
有类Person如下:

public class Person { // javabean    private String name; // 字段    private String password; // 字段    private int age; // 字段    // 注意:Person类从Object类继承了getClass()方法,所以还有一个class属性    public String getAb() {        return null;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}

注意:Person类从Object类继承了getClass()方法,所以还有一个class属性
使用内省api操作bean的属性。

  • 得到bean的所有属性。

    public void test1() throws Exception {    BeanInfo info = Introspector.getBeanInfo(Person.class);    PropertyDescriptor[] pds = info.getPropertyDescriptors();    for(PropertyDescriptor pd : pds) {        System.out.println(pd.getName());    }}

    输出:

    ab
    age
    class
    name
    password

    若只想获得Person类自己的属性,不想获取从父类继承的属性,可这样做:

    public void test1() throws Exception {    BeanInfo info = Introspector.getBeanInfo(Person.class, Object.class); // 若只想获得Person类自己的属性,不想获取从父类继承的属性    PropertyDescriptor[] pds = info.getPropertyDescriptors();    for(PropertyDescriptor pd : pds) {        System.out.println(pd.getName());    }}
  • 操作bean的指定属性:age

    public void test2() throws Exception {    Person p = new Person();    PropertyDescriptor pd = new PropertyDescriptor("age", Person.class);    // 得到属性的写方法,为属性赋值    Method method = pd.getWriteMethod(); // 得到public void setAge(int age) {...}    method.invoke(p, 45);    // 获取属性的值    method = pd.getReadMethod(); // 得到public int getAge() {...}    System.out.println(method.invoke(p, null));}
  • 高级点的内容:获取当前操作的属性的类型。

    public void test3() throws Exception {    PropertyDescriptor pd = new PropertyDescriptor("age", Person.class);    System.out.println(pd.getPropertyType()); // 输出int}

使用beanUtils操纵javabean

Sun公司的内省API过于繁琐,所以Apache组织结合很多实际开发中的应用场景开发了一套简单、易用的API操作Bean的属性——BeanUtils,在BeanUtils中可以直接进行类型的自动转换。
Beanutils工具包的常用类:
①BeanUtils
②PropertyUtils
③ConvertUtils.regsiter(Converter convert, Class clazz)`
④自定义转换器

  • BeanUtil工具包下载:
    1. 登录官网进行下载
    2. 点击Download
    3. 点击commons-beanutils-1.9.2-bin.zip进行下载就OK了,若要查看源码也可下载commons-beanutils-1.9.2-src.zip
  • 使用BeanUtils
    在项目中导入commons-beanutils-1.9.2.jar包即可(PS:把此jar包复制到项目的lib文件夹,右击包Build Pathadd to build path显示奶瓶即可)
    注意:commons-beanutils-1.9.2.jar要与commons-logging-1.2.Jar共用
    在前面内省例子的基础上,用BeanUtils类先get原来设置好的属性,再将其set为一个新值。

    System.out.println(BeanUtils.getProperty(pt, "x"));BeanUtils.setProperty(pt, "x", "89");

    以下代码会输出java.lang.String

    System.out.println(BeanUtils.getProperty(pt, "x").getClass().getName()); // java.lang.String

    注意: BeanUtils操作属性是以String类型设置进去的,自动进行类型转换(String→int),但实际JavaBean的属性是int,获取属性时也以String返回。
    结论:get属性时返回的结果为字符串,set属性时可以接受任意类型的对象,通常使用字符串
    BeanUtils支持属性的级联操作(属性链)
    若类ReflectPoint中有属性birthday,并有其setter/getter方法如下:

    private Date birthday = new Date();public Date getBirthday() {    return birthday;}public void setBirthday(Date birthday) {    this.birthday = birthday;}

    BeanUtils支持属性的级联操作如下:

    BeanUtils.setProperty(pt1, "birthday.time", "111"); // 支持属性的级联操作(属性链)System.out.println(BeanUtils.getProperty(pt1, "birthday.time"));

    再来看一个案例,有Person类如下:

    public class Person { // javabean    private String name; // 字段    private String password; // 字段    private int age; // 字段    private Date birthday;    public String getAb() {        return null;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public Date getBirthday() {        return birthday;    }    public void setBirthday(Date birthday) {        this.birthday = birthday;    }}

    使用beanUtils操作bean的属性(第三方JAR包)。

    public void test1() throws IllegalAccessException, InvocationTargetException {    Person p = new Person();    BeanUtils.setProperty(p, "name", "xcc");    System.out.println(p.getName());}

    注意:BeanUtils对数据进行转换,转换时只支持8种基本数据类型,碰到复杂类型无法转换!
    所以,下面的代码是有问题的。

    public void test2() throws IllegalAccessException, InvocationTargetException {    String name = "aaaa";    String password = "123";    String age = "34";    String birthday = "1980-09-09";    Person p = new Person();    BeanUtils.setProperty(p, "name", name);    BeanUtils.setProperty(p, "password", password);    BeanUtils.setProperty(p, "age", age); // 对数据进行转换,转换时只支持8种基本数据类型    BeanUtils.setProperty(p, "birthday", birthday); // 碰到复杂类型无法转换!!!否则必须给beanUtils注册日期转换器    System.out.println(p.getName());    System.out.println(p.getPassword());    System.out.println(p.getAge());    System.out.println(p.getBirthday());}

    为了让日期赋到bean的birthday属性上,我们给beanUtils注册一个日期转换器

    public void test3() throws IllegalAccessException, InvocationTargetException {    String name = "aaaa";    String password = "123";    String age = "34";    String birthday = "1980-09-09";    // 为了让日期赋到bean的birthday属性上,我们给beanUtils注册一个日期转换器    ConvertUtils.register(new Converter() {        @Override        public <T> T convert(Class<T> type, Object value) {            if(value == null) { // 先检查                return null;            }            if(!(value instanceof String)) {                throw new ConversionException("只支持String类型的转换!!"); // BeanUtils文档中建议抛此异常            }            String str = (String) value; // 非空且为String            if(str.trim().equals("")) { // 再判断是否有值                return null;            }            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");            try {                return (T) df.parse(str);            } catch (ParseException e) {                /*                 * 出现异常,一定要通知上一层程序,抛个异常给上一层                 * 不能打印在控制台上                 */                // e.printStackTrace(); // error                // throw new RuntimeException(); // error                throw new RuntimeException(e); // 异常链不能断,必须把原来的异常信息封装进去,抛出异常给上一层,上一层就会知道到底出了什么问题            }        }    }, Date.class);    Person p = new Person();    BeanUtils.setProperty(p, "name", name);    BeanUtils.setProperty(p, "password", password);    BeanUtils.setProperty(p, "age", age);     BeanUtils.setProperty(p, "birthday", birthday);    System.out.println(p.getName());    System.out.println(p.getPassword());    System.out.println(p.getAge());    // Date date = p.getBirthday();    // System.out.println(date.toLocaleString());    System.out.println(p.getBirthday());}

    注意:异常的处理原则——出现异常,一定要通知上一层程序,抛个异常给上一层,不能打印在控制台上;且异常链不能断,必须把原来的异常信息封装进去,抛出异常给上一层,上一层就会知道到底出了什么问题
    再我们自定义转换器之前,应该看看该工具类有没有给我们提供相应的转换器,可发现该工具类给我们提供了一个转换器DateLocaleConverter

    public void test4() throws IllegalAccessException, InvocationTargetException {    String name = "aaaa";    String password = "123";    String age = "34";    String birthday = "1980-09-09";    ConvertUtils.register(new DateLocaleConverter(), Date.class); // 此转换器DateLocaleConverter有bug    Person p = new Person();    BeanUtils.setProperty(p, "name", name);    BeanUtils.setProperty(p, "password", password);    BeanUtils.setProperty(p, "age", age);     BeanUtils.setProperty(p, "birthday", birthday);    System.out.println(p.getName());    System.out.println(p.getPassword());    System.out.println(p.getAge());    // Date date = p.getBirthday();    // System.out.println(date.toLocaleString());    System.out.println(p.getBirthday());}

    注意:此转换器DateLocaleConverter有bug,对于String birthday = "";会报异常,没有我们自定义的转换器健壮
    用map集合中的值,填充bean的属性。

    public void test5() throws IllegalAccessException, InvocationTargetException {    Map map = new HashMap();    map.put("name1", "aaa"); // 注意:map集合中的键必须和bean的属性名称一致    map.put("password", "123");    map.put("age", "23");    map.put("birthday", "1980-09-09");    ConvertUtils.register(new DateLocaleConverter(), Date.class);    Person bean = new Person();    BeanUtils.populate(bean, map); // 用map集合中的值,填充bean的属性    System.out.println(bean.getName());    System.out.println(bean.getPassword());    System.out.println(bean.getAge());    // Date date = p.getBirthday();    // System.out.println(date.toLocaleString());    System.out.println(bean.getBirthday());}

    注意:map集合中的键必须和bean的属性名称一致

  • 使用PropertyUtils
    用PropertyUtils类先get原来设置好的属性,再将其set为一个新值。

    PropertyUtils.setProperty(pt, "x", 78);System.out.println(PropertyUtils.getProperty(pt, "x"));

    若设置属性时,

    PropertyUtils.setProperty(pt1, "x", "9");

    则会报异常java.lang.IllegalArgumentException: argument type mismatch
    且,如下代码会输出java.lang.Integer

    System.out.println(PropertyUtils.getProperty(pt, "x").getClass().getName());

    结论:get属性时返回的结果为该属性本来的类型,set属性时只接受该属性本来的类型

0 0
原创粉丝点击