内省机制-保证java的封装性-实现Map和JavaBean相互转换

412 阅读6分钟

我正在参加「掘金·启航计划」

1. 内省介绍

如果使用反射,直接操作JavaBean中的字段,会破坏java的封装性,所以如果我们在开发中需要操作JavaBean的属性,我们可以选择一种更专业的手段来操作JavaBean的属性---内省机制(底层使用反射实现)

内省机制的主要作用:用于间接操作javabean中的属性

目标:记住内省的核心类 Introspector,熟练操作 JavaBean 的属性.

内省的入口: Introspector

操作步骤:

1.获取JavaBean相关的信息对象:BeanInfo

2.该BeanInfo中就会封装有当前Bean的成员(字段/属性/事件)

3.获取到对应的属性,对其操作

java.beans.Introspector类:

常用API:

static BeanInfo getBeanInfo(Class<?> beanClass) : 获取字节码对象对应的JavaBean信息static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)

java.beans.BeanInfo接口:

常用API:

PropertyDescriptor[] getPropertyDescriptors() : 获取所有的属性描述器

java.beans.PropertyDescriptor类:

常用API:

String getName() : 获得属性的名称 
​
Class<?> getPropertyType() : 获得属性的类型 
​
Method getReadMethod() : 获得用于读取属性值的方法对象(相当于获取到了getXxx方法的Method)
​
Method getWriteMethod() : 获得用于设置属性值的方法对象(相当于获取到了setXxx方法的Method)

2. 获取JavaBean中的属性信息

准备一个JavaBean信息

public class Student {
​
     private Long id;
     private String name;
     private Integer age;
​
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public Integer getAge() {return age;}
    public void setAge(Integer age) { this.age = age;}
}
​

通过内省机制获取Student这个类的属性成员信息

@Test
public void testIntrospector() throws Exception {
    // 1. 获取Student类的beanInfo信息
    BeanInfo beanInfo = Introspector.getBeanInfo(Student.class);
    // 2. 通过beanInfo对象获取所有的属性描述器信息
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    // 3. 遍历属性描述器数组,获取到每个属性描述器对象
    for (PropertyDescriptor pd: pds) {
        System.out.println("属性名:" + pd.getName());// 获取属性名
        System.out.println("属性类型:" + pd.getPropertyType()); // 获取属性类型
        // 获取属性的getter 和setter 方法
        Method getMethod = pd.getReadMethod();
        Method setMethod = pd.getWriteMethod();
        System.out.println("属性getter:" + getMethod);
        System.out.println("属性setter:" + setMethod);  
    }
}

问题:我们发现获取的bean的信息,包含了很多,它们来自于Object 类中的,

而我们在使用内省机制的时候,并不希望看到Object 类中的属性。

解决方案:使用getBeanInfo()的重载方法,可以帮我们完成过滤,如图:

static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)

Snip20210815_2.png

3. 操作JavaBean中的属性的值案例

操作Student类中的name属性,给name属性赋值:小liu,然后再获取并打印到控制台

//需求:操作Student类中的name属性,给name属性赋值:小狼,然后再获取并打印到控制台
@Test
public void testIntrospector2() throws Exception {
        
        // 1. 获取Student类的BeanInfo信息
        BeanInfo beanInfo = Introspector.getBeanInfo(Student.class,Object.class);
        // 2. 通过beanInfo 获取所有的属性描述器信息
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        // 3. 遍历属性描述器数组,获取到每个属性描述器
        for (PropertyDescriptor pd: pds) {  
        // 4.判断遍历的属性名称是否为name属性
            if("name".equals(pd.getName())) {
        // 5. 获取name属性的setXxx方法的方法对象
               Method setMethod = pd.getWriteMethod();
        // 6. 创建对象来调用方法
                Person obj = Person.class.newInstance();
        // 7.通过反射调用方法,给name属性设置内容
                setMethod.invoke(obj, "小liu");
        // 8.获取name属性的getXxx方法的方法对象
               Method getMethod = pd.getReadMethod();
        // 9.通过反射调用name属性的getXxx方法,获取name的值,并打印到控制台
            System.out.println(getMethod.invoke(obj));
            }
           
        }
 }

4.JavaBean 和 Map 之间的转化

4.1 为什么需要将 JavaBean 和 Map 进行转换?

在很多应用场景中,需要将 key=value 形式的数据与 JavaBean 对象相互转换.

4.2 为什么具备可以转换的条件?

并不是说,任何两种结构的数据都是可以相互转换的.之所以map和JavaBean可以转换,是因为它们在数据结构上就极其相似.

Map是由key-value组成,key是不能重复的.

JavaBean是由属性名和属性值组成,属性名是不同的.

如果把JavaBean的属性名看做是Map的key,把属性值看做是Map的value,那么一个Map对象和一个JavaBean是等级的. 如图:

Snip20210815_1.png

4.3 JavaBean 转换成Map案例

javaBean:

public class Student {
​
     private Long id;
     private String name;
     private Integer age;
​
    public Long getId() { return id;}
    public void setId(Long id) { this.id = id;}
    public String getName() {return name; }
    public void setName(String name) {this.name = name;}
    public Integer getAge() {return age;}
    public void setAge(Integer age) { this.age = age; }
}

把通过Student这个类创建的实例,通过内省机制把实例中的数据取出存放到一个map中,例如:

实例:
  
Student s = new Student();
s.setId(12L);
s.setName("小狼");
s.setAge(10);
​
存放到map中的效果:
 Map<String,Object> map = new HashMap();
 map.put("id",12);
 map.put("name","小狼");
 map.put("age",10);
    public static Map<String, Object> bean2Map(Object bean) throws Exception {
        // 1. 定义一个map,用来存储bean中的数据
        Map<String, Object> map = new HashMap<String, Object>();
        // 2. 获取bean的字节码对象
        Class<? extends Object> clz = bean.getClass();
        // 3. 通过内省获取该类的BeanInfo对象
        BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
        // 通过BeanInfo对象获取属性描述器
        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            // 获取属性名称
            String name = propertyDescriptor.getName();
            // 获取getter方法对象
            Method readMethod = propertyDescriptor.getReadMethod();
            // 通过反射执行方法获取值
            Object value = readMethod.invoke(bean);
            map.put(name, value);
        }
        return map;
    }

4.4 Map转成JavaBean对象案例

处理一个存有信息的map,通过内省机制把map的数据取出存放到一个JavaBean的实例中,例如:

存有信息的map:

 Map<String,Object> map = new HashMap();
 map.put("id",12);
 map.put("name","小狼");
 map.put("age",10);

把map中的数据取出封装到Student对象的实例:

Student s = new Student();
s.setId(12L);
s.setName("小狼");
s.setAge(10);
public static Object map2bean(Map<String, Object> map, Class clz) throws Exception {
    // 1. 根据传入的字节码对象创建真实对象
    Object obj = clz.newInstance();
    // 2. 通过内省机制获取字节码对象的BeanInfo信息
    BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
    // 3. 获取所有的属性描述器
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    // 4.遍历所有的描述器数组
    for (PropertyDescriptor pd : pds) {
        // 5.获取成员属性的名称
        String key = pd.getName();
        // 6.把获取的成员属性名称作为key,从map中把对应的value 取出
        Object value = map.get(key);
        // 7.获取属性对应的setter方法的方法对象
        Method m =  pd.getWriteMethod()
        // 8.通过反射执行setter方法,把value 设置给属性
        m.invoke(obj,value);
    }
    return obj;
}

问题: 调用者已经告诉工具方法要把map转为 Person,而拿到数据之后任然需要做强转,不合理.

解决方案: 使用泛型 处理一个存有信息的map,通过内省机制把map的数据取出存放到一个JavaBean的实例中

    public static <T> T map2Bean(Map<String, Object> map, Class<T> clz) throws Exception {
        // 根据字节码对象获取类的真实对象
        T instance = clz.newInstance();
        // 通过内省获取该类的BeanInfo对象
        BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
        // 通过BeanInfo对象获取属性描述器
        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor propertyDescriptor : descriptors) {
            // 属性名称作为map中的key,根据key获取value
            Object value = map.get(propertyDescriptor.getName());
            // 获取方法对象
            Method writeMethod = propertyDescriptor.getWriteMethod();
            // 把value 作为参数,通过反射调用setter方法
            writeMethod.invoke(instance, value); // 相当于instance.setXxx(value) 方法
        }
        return instance;
    }
}

\