我正在参加「掘金·启航计划」
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)
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是等级的. 如图:
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;
}
}
\