反射的运用

一、反射介绍

定义

在运行时期,能够动态访问、检测完整类结构信息甚至修改类本身的一种能力

特点

  • 优点
    1. 灵活性高,因为是动态编译。(静态编译即编译器是确定类型&绑定对象;动态编译即运行期确定类型&绑定对象)
    2. 动态编译体现了Java的灵活性、多态特性&降低类之间耦合
  • 缺点
    1. 执行效率低,主要通过JVM执行,所以时间成本会 高于 直接执行相同操作
    2. 反射调用方法是通过invoke方法需要传入Object object和Object… args,
      基本数据类型需要拆装箱,产生大量额外的对象和内存开销,频繁出发GC
    3. 异常处理:invoke方法会抛出InvokeTargetException,从而导致无法在原来的try/catch中捕捉到自定义异常,
      这里是作者自己遇到的问题,觉得反射导致异常处理比较麻烦

二、具体内容

Class对象

万物皆可对象,所以所有的类都有一个类对象。

java.lang.Class类是反射机制的基础,存放着对应类型对象的运行时信息。

获取类对象的方法如下

  • Object.getClass():先new出一个对象object,然后object.getClass()
  • T.class:直接通过类(Class)获取,Class.class
  • Class.forName():Class.forName(“类全包名”),例如最熟悉的JDBCClass.forName(“com.mysql.jdbc.Driver”)

主要使用类和方法

Class类


Constructor类(构造器)

// a. 获取指定的构造函数 (公共 / 继承)
Constructor getConstructor(Class... parameterTypes)
// b. 获取所有的构造函数(公共 / 继承) 
Constructor[] getConstructors(); 
// c. 获取指定的构造函数 ( 不包括继承)
Constructor getDeclaredConstructor(Class... parameterTypes) 
// d. 获取所有的构造函数( 不包括继承)
Constructor[] getDeclaredConstructors(); 

Fields类(属性或者叫域)

// a. 获取指定的属性(公共 / 继承)
Field getField(String name) ;
// b. 获取所有的属性(公共 / 继承)
Field[] getFields() ;
// c. 获取指定的所有属性 (不包括继承)
Field getDeclaredField(String name) ;
// d. 获取所有的所有属性 (不包括继承)
Field[] getDeclaredFields() ;
// 最终都是获得一个Field类对象

Method类(方法)

// a. 获取指定的方法(公共 / 继承)
Method getMethod(String name, Class... parameterTypes) ;
// b. 获取所有的方法(公共 / 继承)
Method[] getMethods() ;
// c. 获取指定的方法 ( 不包括继承)
Method getDeclaredMethod(String name, Class... parameterTypes) ;
// d. 获取所有的方法( 不包括继承)
Method[] getDeclaredMethods() ;
// 最终都是获得一个Method类对象

父类

getSuperclass();

创建对象

Object newInstance(); 

获取类名

String getName();

三、应用实例

问题说明

为了重构一个很古老的项目,我们并不想进行大量的重复工作。
很难描述具体项目结构,打个比方,我们现在就是想通过方法名来调用某个对象的某个方法。
我不管你要什么参数,我把我有的所有参数都给你,当然参数名字和类型是一致的。

问题要点

要执行一个方法,首先要确定方法,执行对象和参数列表,重中之重就是参数列表
脑海里设想一下这个大致的流程

  1. 反射获取到该method,然后去获取参数的参数名称列表
  2. 把需要的参数值从所有的参数值通过名字一致过滤出来,放进一个Object[]
  3. 然后menthod.invoke(Object object,Object… args)
  • 大家都知道,java被编译后,参数名称是被擦除的,class文件里面是用arg0,arg1代替,
    幸好1.8后,可以设置编译器参数,将入参参数名保存,这也是这个思路之所以能存在的根本
  • 反射调用方法的时候,参数不是按照名字对应的,而是根据顺序,
    所以你一定要保证参数值列表内的参数前后顺序正确,否则就会方法参数不匹配
    幸好method.getParameters(),获得的参数名称值就是按着顺序来着的。

这两个幸好下来,自己都感觉自己这个方法啊。真的是非常巧合之下产生。

四、代码示例

public class MethodPool {

    //做一个方法池,这样可以避免多次创建方法,减少开支
    private static Map methodMap = new HashMap<>();


    public static Response invoke(Object sourceObject, CommonParameters inParameters) throws IllegalAccessException, IOException {

        //包括父类的域也要统计出来,CommonParameters是一个父类被其他的controller统一参数类继承
        //其实这里感觉很神奇,其实应该说是向上转型,子转父,但是依旧保存了子的域,多态很厉害
        Field[] fields = getFieldFromClass(inParameters.getClass());

        Method method = getMethodByName(sourceObject, inParameters.getMethodName());
        
        if (method == null) {
            throw new RuntimeException("no such method");
        }

        //Get the parameters required for method execution
        //获取到方法执行需要的参数值
        Parameter[] parameters = method.getParameters();

        //set access and to map
        //设置域访问限制和映射到map
        Map paramFileMap = Stream.of(fields).peek(it -> it.setAccessible(true))
                .collect(Collectors.toMap(Field::getName, Function.identity()));


        Object[] obejects = new Object[parameters.length];

        //get all the parameters you need by filter
        //根据参数名称过滤并且获得参数值放入objects
        for (int i = 0; i < parameters.length; i++) {
            //System.out.println(parameters[i].getName());
            Field field = paramFileMap.get(parameters[i].getName());
            if (ObjectUtils.isEmpty(fields)) {
                throw new RuntimeException("no parameter named " + parameters[i].getName() + "can be found");
            }
            obejects[i] = field.get(inParameters);
            String log = ObjectUtils.isEmpty(obejects[i]) ? "null" :
                    " type->" + obejects.getClass().getTypeName() +
                            " value->" + obejects[i];
            System.out.println("objects[" + i + "]:"+log);
        }

        //这里就是异常处理的问题,表示很无奈
        //反射调用的异常覆盖了我的原有自定义异常
        //导致我try/catch无法捕捉我的自定义异常
        Response response = new Response();
        Object object = null;
        try {
            object = method.invoke(sourceObject, obejects);
            if(ObjectUtils.isEmpty(object)){
                object = "true";
            }
            response.setData(object);
        }catch (InvocationTargetException e){
            response.setData("");
            response.setCode(-1);
            if(e.getTargetException().getClass() == NoRecordsReturned.class){
                response.setMsg(((NoRecordsReturned)e.getTargetException()).msg);
                response.setData("");
                response.setCode(0);
            }else if(e.getTargetException().getClass() == ProfileError.class){
                response.setMsg(((ProfileError)e.getTargetException()).msg);
            }
        }
        return response;
    }

    /**
     * get the Filed[] of the class and its parent class
     *
     * @param clazz
     * @return
     */
    private static Field[] getFieldFromClass(Class clazz) {
        Field[] fields = clazz.getDeclaredFields();

        Field[] supFileds = clazz.getSuperclass().getDeclaredFields();

        return concat(fields, supFileds);
    }

    /**
     * merge two arrays
     *
     * @param afields
     * @param bfields
     * @return
     */
    private static Field[] concat(Field[] afields, Field[] bfields) {
        Field[] fields = new Field[afields.length + bfields.length];
        System.arraycopy(afields, 0, fields, 0, afields.length);
        System.arraycopy(bfields, 0, fields, afields.length, bfields.length);
        return fields;
    }

    /**
     * Find the corresponding method in the object by the method name
     *
     * @param obj
     * @param methodName
     * @return
     */
    private static Method getMethodByName(Object obj, String methodName) {
        Class aClass = obj.getClass();
        String key = aClass.getName() + "." + methodName;
        //System.out.println(key);
        
        Method method = methodMap.get(key);
        if (ObjectUtils.isEmpty(method)) {
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method m :
                    declaredMethods) {
                if (methodName.equals(m.getName())) {
                    methodMap.put(key, m);
                    method = m;
                }
            }
        }
        return method;
}

五、结束语

可能读者读起来有些云里雾里,我的确很难讲清楚项目结构,不过我也就自己存个档
也有可能说我写的很简陋,那就太好了,希望大家能指正和指导,让知识运用的更加完美
不过第一次为一个项目写了个工具,我反正感觉还是挺兴奋的,而且的确有减少不少工作量,很有意思。

你可能感兴趣的:(Java)