Java反射/内省(反射方式实现javaBean与map互转)

什么是Java反射机制

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

举例什么地方用到反射机制

  1. JDBC中,利用反射动态加载了数据库驱动程序
  2. Web服务器中利用反射调用了Servlet的服务方法
  3. Eclipse等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
  4. 很多框架都用到反射机制,如Spring:ICO创建对象时set注入属性,AOP,动态代理

Java反射机制的作用

可以在程序运行时对任意一个类或对象进行某些操作;

  1. 在运行时判定任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时判定任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的方法
  5. 生成动态代理;

反射机制的优缺点

优点:运行期类型的判断、动态加载类、提高代码灵活度

缺点:性能瓶颈,反射相当于一系列解释操作,通知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 T mapToJavaBean(Map map, Class c)
map2JavaBean思路:

  1. newInstance创建实例

    T o =  c.newInstance();
    
  2. 内省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——如果在自省期间发生异常。

  3. 通过 getPropertyDescriptors() 获取该类下所有的属性数组(每个子元素中都有getWriteMethod和getReadMethod方法,用来写入和读取)

    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    

    返回:PropertyDescriptor对象的数组,如果要通过自动分析获得信息,则为空

  4. 遍历该数组,把获取的属性名字作为 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);
    }
    
  5. 返回 实例 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 思路

  1. 创建HashMap对象,用于存储javaBean转换后的键值对。

    Map<String, Object> map = new HashMap<String, Object>();
    
  2. 获取所传入的javaBean对象的Class对象,该Class对象包含了关于javaBean的类的信息,比如类名,父类,接口,成员变量,方法等等

    Class<?> aClass = object.getClass();
    
  3. 使用Class对象的getDeclaredFields()方法获取该对象的所有字段。注意,该方法只能获取当前类中声明的成员变量,无法继承父类的属性和方法;如果需要获取父类的公共成员变量信息,可以使用getFields()方法(只能获取公共方法和属性)。

    Field[] fields = aClass.getDeclaredFields();
    

    getFields()和getDeclaredFields()的区别:

    1. getFields() 方法只能获取到类中的公共(public)成员变量,包括从父类继承的公共成员变量。而 getDeclaredFields() 方法可以获取到类中所有声明的成员变量,包括私有(private)、受保护(protected)和默认(package-private)访问权限的成员变量,但不包括从父类继承的成员变量。
    2. getFields() 方法返回的是一个包含公共成员变量的数组,而 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;
    

完整方法:

    /**
     * 对象转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

posted @ 2023-03-07 16:27  destiny-2015  阅读(266)  评论(0编辑  收藏  举报