目录
- 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)输出结果为:
老师在上课。