Java反射/内省(反射方式实现javaBean与map互转)
什么是Java反射机制
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
举例什么地方用到反射机制
- JDBC中,利用反射动态加载了数据库驱动程序
- Web服务器中利用反射调用了Servlet的服务方法
- Eclipse等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
- 很多框架都用到反射机制,如Spring:ICO创建对象时set注入属性,AOP,动态代理
Java反射机制的作用
可以在程序运行时对任意一个类或对象进行某些操作;
- 在运行时判定任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判定任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
- 生成动态代理;
反射机制的优缺点
优点:运行期类型的判断、动态加载类、提高代码灵活度
缺点:性能瓶颈,反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的java代码要慢很多。
map与javaBean互转(使用反射与内省)
一般情况下,使用工具类的情况比较多,如json工具类,BeanUtils等等
json工具类
fastJson中不支持map与javaBean互转,需要先转成jsonString作为中间量;
如:
将map转成JavaBean:map需要先转成jsonString,再转成javaBean
String s = JSON.toJSONString(map1);
Student student1 = JSONObject.parseObject(s, Student.class);
将JavaBean转成map:javaBean需要先转成jsonString,再转成map
String s1 = JSON.toJSONString(student);
Map map = JSON.parseObject(s1, Map.class);
使用工具类基本上都需要导包,除此之外,可以使用基于 java 原生实现的反射机制来实现map与javaBean互转,不需要导入其他的依赖;
反射机制与内省机制
反射机制+内省器,内省(Introspector)是 Java 语言对 JavaBean 类属性、事件的一种缺省处理方法,也是基于 java 原生实现
Map转javaBean:
方法:public static
map2JavaBean思路:
-
newInstance创建实例
T o = c.newInstance();
-
内省Introspector类调用getBeanInfo获取bean对象信息,并封装到BeanInfo中
BeanInfo beanInfo = Introspector.getBeanInfo(c, Object.class);
getBeanInfo:对Java bean进行内省,并在给定的“停止”点以下了解它的所有属性、公开的方法。如果先前基于相同的参数对Java Bean的BeanInfo类进行了内省,则从BeanInfo缓存中检索BeanInfo类。
参数:beanClass—要分析的bean类。
stopClass——停止分析的基类。stopClass或其基类中的任何方法/属性/事件都将在分析中被忽略。
返回:bean的BeanInfo
抛出:IntrospectionException——如果在自省期间发生异常。
-
通过 getPropertyDescriptors() 获取该类下所有的属性数组(每个子元素中都有getWriteMethod和getReadMethod方法,用来写入和读取)
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
返回:PropertyDescriptor对象的数组,如果要通过自动分析获得信息,则为空
-
遍历该数组,把获取的属性名字作为 map 的 key,通过 key 取出对应的 value 值
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { //4.1 获取 set方法 Method writeMethod = propertyDescriptor.getWriteMethod(); //4.2 获取属性名称作为map中检索关键字key String key = propertyDescriptor.getName(); //4.3 根据key获取map中的value, Object value = map.get(key); //4.4 执行set方法,将map中的value映射到对应字段中 writeMethod.invoke(o, value); }
-
返回 实例 o
return o;
完整方法:
package utils;
import java.beans.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
public class BeanMapUtilByReflect {
/**
* Map 转 JavaBean
*/
public static <T> T mapToJavaBean(Map map, Class<T> c) throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {
// 1、通过字节码对象创建空的实例
T o = c.newInstance();
// 2、通过 内省Introspector 类把bean对象信息封装到 beanInfo 中
BeanInfo beanInfo = Introspector.getBeanInfo(c, Object.class);
// 3、通过 getPropertyDescriptors() 获取该类下所有的属性数组(每个子元素中都有getWriteMethod和getReadMethod方法,用来写入和读取)
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// 4、遍历该数组,把获取的名字作为 map 的 key,通过 key 取出对应的 value 值
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
//4.1 获取 set方法
Method setter = propertyDescriptor.getWriteMethod();
//4.2 获取属性名称 要求map中映射字段的key与javaBean中对应
String key = propertyDescriptor.getName();
//4.3 根据key获取map中的value,
Object value = map.get(key);
//4.4 执行set方法,将map中的value映射到对应字段中
if (setter != null) {
setter.invoke(o, value);
}
}
return o;
}
}
javaBean2Map 思路
-
创建HashMap对象,用于存储javaBean转换后的键值对。
Map<String, Object> map = new HashMap<String, Object>();
-
获取所传入的javaBean对象的Class对象,该Class对象包含了关于javaBean的类的信息,比如类名,父类,接口,成员变量,方法等等
Class<?> aClass = object.getClass();
-
使用Class对象的getDeclaredFields()方法获取该对象的所有字段。注意,该方法只能获取当前类中声明的成员变量,无法继承父类的属性和方法;如果需要获取父类的公共成员变量信息,可以使用getFields()方法(只能获取公共方法和属性)。
Field[] fields = aClass.getDeclaredFields();
getFields()和getDeclaredFields()的区别:
getFields()
方法只能获取到类中的公共(public)成员变量,包括从父类继承的公共成员变量。而getDeclaredFields()
方法可以获取到类中所有声明的成员变量,包括私有(private)、受保护(protected)和默认(package-private)访问权限的成员变量,但不包括从父类继承的成员变量。getFields()
方法返回的是一个包含公共成员变量的数组,而getDeclaredFields()
方法返回的是一个包含所有声明的成员变量的数组。
-
遍历所有字段,设置字段的可访问性(即使私有字段也可以访问)。
for (Field field : fields) { /* 4.1. setAccessible(true): 用于设置类的私有成员变量、方法或构造函数的访问权限。当调用setAccessible(true)后,可以绕过访问修饰符的限制,直接访问和修改私有成员变量、调用私有方法或构造私有对象。 这个方法可以用于反射机制,通过反射来访问和操作类的私有成员。但是需要注意,使用setAccessible(true)是一种破坏封装性的行为,可能会导致代码的安全性问题和不稳定性,所以在使用时需要谨慎,并且遵循良好的编程实践。 */ field.setAccessible(true); //4.2. 将字段名作为键,字段值作为值,存入HashMap中。 map.put(field.getName(), field.get(object)); }
-
遍历完所有字段后,将存储转换结果的HashMap返回。
return map;
完整方法:
/**
* 对象转Map
* @param object
* @return map
* @throws IllegalAccessException
*/
public static Map beanToMap(Object object) throws IllegalAccessException {
//1.创建一个HashMap对象,用于存储转换后的键值对。
Map<String, Object> map = new HashMap<String, Object>();
//2.获取传入对象的Class对象。
Class<?> aClass = object.getClass();
//3.使用Class对象的getDeclaredFields()方法获取该对象的所有字段。
Field[] fields = aClass.getDeclaredFields();
//4.遍历所有字段,设置字段的可访问性(即使私有字段也可以访问)。
for (Field field : fields) {
/*
4.1. setAccessible(true): 用于设置类的私有成员变量、方法或构造函数的访问权限。当调用setAccessible(true)后,可以绕过访问修饰符的限制,直接访问和修改私有成员变量、调用私有方法或构造私有对象。
这个方法可以用于反射机制,通过反射来访问和操作类的私有成员。但是需要注意,使用setAccessible(true)是一种破坏封装性的行为,可能会导致代码的安全性问题和不稳定性,所以在使用时需要谨慎,并且遵循良好的编程实践。
*/
field.setAccessible(true);
//4.2. 将字段名作为键,字段值作为值,存入HashMap中。
map.put(field.getName(), field.get(object));
}
//5.遍历完所有字段后,将存储转换结果的HashMap返回。
return map;
}
特殊情况,javaBean转map时,bean继承父类情况
Class对象在调用getDeclaredFields时,只能返回当前对象所在类的所有属性和方法(包括私有),
在javaBean转map时,如果对象继承了父类的属性(pojo2 extend pojo1),那么在使用反射时,调用getDeclaredFields方法是无法获取父类的私有属性和方法的,即使使用getFields()
方法也只能获取父类的公共方法;
解决思路:通过传入参数的object对象,获取该类的Class对象,然后通过该Class对象调用getSuperclass
获取父类的Class对象,子类的Class对象和父类的Class对象都调用getDeclaredFields,返回两个Filed类型数组,再将两个数组拼接(拼接时,如果有元素重复,取子类中的属性)
测试:
pojo1,父类,私有基本类型包装类+初始化值+静态常量
package pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private Integer id;
private String name;
private Integer age;
private Boolean sex; //注意,反射中 Boolean类型只能传ture/false,如果传参是0或1的话,在进行反射时,会报错 argument type mismatch 类型不匹配
private Double score;//注意,反射中 Float 类型 传入 22.4 必须时22.4f,否则报错argument type mismatch 类型不匹配,改成Bouble类型正确
//属性测试,一般情况下没有
private String schoolName = "光明小学";
private static final String headmaster = "小王";
//Record canned format 记录固定格式
private String recCreator;//记录创建人
private String recCreatorName;//记录创建人姓名
private String recCreateTime;//记录创建时间
private String recRevisor;//记录修改人
private String recRevisorName;//记录修改人姓名
private String recRevisorTime;//记录修改时间
}
pojo2:子类,继承Student,且额外多三个属性
package pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
public class Student2 extends Student{
private String dep;//部门
private Float mathScore;
//属性测试,一般情况下没有
private String schoolName = "光明中学";
private static final String headmaster = "小明";
}
javaBean转Map的静态方法:中间数组拼接时可以改用stream流的方式来优化,优化代码已注释,二选一;
public class BeanMapUtilByReflect {
/**
* 对象转Map
* @param object
* @return map
* @throws IllegalAccessException
*/
public static <T> Map beanToMap(Object object) throws IllegalAccessException {
//1.创建一个HashMap对象,用于存储转换后的键值对。
Map<String, T> map = new HashMap<String, T>();
// 获取对象的Class对象
Class<?> aClass = object.getClass();
// 获取对象的父类
Class<?> superClass = aClass.getSuperclass();
//获取当前类中所有属性和方法
Field[] declaredFields = aClass.getDeclaredFields();
// 获取父类的所有字段,包括私有字段
Field[] fields = superClass.getDeclaredFields();
List<Field> resultList = new ArrayList<>();
// 将子类中的属性添加到结果数组
for (Field field : declaredFields) {
resultList.add(field);
}
// // 遍历父类字段数组,如果父类元素不在子类数组中则添加到子类的结果数组
for (Field field : fields) {
boolean contains = false;
for (Field resultField : resultList) {
String name1 = field.getName();
if (name1.equals(resultField.getName())) {
contains = true;
break;
}
}
if (!contains) {
resultList.add(field);
}
}
//上面遍历改用Stream流的方式
// List<Field> finalResultList = resultList;
// resultList = Arrays.stream(fields)
// .filter(field -> !finalResultList.stream().anyMatch(resultField -> field.equals(resultField)))
// .collect(Collectors.toList());
// 遍历合并后的数组中的所有字段
for (Field field : resultList) {
try {
// 设置字段的可访问性
field.setAccessible(true);
// 获取字段的值
Object value = field.get(object);
// 将字段名和字段值存入Map中
map.put(field.getName(), (T)value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
}
填充数据,模拟sql查询
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import pojo.Student;
import pojo.Student2;
import utils.BeanMapUtilByReflect;
import utils.ObjectMapUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PaddingData {
public List<Student2> queryAll() throws Exception {
List<Student2> list = new ArrayList();
Student2 student = new Student2();
student.setAge(10);
student.setName("hy");
student.setId(1011);
student.setSex(false);
student.setScore(88.7D);
student.setRecCreator("admin");
student.setRecCreatorName("admin");
student.setRecCreateTime("20220604");
student.setRecRevisor("root");
student.setRecRevisorName("root");
student.setRecRevisorTime("20230420");
student.setDep("depart");
student.setMathScore(99.4f);
//此时student类中应该有15个变量,包括上面已填充的13个变量和两个初始化的量
Map map = BeanMapUtilByReflect.beanToMap(student);
map.forEach((key,value)-> System.out.println(key+":"+value));
list.add(map);
return list;
}
}
测试类:
public class StreamTest1 {
public static void main(String[] args) throws Exception {
PaddingData paddingData = new PaddingData();
List<Student> studentList = paddingData.queryAll();
for (Student s: studentList
) {
System.out.println(s);
}
}
}
结果:满足需求
recRevisorName:root
recRevisorTime:20230420
headmaster:小明
sex:false
mathScore:99.4
dep:depart
score:88.7
recRevisor:root
recCreator:admin
recCreatorName:admin
recCreateTime:20220604
name:hy
id:1011
schoolName:光明中学
age:10