目录

  • 1.概述
  • 1.1.什么是反射?
  • 1.2.反射的优缺点
  • 1.3.反射的应用场景
  • 1.4.为什么框架需要反射?
  • 2.获取 Class 类对象的三种方式
  • 3.通过反射获取构造方法
  • 4.通过反射获取公共构造方法并创建对象
  • 5.通过反射获取私有构造方法并创建对象
  • 6.通过反射取成员变量对象
  • 7.通过反射获取私有的成员变量
  • 8.通过反射获取成员方法
  • 9.通过反射获取成员方法并调用
  • 10.反射案例 1:越过泛型检查
  • 11.反射案例 2:运行 properties 配置文件中指定类的指定方法


本文章大部分笔记整理来自于视频https://www.bilibili.com/video/BV12k4y1q73J?p=322

1.概述

1.1.什么是反射?

(1)反射是指在程序运行时获取一个类的变量和方法信息(即 JVM 读取相应 Class 字节码文件),然后通过获取的信息来创建对象并调用方法的一种机制。这种动态性可以极大地增强程序的灵活性,使得程序不用在编译期就确定,而在运行期仍然可以扩展。

1.2.反射的优缺点

(1)优点

  • 能够运行时动态获取类的实例,提高灵活性;
  • 与动态编译结合;

(2)缺点

  • 使用反射性能较低,需要解析字节码,将内存中的对象进行解析;解决方案:
  • 通过 setAccessible(true) 关闭 JDK 的安全检查来提升反射速度;
  • 多次创建一个类的实例时,有缓存会快很多 ;
  • ReflectASM 工具类,通过字节码生成的方式加快反射速度;
  • 相对不安全,破坏了封装性(由于通过反射可以获得私有方法和属性);

1.3.反射的应用场景

  • 我们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射机制。比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
    }
}
  • 另外,像 Java 中的注解的实现也用到了反射。为什么你使用 Spring 的时候 ,一个 @Component 注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value 注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理

相关知识点:
Java基础——注解Java 设计模式——代理模式

1.4.为什么框架需要反射?

(1)以 Spring 为例,反射在 Spring 框架中被广泛使用,主要有以下几个方面:

  • 实现 Bean 实例化和依赖注入功能。Spring 框架中的 IoC 容器通过反射来实现 Bean 实例化和依赖注入。在 IoC 容器实例化 Bean 的过程中,使用反射创建 Bean 对象,并通过反射设置对象的属性。这些操作都是在运行时动态地完成的。
  • 实现 AOP 切面功能。Spring 框架中的 AOP 功能通过代理模式和动态代理技术实现,而动态代理技术正是基于 Java 反射机制完成的。Spring 框架通过反射获取类的接口信息和方法信息,动态地创建代理类,然后将原有的方法替换为代理方法,从而实现 AOP 功能。
  • 访问和操作对象信息。Spring 框架中的部分组件需要访问和操作对象信息,比如数据访问组件 JDBC Template 和 ORM (Object-Relational Mapping) 框架 Hibernate、MyBatis 等等。这些组件需要动态地获取对象信息和属性值,通过反射机制可以很方便地实现这些操作。

(2)总的来说,Java 反射机制为 Spring 框架提供了很重要的支持,可以说两者是密不可分的。反射机制使 Spring 框架可以在运行时动态地访问和操作对象信息,实现了 IoC 和 AOP 两大核心特性。反射机制可以提高 Spring 框架的灵活性和可扩展性,使开发人员更加轻松地开发和维护 Spring 应用程序。

2.获取 Class 类对象的三种方式

(1)获取 Class 类对象的三种方式如下所示:

方式

作用

类名.class属性

一般当做锁对象使用

对象名.getClass()方法

一般用来判断两个对象是否是同一个类型的对象

Class.forName(“全类名”)

一般需要结合反射使用

① 类名.class属性和对象名.getClass() 方法是使用最简单的方式获取Class对象,主要区别在于前者根据编译时期已知的类名获取后者根据运行时期已创建的对象获取
② Class.forName(全类名) 用于通过类名字符串动态加载类,它是一种更加灵活的方式,可以在运行时期根据需要来获取 Class 对象

(2)三种方式的代码演示如下:

① 创建实体类 Student

package pojo;

public class Student {
   //此时暂不需要定义属性和方法
}

② 使用上述三种方式

package jf_reflection;

import pojo.Student;

//案例: 反射入门, 演示获取类的字节码文件对象.
//一个.java文件, 对应一个.class文件, 即: 一个java文件只有一个 字节码文件对象.
public class Demo01{
    public static void main(String[] args) throws ClassNotFoundException {
        //演示: 获取类的字节码文件对象的3种方式:
        //方式1: 类名.class属性
        Class<Student> class1 = Student.class;
        
        //方式2: 对象名.getClass()方法
        //?表示泛型的通配符, 即: 可以用来表示任意的数据类型
        //? extends Student:  向下限定, 这里的 ? 必须是 Student类或者其子类对象
        //? super Student:    向上限定, 这里的 ? 必须是 Student类或者其父类
        Class<? extends Student> class2 = new Student().getClass();
        
        //方式3: Class.forName("全类名");
        Class<?> class3 = Class.forName("pojo.Student");
        
        //验证结论: 一个.java文件, 对应一个.class文件, 即: 一个java文件只有一个字节码文件对象.
        System.out.println(class1 == class2);	//true
        System.out.println(class1 == class3);	//true
    }
}

3.通过反射获取构造方法

(1)定义实体类Student

package pojo;

//自定义的JavaBean类, 表示学生类
public class Student {
    //成员变量
    private String name;
    private int age;
    public String phone;

    //构造方法
    public Student() {
    }

    private Student(String name) {
        this.name = name;
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }

    //成员方法
    public void show1() {
        System.out.println("show1 方法");
    }

    private void show2() {
        System.out.println("show2 方法");
    }

    private int getSum(int a, int b) {
        return a + b;
    }

    public void eat() {
        System.out.println("Student eat()");
    }

    public void study() {
        System.out.println("学生要学习!");
    }
}

(2)Constructor单词的意思就是构造方法、构造器,反射获取构造方法涉及到Class类中的方法如下:

方法

作用

public Constructor getConstructor(Class… params);

根据传入的参数类型, 获取其对应的构造方法对象, 只能获取公共的构造方法

public Constructor[] getConstructors();

获取指定类中所有的构造方法对象, 只能获取公共的构造方法

public Constructor getDeclaredConstructor(Class… params);

根据传入的参数类型, 获取其对应的构造方法对象(包括私有)

public Constructor[] getDeclaredConstructors();

获取指定类中所有的构造方法对象(包括私有)

反射获取构造方法代码如下:

package jf_reflection;

import java.lang.reflect.Constructor;

/*
	案例: 反射获取构造方法并使用
    获取构造方法的目的: 通过反射的形式, 创建该类的对象
 */
public class Demo02 {
    public static void main(String[] args) throws Exception {
        //需求1: 获取公共的全参构造
        method01();

        //需求2: 获取公共的所有构造方法
        method02();

        //需求3: 获取私有的带String参数的构造方法
        method03();

        //需求4: 获取所有的构造方法, 包括私有
        method04();
    }

    public static void method01() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取指定的构造器对象
        Constructor<?> con = clazz.getConstructor(String.class, int.class);
        //3.打印
        System.out.println(con);
    }

	public static void method02() throws ClassNotFoundException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取公共的所有构造方法
        Constructor<?>[] cons = clazz.getConstructors();
        //3.打印
        for (Constructor<?> con : cons) {
            System.out.println(con);
        }
    }

	public static void method03() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取私有的带 String 参数的构造方法
        Constructor<?> con = clazz.getDeclaredConstructor(String.class, int.class);
        //3.打印
        System.out.println(con);
    }

    public static void method04() throws ClassNotFoundException {
    	//1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取所有的构造方法, 包括私有.
        Constructor<?>[] cons = clazz.getDeclaredConstructors();
        //3.打印.
        for (Constructor<?> con : cons) {
            System.out.println(con);
        }
    }
}

4.通过反射获取公共构造方法并创建对象

方法

作用

public Constructor getDeclaredConstructor(Class… params);

根据传入的参数类型, 获取其对应的构造方法对象(包括私有)

public T newInstance();

根据类的构造方法直接创建对象

public T newInstance(Object… values);

根据类的构造方法直接创建对象, 并设置值

具体代码如下:

package jf_reflection;

import pojo.Student;

import java.lang.reflect.Constructor;

/*
	案例: 通过反射获取公共的构造方法并创建对象
    获取构造方法的目的:通过反射的形式, 创建该类的对象.
 */
public class Demo03 {
    public static void main(String[] args) throws Exception {
        //需求: 通过反射获取公共的构造方法并创建对象
        //1.获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");

        //2.通过Class#newInstance()方法来创建Student类的对象.
        //Object obj = clazz.newInstance();
        Student s1 = (Student) clazz.newInstance();

        //3.通过 Constructor 构造器对象, 来创建学生类的对象.
        //3.1.获取指定的构造器对象.
        Constructor<?> con = clazz.getDeclaredConstructor();
        //3.2.根据构造器对象, 来创建学生类的对象.
        Student s2 = (Student) con.newInstance();

        //4.打印
        System.out.println(s1 == s2);           //false
        System.out.println("s1: " + s1);
        System.out.println("s2: " + s2);
    }
}

5.通过反射获取私有构造方法并创建对象

方法

作用

public Constructor getDeclaredConstructor(Class… params);

根据传入的参数类型, 获取其对应的构造方法对象(包括私有)

public T newInstance(Object… values);

根据类的构造方法直接创建对象, 并设置值.

public void setAccessible(boolean flag);

暴力反射, 如果传 true, 说明取消访问检查

具体代码如下:

package jf_reflection;

import pojo.Student;
import java.lang.reflect.Constructor;

/*
	案例: 通过反射获取私有构造方法并创建对象
    获取构造方法的目的:通过反射的形式, 创建该类的对象.
 */
public class Demo04 {
    public static void main(String[] args) throws Exception {
        //需求: 通过反射获取公共的构造方法并创建对象
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");

        //2. 通过Constructor构造器对象, 来创建学生类的对象.
        //2.1 获取指定的构造器对象.
        Constructor<?> con = clazz.getDeclaredConstructor(String.class);

        //2.2 暴力反射, 即: 取消访问检查.
        con.setAccessible(true);

        //2.3 根据构造器对象, 来创建学生类的对象.
        Student s2 = (Student) con.newInstance("小李");

        //4. 打印
        System.out.println(s2);
    }
}

6.通过反射取成员变量对象

Field单词的意思就是字段、变量,反射获取成员变量涉及到Class类中的方法如下:

方法

作用

public Field getField(String name);

根据传入的变量名, 获取其对应的成员变量对象, 只能获取公共的

public Field[] getFields();

取指定类中所有的成员变量对象, 只能获取公共的

public Field getDeclaredField(String name);

根据传入的变量名, 获取其对应的成员变量对象(包括私有)

public Field[] getDeclaredFields();

获取指定类中所有的成员变量对象(包括私有)

具体代码如下:

package jf_reflection;

import pojo.Student;

import java.lang.reflect.Field;

//获取成员变量的目的:就是普通的使用变量, 或者修改某些私有的变量.
public class Demo05 {
    public static void main(String[] args) throws Exception {
        //需求1: 根据成员变量名, 获取指定的公共的成员变量.
        method1();

        //需求2: 获取所有的公共的成员变量.
        method2();

        //需求3: 根据成员变量名, 获取指定的私有的成员变量.
        method3();

        //需求4: 获取所有的的成员变量(包括私有).
        method4();
    }
    
    public static void method1() throws Exception {
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");
        //2. 获取指定的成员变量
        Field field = clazz.getField("phone");
        //3. 创建该类对象.
        Student s = (Student) clazz.newInstance();
        //4. 设置指定对象的指定公共属性值.
        field.set(s, "13112345678");
        //5. 打印学生对象.
        System.out.println(s);
    }
    
    public static void method2() throws Exception {
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");
        //2.  获取指定的私有的成员变量
        Field[] fields = clazz.getFields();
        //3. 打印.
        for (Field field : fields) {
            System.out.println(field);
        }
    }
    
    public static void method3() throws Exception {
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");
        //2. 获取所有的成员变量
        Field field = clazz.getDeclaredField("name");
        //3. 打印
        System.out.println(field);
        
    }
    
    public static void method4() throws ClassNotFoundException {
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");
        //2. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        //3. 打印.
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

7.通过反射获取私有的成员变量

Field类中的成员方法如下:

方法

作用

public Field getDeclaredField(String name);

根据传入的变量名, 获取其对应的成员变量对象(包括私有)

public void set(Object obj, Object value);

设置obj对象的指定属性为指定的值(value)

public void setAccessible(boolean flag);

暴力反射, 如果传true, 说明取消访问检查

具体代码如下:

package jf_reflection;

import pojo.Student;

import java.lang.reflect.Field;

/*
	案例: 通过反射获取私有的成员变量并赋值
    获取成员变量的目的:就是普通的使用变量, 或者修改某些私有的变量.
 */
public class Demo06 {
    public static void main(String[] args) throws Exception {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取所有的成员变量
        Field field = clazz.getDeclaredField("name");
        //3.创建该类对象
        Student s = (Student) clazz.newInstance();

        //4.设置指定对象的指定属性为指定的值.
        field.setAccessible(true);      //暴力反射
        field.set(s, "小李");

        //5. 打印学生对象.
        System.out.println(s);
    }
}

8.通过反射获取成员方法

Method 单词的意思就是方法,反射获取成员方法涉及到 Class 类中的方法如下:

方法

作用

public Method getMethod(String name, Class… params);

根据传入的方法名, 获取其对应的成员方法对象,只能获取公共的

public Method[] getMethods();

获取指定类中所有的成员方法对象, 只能获取公共的

public Method getDeclaredMethod(String name, Class… params);

根据传入的方法名, 获取其对应的成员方法对象(包括私有)

public Method[] getDeclaredMethods();

获取指定类中所有的成员方法对象(包括私有)

具体代码如下:

package jf_reflection;

import java.lang.reflect.Method;

/*
	案例: 反射获取成员方法并使用
    获取成员方法的目的:就是为了调用该方法, 且反射能越过泛型检查, 一些在编译期搞不定的事儿挪到运行时就可以搞定
 */
public class Demo07 {
    public static void main(String[] args) throws Exception{
        //需求1: 获取公共的成员方法
        method01();

        //需求2: 获取公共的所有成员方法
        method02();

        //需求3: 获取私有的成员方法
        method03();

        //需求4: 获取所有的成员方法, 包括私有
        method04();
    }
    
    public static void method01() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取指定的成员方法
        Method method = clazz.getMethod("show1");
        //3.打印方法对象
        System.out.println(method);
    }
    
    public static void method02() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取公共的所有成员方法
        Method[] methods = clazz.getMethods();
        //3.打印方法对象
        for (Method method : methods) {
            System.out.println(method);
        }
    }
    
    public static void method03() throws ClassNotFoundException, NoSuchMethodException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取指定的成员方法
        Method method = clazz.getDeclaredMethod("show2");
        //3.打印方法对象
        System.out.println(method);
    }
    
    public static void method04() throws ClassNotFoundException {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取指定的成员方法
        Method[] methods = clazz.getDeclaredMethods();
        //3.打印方法对象
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

9.通过反射获取成员方法并调用

过反射获取成员方法并调用涉及到的 Method 类中的成员方法如下:

方法

作用

public Object invoke(Object obj, Object… args)

执行指定对象(obj)的指定方法(method)

具体代码如下:

package jf_reflection;

import pojo.Student;

import java.lang.reflect.Method;

/*
    案例: 通过反射获取成员方法并调用
    获取成员方法的目的:就是为了调用该方法, 且反射能越过泛型检查, 一些在编译期搞不定的事儿挪到运行时就可以搞定.
 */
public class Demo08 {
    public static void main(String[] args) throws Exception{
        //需求1: 调用Student#show2()方法.  通过反射获取私有的成员方法并调用
        method01();
        
        //需求2: 调用Student#getSum()方法.  通过反射获取公共的成员方法并调用
        method02();
    }

    public static void method01() throws Exception {
        //1. 获取该类的字节码文件对象.
        Class<?> clazz = Class.forName("pojo.Student");
        //2. 获取指定的成员方法
        Method method = clazz.getDeclaredMethod("show2");
        //3. 获取学生类的对象.
        Student s = (Student)clazz.newInstance();
        //4. 核心: 暴力反射
        method.setAccessible(true);
        //5. 调用s对象的show2()方法
        method.invoke(s);
    }
    
    public static void method02() throws Exception {
        //1.获取该类的字节码文件对象
        Class<?> clazz = Class.forName("pojo.Student");
        //2.获取指定的成员方法
        Method method = clazz.getDeclaredMethod("getSum",int.class,int.class);
        //3.获取学生类的对象
        Student s = (Student)clazz.newInstance();
        //4. 调用s对象的show2()方法
        Object sum = method.invoke(s, 10, 20);
        System.out.println("sum: " + sum);
    }
}

10.反射案例 1:越过泛型检查

(1)要求:通过反射技术,向一个泛型为Integer的集合中添加一些字符串数据(注意: 泛型只在编译期有效, 在运行时无效);

(2)实现代码如下:

package jf_reflection;

import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectExercise1 {
    public static void main(String[] args) throws Exception {
        //1.定义 ArrayList 集合, 泛型是 Integer
        ArrayList<Integer> list = new ArrayList<Integer>();
        //2.尝试直接往里边添加一些数据
        list.add(10);
        list.add(20);
        //下面一行代码会报错, 因为泛型规定必须是 Integer 类型的数据
        //list.add("hello");
        
        //3.获取ArrayList集合的字节码文件对象.
        Class<? extends ArrayList> clazz = list.getClass();
        //4.获取 add() 方法对象, 并指定其形参类型为: Object.class
        Method method = clazz.getMethod("add", Object.class);
        //下面一行代码会报错, 因为 ArrayList#add() 底层就是一个 Object 类型的方法
        //Method method = clazz.getMethod("add", String.class);
        //5.执行第 4 步获取到的方法对象,往集合中添加数据
        method.invoke(list, "hello");
        method.invoke(list, 10.3);
        method.invoke(list, false);
        //6.打印集合
        System.out.println(list);
    }
}

(3)输出结果如下:

[10, 20, hello, 10.3, false]

11.反射案例 2:运行 properties 配置文件中指定类的指定方法

(1)要求:通过反射技术,运行 properties 配置文件中指定类的指定方法;
(2)实现代码如下:

# teacher.properties
className=pojo.Teacher
methodName=teach
package pojo;

//自定义的 JavaBean 类, 表示老师类
public class Teacher {
    public void teach() {
        System.out.println("老师在上课。");
    }
}
package jf_reflection;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;

//目的: 通过反射体验代码的灵活性和扩展性, 更满足十大黄金开发原则之"对修改关闭, 对扩展开放".
public class ReflectExercise2 {
    public static void main(String[] args) throws Exception {
        //1.通过 Properties 集合类加载数据
        Properties pp = new Properties();
        pp.load(new FileReader("E:\\testData\\teacher.properties"));
        
        //2.获取类名和方法名
        String className = pp.getProperty("className");
        String methodName = pp.getProperty("methodName");
        
        //3.通过类名获取其对应的字节码文件对象
        Class<?> clazz = Class.forName(className);
        
        //4.获取该类的对象
        Object obj = clazz.newInstance();
        
        //5.通过方法名获取该方法
        Method method = clazz.getMethod(methodName);
        
        //6.执行对应的方法即可
        method.invoke(obj);
    }
}

(3)输出结果为:

老师在上课。