Java反射妙用

一、背景

开发过程中难免遇到处理不同Java类的数据,由于数据结构不一样,很难统一去处理,如果是分开处理会划分很多精力和写大量重复代码。

项目上真实案例:

不同类型的业务要做一个统一的方法处理,需要拿到这些数据,但这些业务数据结构不同,一种业务是多个类型的表组成,很难统一去处理,如果用上反射就可以用统一的方法拿到不同业务类型的代码!

二、代码演示

基础代码示例

正面代码

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射 API 进行反射调用:

Class clz = Class.forName("com.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。

简单例子
public class Apple {
    private int price;
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("com.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

  • 获取类的 Class 对象实例
Class clz = Class.forName("com.zhenai.api.Apple");
  • 根据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
  • 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = appleConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

  • 获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
  • 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);

到这里,我们已经能够掌握反射的基本使用。但如果要进一步掌握反射,还需要对反射的常用 API 有更深入的理解。

在 JDK 中,反射相关的 API 可以分为下面几个方面:获取反射的 Class 对象、通过反射创建类对象、通过反射获取类属性方法及构造器。

项目上实战

项目上也相同的需求,以下代码便是用简单的衍生过来的

传入一个dto(包含不同的类型数据集合),每一个DTO都会继承BaseDTO,可以通过instanceof判断是否有子数据或者集合,在做递归操作获取数据

每一个DTO都会继承BaseDTO,可以通过instanceof判断是否有子数据或者集合,在做递归操作获取数据

也可以查询字段注释或者是常量来根据业务逻辑做进一步操作

    public static void getClaimFiled(T object) {

        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            String type = field.getType().toString();
            // 常量数据不存
            if((type.endsWith("java.lang.String") || type.endsWith("java.lang.Long"))
                    && Modifier.isStatic(field.getModifiers())){
                // 常量
                continue;
            }
            // 获取注释
            Transient annotation = field.getAnnotation(Transient.class);

            if (annotation != null) {
                continue;
            }
            // 判断类型  是 集合还是什么 继承了 baseDto?
            Object value = getFieldValue(field, object);
            // 集合 继续遍历
            if (value instanceof Collection) {
                getClaimFiledList((List)value, list, ss);
            } else if (value instanceof BaseDTO) {
                getClaimFiled(value, list, ss);
            } else {
               
              // 获取到数据变可以存值 此处省。
              // field.getName()
              // value
            }
        }
    }
如果是集合类型则需要循环
  /**
     * 数据集合的获取方式
     */
    public static void getClaimFiledList(List<Object> object) {
        for (Object o : object) {
            getClaimFiled(o, list, ss);
        }
    }
获取数据的值
    /**
     * 通过列获取值
     *
     * @param field 列
     * @param bean  类型
     * @return 值
     */
    public static Object getFieldValue(Field field, Object bean) {
        String fieldGetName = this.parGetName(field.getName());
        Class<?> cls = bean.getClass();
        Method[] methods = cls.getDeclaredMethods();
        try {
            // 没有get方法
            if (!checkGetMet(methods, fieldGetName)) {
                return null;
            }
            Method fieldGetMet = cls.getMethod(fieldGetName, new Class[]{});
            Object value = fieldGetMet.invoke(bean, new Object[]{});
            String fieldType = field.getType().getSimpleName();

            if ("Date".equals(fieldType)) {
                value = fmtDate((Date) value);
            }
            // 金额字段
            if ("BigDecimal".equals(fieldType)) {
                if (value != null) {
                    BigDecimal bd = (BigDecimal) value;
                    value = bd.setScale(NumberConstants.NUM_INT_6, BigDecimal.ROUND_HALF_UP);
                }
            }
            return value;
        } catch (NoSuchMethodException | InvocationTargetException e) {
            logger.error("获取不对值{}", e);
        } catch (IllegalAccessException e) {
            logger.error("IllegalAccessException{}", e);
        }
        return null;
    }


拼接某属性的 get方法,拿到方法名

    /**
     * 拼接某属性的 get方法
     *
     * @param fieldName 字段名
     * @return String 结果
     */
    public static String parGetName(String fieldName) {
        if (null == fieldName || "".equals(fieldName)) {
            return null;
        }
        int startIndex = 0;
        if (fieldName.charAt(0) == UNDERLINE) {
            startIndex = 1;
        }
        return "get"
                + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
                + fieldName.substring(startIndex + 1);
    }

三、拓展

获取反射中的Class对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。

在 Java API 中,获取 Class 类对象有三种方法:

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("java.lang.String");

第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class;

第三种,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

通过反射创建类对象

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

四、可实现内容。

1、通过模版T来获取T传入的所有数据

2、通过空的模版T来反射得到表面来使用动态sql来查询所以相关的数据

说明:表上都会有@Table注解

  /**
     * 查询DTO内包含的表名
     *
     * @param object 模版DTO
     * @return key为表名 value为DTO
     */
    public static Map<String, Object> getDTOFiled(Object object) {
        Map<String, Object> map = new HashMap<>();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 常量数据不存
            if(Modifier.isStatic(field.getModifiers())){
                continue;
            }
            // 判断类型  是 集合还是什么 继承了 baseDto?
            getDTOFieldValue(field, map);
        }
        return map;
    }

    //  获取值
    public static void getDTOFieldValue(Field field, Map<String, Object> map) {
        // 通过字段属性再次反射
        Type type = field.getGenericType();
        try {
            Class cl = Class.forName(type.getTypeName());
            Table table = (Table)cl.getAnnotation(Table.class);
            if (table != null) {
                //  获取数据表名
                map.put(table.name(), cl);
                return;
            }
        } catch (ClassNotFoundException e) {
            logger.error("找不到类名 {}", e);
        }

        if ("java.util.List".equals(field.getType().getTypeName())) {
            Class clazz = (Class)((ParameterizedType) type).getActualTypeArguments()[0];
            //获取类型的类型参数类型。  你可以去查看jdk帮助文档对ParameterizedType的解释。
            // getFiledName(cl1z);
            Table table = (Table)clazz.getAnnotation(Table.class);
            if (table != null) {
                //  获取数据表名
                map.put(table.name(), clazz);
                return;
            }
        }
    }

获取DTO中的某区域的数据, 方法开始回住的根据某个参数来获取存值,之后在便利满足条件的数据

由于次对象会基础父类BaseDto.class,一些数据存在父类中,不能直接获取到。

public static void getHeaderFiled(Object object) {
        Class<?> tmpCl = object.getClass();
        // 获取继承的父类
        Class<?> baCl = tmpCl.getSuperclass();
        if (baCl == BaseDto.class) {
             // 找到 获取头的属性
            Field field = baCl.getDeclaredField("ceader");
            String fieldGetName = DtoRefUtil.parGetName(field.getName());
                    try {
                        Method fieldGetMet = baCl.getMethod(fieldGetName, new Class[]{});
                        Object value = fieldGetMet.invoke(object, new Object[]{});
                        Class<?> cls = value.getClass();
                        Table table = cls.getAnnotation(Table.class);
                        getHeaderFiled(value);
                    } catch (NoSuchMethodException | InvocationTargetException e) {
                        logger.error("获取不对值{}", e);
                    } catch (IllegalAccessException e) {
                        logger.error("IllegalAccessException{}", e);
                    }
        }
  
  Field[] fields = object.getClass().getDeclaredFields();

        for (Field field : fields) {
            // 常量数据不存
            if(Modifier.isStatic(field.getModifiers())){
                continue;
            }
            // 不存头数据 前已经存过了
            if ("ceader".equals(field.getName())) {
                continue;
            }
            // 判断类型  是 集合还是什么 继承了 baseDto?
            Object value = getFieldValue(field, object);
            // 集合 继续遍历
            if (value instanceof BaseDTO) {
                Class<?> cls = value.getClass();
                getClaimHeaderFiled(value, list);
            } else {
                // 获取注释
                Transient annotation = field.getAnnotation(Transient.class);
                if (annotation != null) {
                    continue;
                }
 
        for (Field field : fields) {
            // 常量数据不存
            if(Modifier.isStatic(field.getModifiers())){
                continue;
            }
            // 判断类型  是 集合还是什么 继承了 baseDto?
            Object value = getFieldValue(field, object);
            // 集合 继续遍历
            if (value instanceof BaseDTO) {
                Class<?> cls = value.getClass();
                //  获取数据表名
                Table table = cls.getAnnotation(Table.class);
                getHeaderFiled(value);
            } else {
                // 获取注释
                Transient annotation = field.getAnnotation(Transient.class);
                if (annotation != null) {
                    continue;
                }
              // field.getName()
              // String.valueOf(value)
            }
        });
                
            }
        }
       }

五、零碎知识和可能遇到的问题

1、递归调用DTO和继承的父DTO的值

Class<?> tmpCl = object.getClass();
// 获取继承的父类
Class<?> baCl = tmpCl.getSuperclass();

2、递归调用DTO和成员变量的值(可能是list)

value instanceof BaseDTO // value instanceof Collection

3、通过注释判断是否要获取

Transient annotation = field.getAnnotation(Transient.class);

4、静态变量排除

Modifier.isStatic(field.getModifiers())

5、获取成员变量对应的属性(非基本属性类型)

 Class clazz = (Class)((ParameterizedType) type).getActualTypeArguments()[0];
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容