深圳幻海软件技术有限公司 欢迎您!

原来高手是这么回答和使用反射的,又觉得涨了不少知识

2023-02-28

​反射是Java语言中非常重要的一个基础知识点,它的应用往往在于代码的封装上和框架的设计上,对于一般的码农和初级工程师来说,在日常的编码工作中很少直接使用反射,所以不少Java小伙伴对反射是既熟悉又陌生。熟悉是都听说过,听说是一个很牛掰的技术,是封装框架,走向架构的必修课,陌生在于日常开发很少直接使

​反射是Java语言中非常重要的一个基础知识点,它的应用往往在于代码的封装上和框架的设计上,对于一般的码农和初级工程师来说,在日常的编码工作中很少直接使用反射,所以不少Java小伙伴对反射是既熟悉又陌生。

熟悉是都听说过,听说是一个很牛掰的技术,是封装框架,走向架构的必修课,陌生在于日常开发很少直接使用,反正不影响哥们施展CV大法。

如果企业没有编程规范要求和来自领导的review,优化程序是不可能的,重构,向上封装?想都不要想。如果想要让自己的编程功力提高,提升自己的架构思维,那么在日常编码过程中,一定要多去思考,我的代码能否再精简?能否再通用?能否变成一个团队插件?想要实现这个目的,反射就可能派上用场了。

本文会从以下几点为小伙伴解密反射,保障看完神清气爽,直呼哇塞:

  • 反射的概念和相关API
  • 反射语法,如何获取和操作反射类,构造器,方法,属性
  • 反射的应用场景,从框架和个人项目经验分享
  • 面试中高手都怎么回答反射的优缺点拿到高薪

什么是反射

Reflection(反射)是Java程序开发语言的重要特性之一,通过反射机制允许在程序运行期间,可以根据任意一个.class【字节码文件】文件获取到这个类的所有信息,包括(成员变量,成员方法,构造器等),并可以通过相应的方式操纵类的字段、方法、构造器

基于反射机制,Java实现在程序运行期间,动态获取信息和动态调用对象的方法,提高了程序的扩展性

使用反射的前提是必须有字节码文件,也就是.class文件,.class文件就是通过.java文件编译而来的。Java作为面向对象的编程语言,提供了为反射准备的字节码文件对象java.lang.Class<T>

反射相关API

反射机制相关的包都在

java.lang.reflect.*;
  • 1.

一些反射中的常用类:

作用

java.lang.Class

代表整个字节码。代表一个类型,代表整个类

java.lang.reflect.Method

代表字节码中的方法字节码。代表类中的方法

java.lang.reflect.Constructor

代表字节码中的构造方法字节码。代表类中的构造方法

java.lang.reflect.Field

代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)

还可以通过字节码对象【Class对象】获取注解以下信息,这些信息不在java.lang.reflect包中,所以单独列出来

作用

java.lang.annotation.Annotation

可以获取类,字段,方法等地方的注解

java.lang.Package

获取所在包

java.lang.ClassLoader

获取类加载器

反射语法应用

反射是将运行中的class文件读取到并封装进反射类【Class<T>】中,并且可以根据反射类获取类中的构造方法,普通方法和属性,接下来根据案例依次演示

获取反射类

咱们写的代码是存储在后缀名是 .java的文件里的,但是它会被编译,最终真正去执行的是编译后的 .class文件。Java是面向对象的语言,一切皆对象,所以java认为这些编译后的 class文件也是一种对象,给抽象成了一种类,这个类就是Class,获取反射类有三种方式:

  • 通过Object类的 getClass方法来获取,所有的类都直接或者间接继承Object类,所有的对象都可以使用该方法
  • 使用 类名.class 的方式
  • 使用Class.forName方法

需求:有以下Student类,我们通过三种方式获取该类的Class对象,并根据Class对象的getName方法,输出类的全路径

student类:

package com.stt.reflect;

public class Student {
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

获取反射类:

public class StudentMain {
    public static void main(String[] args) {
        // 1、通过 getClass 方法获取 Class类,
        // 1) 创建Student类对象
        Student student = new Student();
        // 2) 所有的类都直接或者间接继承Object类,每个对象都有getClass方法
        Class studentClazz1 = student.getClass();
        System.out.println("studentClazz1===>" + studentClazz1.getName());
        // 2、通过.class获取
        Class studentClazz2 = Student.class;
        System.out.println("studentClazz2===>" + studentClazz2.getName());
        // 3、通过Class类的forName方法获取,传入类的全路径
        try {
            // 此方法抛出 ClassNotFoundException 异常,因为填写的类全路径可能错误,该类可能不存在
            Class studentClazz3 = Class.forName("com.stt.reflect.Student");
            System.out.println("studentClazz3===>" + studentClazz3.getName());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

输出结果:

反射获取构造方法

方法

作用

Constructor<T> getConstructor(Class<?>... parameterTypes)

根据参数列表,获取指定数据类型的构造方法,抛出 NoSuchMethodException, SecurityException

Constructor<?>[] getConstructors()

获取所有的构造方法,抛出 SecurityException

改造Student类,增加四个构造方法,默认有一个无参的构造方法

package com.stt.reflect;

public class Student {
    private Long id;
    private String name;
    private String sex;
    private Integer age;

    // 无参构造
    public Student() {
        System.out.println("=======无参构造方法=======");
    }

    // 单参构造
    public Student(String name) {
        this.name = name;
        System.out.println("=======单参构造方法=======name:" + name);
    }

    // 两参构造
    public Student(Long id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("========两参构造方法=======id:" + id + ", name: " + name);
    }

    // 三参构造
    public Student(Long id, String name, String sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        System.out.println("========三参构造方法=======id:" + id + ", name: " + name + ", sex: " +sex);
    }

    // 四参构造
    public Student(Long id, String name, String sex, Integer age) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
        System.out.println("========三参构造方法=======id:" + id + ", name: " + name + ", sex: " + sex + ", age: " +age);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

获取指定参数构造方法

  • 根据getConstructor()方法获取构造方法
  • 通过 Constructor 类的 newInstance()方法可以调用对应的构造方法创建对象
package com.stt.reflect;

import java.lang.reflect.Constructor;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取无参的构造方法
        Constructor<Student> noParamConstructor = clazz.getConstructor();
        // 通过Constructor创建对象
        Student student1 = noParamConstructor.newInstance();
        // 3、获取单灿构造方法,单参是String类型的name,所以参数写为String.class
        Constructor<Student> oneStringParamConstructor = clazz.getConstructor(String.class);
        Student student2 = oneStringParamConstructor.newInstance("添甄");
        // 4、获取两参构造方法,类型分别为Long和String
        Constructor<Student> twoStringParamConstructor = clazz.getConstructor(Long.class,String.class);
        Student student3 = twoStringParamConstructor.newInstance(1L,"添甄");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

运行结果:

获取所有构造方法

将无参的构造方法设置为私有

private Student() {
    System.out.println("=======无参构造方法=======");
}
  • 1.
  • 2.
  • 3.

获取所有构造方法:

package com.stt.reflect;

import java.lang.reflect.Constructor;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取所有构造方法
        Constructor<Student>[] constructors = clazz.getConstructors();

        // 3、遍历构造方法
        for (Constructor<Student> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

输出结果:

可以获取所有的公开构造方法,私有构造获取不到

获取私有构造

小贴士:通过反射获取私有的构造函数时,用普通的getConstructor()会报错,因为它是私有的,所以提供了专门反射私有构造函数的方法 getDeclaredConstructor(int.class);

如果要调用这个方法还需要设置一下暴力反射才可以,即调用构造器对象的setAccessible(true);方法

获取所有私有方法:

package com.stt.reflect;

import java.lang.reflect.Constructor;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取所有构造方法,包括私有
        Constructor<Student>[] constructors = clazz.getDeclaredConstructors();
        // 3、遍历构造方法
        for (Constructor<Student> constructor : constructors) {
            System.out.println(constructor);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

运行结果:

获取私有构造方法并调用

仅仅获取到调用会出现 IllegalAccessException 异常

package com.stt.reflect;

import java.lang.reflect.Constructor;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取无参的构造方法,包括私有
        Constructor<Student> constructor = clazz.getDeclaredConstructor();
        // 3、此时直接调用会报错
        Student student = constructor.newInstance();

    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

报错:

设置暴力破解即可访问:也就是调用一下 setAccessible(true)方法

package com.stt.reflect;

import java.lang.reflect.Constructor;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取无参的构造方法,包括私有
        Constructor<Student> constructor = clazz.getDeclaredConstructor();
        // 3、暴力破解,允许调用私有方法
        constructor.setAccessible(true);
        // 4、此时直接调用会报错
        Student student = constructor.newInstance();

    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

运行结果:

反射获取方法

在Student类中添加以下普通方法

package com.stt.reflect;

public class Student {
    // ......
    
    public void speak() {
        System.out.println("你好啊!" + name);
    }

    public Integer getAge() {
        System.out.println("getAge===》" + this.age);
        return this.age;
    }
    
    public void setName(String name) {
        this.name = name;
        System.out.println("设置名字为:" + name);
    }
    
    public static void skill() {
        System.out.println("======skill静态方法======");
    }

    private void method1() {
        System.out.println("======私有方法======");
    }

    private static void method2(String[] args) {
        System.out.println("======私有静态方法======" + args);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.

获取所有非私有方法

获取方法,通过 Method 类型对象封装

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取所有非私有方法,包括父类中方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

如下图:获取到了所有的非私有方法,包括父类中的方法

获取所有方法

通过调用getDeclaredMethods方法获取所有方法,

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、获取所有非私有方法,包括父类中方法
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

如下,获取了包括私有方法,但是不包括父类中的方法

获取指定方法并调用

我们可以通过 getMethod(String name, Class<?>... parameterTypes) 方法,根据方法名和参数获取方法,在通过Method对象的 invoke(Object obj, Object... args)方法调用方法,如果是私有方法,你懂的,需要暴力破解一下再调用,和构造器一样

  • 调用四个参数构造方法创建student对象
  • 分别调用speak,getAge和setName方法,分别对应无返回值和有返回值和有参数等情况
package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");

        // 2、获取无参的 speak 方法
        Method speakMethod = clazz.getMethod("speak");
        // 3、创建Student对象
        Constructor<Student> constructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
        Student student = constructor.newInstance(1L,"添甄","男",26);
        // 传入 student,意为要调用student对象的speak
        Object returnResult = speakMethod.invoke(student);
        System.out.println("方法返回值==》" + returnResult);

        // 4、调用getAge方法,没有参数有返回值
        Method ageMethod = clazz.getMethod("getAge");
        Integer returnAge = (Integer)ageMethod.invoke(student);
        System.out.println(returnAge);
        
        // 5、调用有参数的方法,getMethod参数2写为参数的类型
        Method setNameMethod = clazz.getMethod("setName",String.class);
        setNameMethod.invoke(student, "王小波");

    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.

调用私有方法

方式和调用私有构造器一致,首先通过 getDeclaredMethod 获取私有方法,其次调用 setAccessible(true) 设置可访问为true

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、创建Student对象
        Constructor<Student> constructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
        Student student = constructor.newInstance(1L,"添甄","男",26);
        // 3、通过 getDeclaredMethod 获取私有方法 private void method1()

        Method method1 = clazz.getDeclaredMethod("method1");
        // 设置为可访问 true
        method1.setAccessible(true);
        // 调用方法
        Object invoke = method1.invoke(student);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

参数为数组

参数维数组有点特殊,这是因为 jdk1.4和jdk1.5处理invoke方法有区别

1.5:public Object invoke(Object obj,Object…args):1.5参数为Object类型的可变参数

1.4:public Object invoke(Object obj,Object[] args):1.4参数为一个Object类型数组

在反射方法时,如果方法的参数是一个数组,考虑到向下兼容问题,会按照JDK1.4的语法来对待(JVM会把传递的数组参数拆开,拆开就会报参数的个数不匹配的错误) 解决办法:防止JVM拆开你的数组 方式一:把数组看做是一个Object对象 方式二:重新构建一个Object数组,那个参数数组作为唯一的元素存在。

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、创建Student对象
        Constructor<Student> constructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
        Student student = constructor.newInstance(1L,"添甄","男",26);
        // 3、通过 getDeclaredMethod 获取私有方法 private void method1()

        Method method1 = clazz.getDeclaredMethod("method2",String[].class);
        // 设置为可访问 true
        method1.setAccessible(true);
        // 调用方式1:将数组强转为Object对象
         method1.invoke(student,(Object)new String[]{"a","b"});

        // 调用方式2:将数组存储进Object数组中
        method1.invoke(student,new Object[]{new String[]{"a","b"}});
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

错误调用:如果直接传进数组,则会出现IllegalArgumentException【非法参数异常】报错

Method method1 = clazz.getDeclaredMethod("method2",String[].class);
// 设置为可访问 true
method1.setAccessible(true);
// 错误调用:直接传递数组
method1.invoke(student,new String[]{"a","b"});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

反射获取属性

设置name属性为public

private Long id;
public String name;
private String sex;
private Integer age;
  • 1.
  • 2.
  • 3.
  • 4.

相关方法:

方法

作用

Field getField(String name)

获取指定非私有属性

Field[] getFields()

获取所有非私有属性

Field getDeclaredField(String name)

获取指定私有属性

Field[] getDeclaredFields()

获取所有私有属性

获取属性并通过get方法获取属性值

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、创建Student对象
        Constructor<Student> constructor = clazz.getConstructor(Long.class,String.class,String.class,Integer.class);
        Student student = constructor.newInstance(1L,"添甄","男",26);

        // 3、获取name属性
        Field nameField = clazz.getField("name");
        // 4、获取所有费私有属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println("field name====》" + field.getName());
            System.out.println("字段值==》" + field.get(student));
        }
        
        // 5、获取指定私有属性
        Field sexField = clazz.getDeclaredField("sex");
        sexField.setAccessible(true);
        System.out.println(sexField.get(student));
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

运行结果:

总结

  • 要使用反射首先通过上述三种方式获取Class对象,该对象有泛型
  • 可以根据相关get方法获取非私有的构造方法,普通方法和属性
  • 可以根据 相关getDeclaredXXX系列方法,获取私有的构造方法,普通方法和属性,如果是私有需要调用对应的 setAccessible(true)允许暴力访问进行操作
  • 调用方法和属性时,需要指定一个对象,即调用该对象的指定方法
  • Class、Constructor、Method和Field的方法其实都比较简单,可以自己动手调用一下,印象会更深刻
  • 我们还可以根据获取Class、Method、Field、Constructor获取对应的注解,即 Annotation对象
  • 而且私有的方法和属性都可以被强制调用,反射破坏了封装性,带来一些安全隐患

反射应用场景

Tomcat加载文件

使用Servlet编写web应用时,往往都有一个web.xml文件,

<!-- 配置方式一 --> 
<servlet> 
    <servlet-name>my3</servlet-name> 
    <servlet-class>com.servlet.MyServlet3</servlet-class>
</servlet> 
<servlet-mapping> 
    <servlet-name>my3</servlet-name> 
    <url-pattern>/my3</url-pattern>
</servlet-mapping>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

该文件会交给Tomcat运行:

  1. Tomcat服务器首先读取配置文件web.xml中配置好的Servlet的子类名称
  2. Tomcat根据读取到的客户端实现的Servlet子类的类名字符串去寻找对应的字节码文件,如果找到就将其加载到内存
  3. Tomcat通过预先设置好的Java反射处理机制解析字节码文件并创建相应的实例对象,之后调用所需要的方法
  4. Tomcat一启动,用户自定义的Servlet的子类通过Tomcat内部的反射框架也随之运行

Spring框架的IOC

就还比如spring框架的xml文件的bean配置,都是通过读取class属性值,通过反射创建对象

<bean class="com.tianzhen.spring.beans.User" name="user"/>
  • 1.

动态代理

代理模式就是为某对象创建一个代理对象,然后通过代理对象代替原对象执行程序,已达到增强对象的作用

在程序运行之前,代理类.class文件就已经被创建了,Java中提供的动态代理就是通过反射实现,动态代理的重要实现接口 InvocationHandler就要求代理类重写 invoke 方法,我们这里可以实现一个PersonService类的代理对象

接口:

public interface IPersonService {
    void speak();
}
  • 1.
  • 2.
  • 3.

PersonService类

public class PersonService implements IPersonService{
    @Override
    public void speak() {
        System.out.println("君不见,高堂明镜悲白发,朝如青丝暮成雪。");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

PersonService代理对象

public class PersonServiceProxy implements InvocationHandler {
    //业务类对象
    private Object target;

    public PersonServiceProxy(Object target){
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        System.out.println("invoke开始");
        result = method.invoke(target,args);
        System.out.println("invoke结束");
        return result;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

测试类

public class Test {
    public static void main(String[] args) {
        //被代理对象
        PersonService personService = new PersonService();
        //代理对象
        PersonServiceProxy serviceProxy = new PersonServiceProxy(personService);
        // 通过反射,获取类加载器
        ClassLoader classLoader = personService.getClass().getClassLoader();
        // 通过反射,获取该类实现的接口
        Class<?>[] interfaces = personService.getClass().getInterfaces();
        IPersonService iPersonService = (IPersonService)Proxy.newProxyInstance(classLoader, interfaces, serviceProxy);
        iPersonService.speak();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

由此实现,在调用speak方法之前和之后都可以执行一些其他操作

反射项目案例

之前做的项目中也应用到反射,来导出指定字段的数据,需求如下:

  • 学生有部分字段,现需要根据用户选中的字段进行指定字段的导出
  • 并且导出的excel表中的表头需要和系统中一致

此时我的实现方式就是在字段上添加自定义注解 @Excel,注解的name属性就是该字段在excel表中的名字

@Excel(name = "年级(必填)")
private String stuGrade;
@Excel(name = "学号(必填)")
private String stuNo;
@Excel(name = "姓名(必填)")
private String stuName;
@Excel(name = "性别(必填)")
private String stuSex;

@Excel(name = "出生年月")
private String stuBirth;
@Excel(name = "年龄")
private Integer stuAge;
@Excel(name = "民族(必填)")
private String stuNation;
@Excel(name = "手机号码")
private String stuMobile;
@Excel(name = "政治面貌")
private String stuAff;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

其次,在接口中获取前端传过来的字段名字,后端通过反射匹配,在获取到字段上的 @Excel注解的name属性去生成excel表格

// 获取用户需要导出的字段名,如stuGrade,stuNo
String[] columns = stuVo.getColumns();
// 通过反射获取学生字节码对象
Class<StuVo> stuVoClass = StuVo.class;
// 存储字段名
List<String> fieldsName = new ArrayList<>();
if(StringUtils.isNotNull(columns) && columns.length > 0) {
    // 获取用户需要的所有的列名
    for (String column : columns) {
        Field field = null;
        try {
            // 获取对应的字段,因为字段都是private所以使用 getDeclaredField 方法
            field = stuVoClass.getDeclaredField(column);
        } catch (NoSuchFieldException e) {
            throw new CustomException("找不到对应的列");
        }
        // 根据字段 获取字段上的 Excel 注解
        Excel annotation = field.getAnnotation(Excel.class);
        if(StringUtils.isNotNull(annotation)) {
            // 获取注解的name属性值
            String name = annotation.name();
            // 将name属性值存储到集合中
            fieldsName.add(name);
        }
    }
}else {
    throw new CustomException("请选择要导出的列!");
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

以次来实现,生成excel表时的列名,这就是反射在我的项目中的一个应用

反射创建对象

我们这里分别通过反射和new分别创建 10000000 个对象,查看耗时两者差异

package com.stt.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class StudentMain {
    public static void main(String[] args) throws Exception {
        // 1、获取Class类,这里将异常向上抛出
        Class clazz = Class.forName("com.stt.reflect.Student");
        // 2、通过反射 创建 10000000 个Student对象
        Constructor<Student> constructor = clazz.getConstructor();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            Student student = constructor.newInstance();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("通过反射创建10000000个对象耗时:" + (endTime - startTime) + "毫秒");
        
        // 3、通过new 创建 10000000 个Student对象
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            Student student = new Student();
        }
        endTime = System.currentTimeMillis();
        System.out.println("通过new创建10000000个对象耗时:" + (endTime - startTime) + "毫秒");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

运行结果:发现调用空参构造,反射耗时147毫秒,new仅仅需要2毫秒,调用有参的构造方法耗时几乎一致,仅仅是赋值,并没有复杂的构造,由此发现反射的性能其实是比较低的

高手回答反射优缺点

优点

  • 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作
  • 提高代码的复用率,比如动态代理,就是用到了反射来实现
  • 可以在运行时轻松获取任意一个类的方法、属性并且还能通过反射进行动态调用

缺点

  • 反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。
  • 使用反射以后,代码的可读性会下降
  • 反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性​