当前位置: 首页 > news >正文

《Java反射与动态代理:从原理到实践》

在Java开发中,反射和动态代理是两个强大的底层技术,它们为框架设计、代码解耦和功能增强提供了核心支持。本文将从概念解析、核心API、代码示例到实际应用,全面讲解反射与动态代理的使用,帮助开发者理解其原理并灵活运用到项目中。

一、Java反射机制

反射是Java语言的核心特性之一,它允许程序在运行状态下动态获取类的信息(属性、方法、构造器等),并动态调用对象的方法,无需在编译期确定具体的类和方法。

1.1 反射的核心价值

专业定义

对于任意一个类,能获取其所有属性和方法;对于任意一个对象,能调用其任意属性和方法——这种动态获取信息、动态调用方法的功能,称为Java反射机制。

通俗理解(重点掌握)
  • 无视访问修饰符:通过反射可访问类的私有属性、私有方法和私有构造器(需临时修改访问权限)。
  • 配置化开发:将类名、方法名写入配置文件,运行时读取配置动态创建对象、调用方法,需求变更时无需修改代码,仅需修改配置。

1.2 反射的学习核心

反射的操作本质是操作Class字节码文件对象(内存中唯一的字节码对象),核心学习内容分为4部分:

  1. 如何获取Class字节码文件对象
  2. 如何通过反射获取构造器并创建对象
  3. 如何通过反射获取成员变量并读写值
  4. 如何通过反射获取成员方法并调用

1.3 获取Class字节码对象的三种方式

Class字节码对象是反射的“入口”,每个类在内存中仅有一个Class对象,以下是三种获取方式的对比:

获取方式代码示例适用场景
Class.forName(“全类名”)Class clazz = Class.forName("com.demo.Student");最常用,适合配置化开发(从配置文件读取全类名)
类名.classClass clazz = Student.class;已知具体类,编译期确定类
对象.getClass()Student s = new Student(); Class clazz = s.getClass();已知对象实例,需动态获取其类型

注意:三种方式获取的Class对象是同一个(内存中唯一),如下代码验证:

Class clazz1 = Class.forName("com.demo.Student");
Class clazz2 = Student.class;
Student s = new Student();
Class clazz3 = s.getClass();System.out.println(clazz1 == clazz2); // true
System.out.println(clazz2 == clazz3); // true

1.4 字节码文件与字节码对象的区别

  • 字节码文件(.class):Java文件编译后生成的硬盘文件,是静态的物理文件。
  • 字节码对象(Class对象):.class文件加载到JVM内存后,JVM自动创建的对象,包含类的构造器、属性、方法等信息,是动态的内存对象。

1.5 通过反射获取构造器

构造器的作用是创建对象,反射提供了获取“所有构造器”或“指定构造器”的API,支持访问public和private构造器。

核心API(规则:get→获取,Declared→包含私有,s→复数)
方法名说明
Constructor<?>[] getConstructors()获取所有public构造器
Constructor<?>[] getDeclaredConstructors()获取所有构造器(含private)
Constructor getConstructor(Class<?>… params)获取指定public构造器(参数为参数类型的Class对象)
Constructor getDeclaredConstructor(Class<?>… params)获取指定构造器(含private)
代码示例
public class ReflectDemo {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {// 1. 获取Class对象Class clazz = Class.forName("com.demo.Student");// 2. 获取所有public构造器Constructor[] publicCons = clazz.getConstructors();for (Constructor con : publicCons) {System.out.println(con); // 输出public构造器}// 3. 获取所有构造器(含private)Constructor[] allCons = clazz.getDeclaredConstructors();for (Constructor con : allCons) {System.out.println(con); // 输出public和private构造器}// 4. 获取指定构造器(空参public)Constructor emptyCon = clazz.getConstructor();System.out.println(emptyCon);// 5. 获取指定构造器(带参private)Constructor privateCon = clazz.getDeclaredConstructor(String.class, int.class);System.out.println(privateCon);}
}

1.6 通过反射创建对象(关键:newInstance())

获取构造器后,通过newInstance(参数)方法创建对象。若构造器是private的,需先调用setAccessible(true)临时取消访问检查(暴力反射)。

代码示例(Student类见下文)
public class ReflectCreateObj {public static void main(String[] args) throws Exception {Class clazz = Class.forName("com.demo.Student");// 1. 空参构造器创建对象(public)Constructor emptyCon = clazz.getConstructor();Student stu1 = (Student) emptyCon.newInstance(); // 无参数System.out.println(stu1); // Student{name=null, age=0}// 2. 带参构造器创建对象(private)Constructor privateCon = clazz.getDeclaredConstructor(String.class, int.class);privateCon.setAccessible(true); // 临时开放权限Student stu2 = (Student) privateCon.newInstance("张三", 23);System.out.println(stu2); // Student{name=张三, age=23}}
}// 对应的Student类(JavaBean)
class Student {private String name;private int age;public Student() {} // 空参publicprivate Student(String name, int age) { // 带参privatethis.name = name;this.age = age;}// getter/setter/toString省略
}

1.7 通过反射操作成员变量

反射可获取成员变量并读写其值,支持public和private变量(private需setAccessible(true))。

核心API
方法名说明
Field[] getFields()获取所有public成员变量
Field[] getDeclaredFields()获取所有成员变量(含private)
Field getField(String name)获取指定public变量(参数为变量名)
Field getDeclaredField(String name)获取指定变量(含private)
void set(Object obj, Object value)给obj对象的该变量赋值
Object get(Object obj)获取obj对象的该变量值
代码示例(读写私有变量name)
public class ReflectField {public static void main(String[] args) throws Exception {Student stu = new Student("张三", 23);Class clazz = stu.getClass();// 1. 获取私有变量nameField nameField = clazz.getDeclaredField("name");nameField.setAccessible(true); // 开放权限// 2. 读取name的值String oldName = (String) nameField.get(stu);System.out.println("修改前:" + oldName); // 张三// 3. 修改name的值nameField.set(stu, "李四");String newName = (String) nameField.get(stu);System.out.println("修改后:" + newName); // 李四}
}

1.8 通过反射调用成员方法

反射可获取成员方法并调用,支持public和private方法(private需setAccessible(true))。

核心API
方法名说明
Method[] getMethods()获取所有public方法(含父类public方法)
Method[] getDeclaredMethods()获取所有方法(含private,仅当前类)
Method getMethod(String name, Class<?>… params)获取指定public方法(参数1:方法名,参数2:方法参数类型的Class对象)
Method getDeclaredMethod(String name, Class<?>… params)获取指定方法(含private)
Object invoke(Object obj, Object… args)调用obj对象的该方法(参数1:调用对象,参数2:方法实际参数)
代码示例(调用私有方法study()和public方法eat())
public class ReflectMethod {public static void main(String[] args) throws Exception {Student stu = new Student();Class clazz = stu.getClass();// 1. 调用私有方法study()(无参)Method studyMethod = clazz.getDeclaredMethod("study");studyMethod.setAccessible(true);studyMethod.invoke(stu); // 输出:学生在学习// 2. 调用public方法eat()(带参,有返回值)Method eatMethod = clazz.getMethod("eat", String.class);String result = (String) eatMethod.invoke(stu, "重庆小面");System.out.println(result); // 输出:学生已经吃完了,非常happy}
}// Student类补充方法
class Student {// 私有方法private void study() {System.out.println("学生在学习");}// public带参方法(有返回值)public String eat(String something) {System.out.println("学生在吃" + something);return "学生已经吃完了,非常happy";}
}

1.9 反射的经典应用场景

场景1:泛型擦除(理解泛型的本质)

集合的泛型仅在编译期有效,编译为.class文件后泛型被擦除。通过反射可突破泛型限制,向ArrayList<Integer>中添加字符串。

public class GenericErasure {public static void main(String[] args) throws Exception {ArrayList<Integer> list = new ArrayList<>();list.add(123); // 正常添加Integer// 反射突破泛型限制Class clazz = list.getClass();Method addMethod = clazz.getMethod("add", Object.class);addMethod.invoke(list, "abc"); // 添加StringSystem.out.println(list); // 输出:[123, abc]}
}
场景2:修改字符串内容(理解String不可变的本质)

String的不可变源于底层private final byte[] valueprivate限制外部访问,final限制value引用不可变。通过反射可修改value数组的内容(JDK高版本已屏蔽此操作,低版本可验证)。

public class ModifyString {public static void main(String[] args) throws Exception {String s = "abc";Class clazz = s.getClass();// 获取value数组Field valueField = clazz.getDeclaredField("value");valueField.setAccessible(true);byte[] value = (byte[]) valueField.get(s);// 修改数组内容value[0] = 100; // 'd'的ASCII码是100System.out.println(s); // 输出:dbc}
}
场景3:配置化动态创建对象(框架核心思想)

将类名、方法名写入配置文件,运行时读取配置动态创建对象、调用方法,需求变更无需改代码。

步骤1:编写配置文件(prop.properties)

classname=com.demo.Student
methodname=sleep

步骤2:通过反射读取配置并执行

public class ConfigReflect {public static void main(String[] args) throws Exception {// 1. 读取配置文件Properties prop = new Properties();prop.load(new FileInputStream("prop.properties"));String className = prop.getProperty("classname");String methodName = prop.getProperty("methodname");// 2. 动态创建对象Class clazz = Class.forName(className);Object obj = clazz.getDeclaredConstructor().newInstance();// 3. 动态调用方法Method method = clazz.getDeclaredMethod(methodName);method.invoke(obj); // 输出:学生在睡觉}
}
场景4:通用对象保存(任意对象的属性写入文件)

编写通用方法,将任意对象的所有属性名和值保存到本地文件,无需为每个类单独编写保存逻辑。

public class SaveObject {// 通用方法:保存任意对象的属性到文件public static void save(Object obj) throws Exception {Class clazz = obj.getClass();BufferedWriter bw = new BufferedWriter(new FileWriter("obj.txt"));// 获取所有属性并写入文件Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {field.setAccessible(true);String fieldName = field.getName();Object fieldValue = field.get(obj);bw.write(fieldName + "=" + fieldValue);bw.newLine();}bw.close();}public static void main(String[] args) throws Exception {Student stu = new Student("小A", 23, '女', 167.5, "睡觉");save(stu); // 执行后obj.txt中会有stu的所有属性}
}

1.10 反射的优缺点

优点缺点
1. 动态获取类信息,支持配置化开发;
2. 突破访问修饰符限制,方便框架底层实现;
3. 通用化代码,减少重复开发。
1. 破坏封装性(访问私有成员);
2. 性能开销(动态解析字节码,比直接调用慢);
3. 编译期无法检查类型,易引发运行时异常。

二、Java动态代理

动态代理是基于反射实现的“增强技术”,它能在不修改原始类代码的前提下,为方法添加额外功能(如日志、权限校验、性能统计),实现“无侵入式增强”。

2.1 动态代理的核心概念

  • 真实对象:真正执行业务逻辑的对象(如“大明星”)。
  • 代理对象:由JVM动态生成的对象,负责“增强真实对象的方法”或“拦截方法调用”(如“经纪人”)。
  • 核心原则:代理仅能增强“接口中定义的方法”,需将待增强方法定义在接口中。

2.2 动态代理的三要素

  1. 接口:定义待增强的方法(如Star接口的sing()dance())。
  2. 真实对象:实现接口,执行业务逻辑(如BigStar类)。
  3. 代理对象:由Proxy类动态生成,通过InvocationHandler定义增强逻辑。

2.3 动态代理的实现(核心:Proxy.newProxyInstance())

Java通过java.lang.reflect.Proxy类生成代理对象,核心方法是newProxyInstance(),需传入3个参数:

  1. 类加载器:用于加载动态生成的代理类(通常用当前类的类加载器)。
  2. 接口数组:代理对象需实现的接口(必须包含待增强的方法)。
  3. InvocationHandler:匿名内部类,定义代理对象的增强逻辑(核心)。
代码示例:经纪人代理大明星

步骤1:定义接口(Star)

// 定义待增强的方法
public interface Star {String sing(String songName); // 唱歌(有返回值)void dance(); // 跳舞(无返回值)
}

步骤2:实现真实对象(BigStar)

// 真实对象:大明星
public class BigStar implements Star {private String name;public BigStar(String name) {this.name = name;}// 实现唱歌方法@Overridepublic String sing(String songName) {System.out.println(this.name + "正在唱《" + songName + "》");return "谢谢大家!";}// 实现跳舞方法@Overridepublic void dance() {System.out.println(this.name + "正在跳舞");}
}

步骤3:创建代理工具类(ProxyUtil)

// 代理工具类:生成代理对象
public class ProxyUtil {// 通用方法:为真实对象创建代理public static Star createProxy(BigStar realStar) {// 动态生成代理对象Star proxy = (Star) Proxy.newProxyInstance(// 参数1:类加载器(加载代理类)ProxyUtil.class.getClassLoader(),// 参数2:接口数组(代理需实现的接口)new Class[]{Star.class},// 参数3:增强逻辑(核心)new InvocationHandler() {/**
http://www.xdnf.cn/news/1401571.html

相关文章:

  • SpringBoot整合Actuator实现健康检查
  • MIT 6.5840 (Spring, 2024) 通关指南——Lab 1: MapReduce
  • GitHub 热榜项目 - 日榜(2025-08-30)
  • 基于Ubuntu本地GitLab 搭建 Git 服务器
  • 解构机器学习:如何从零开始设计一个学习系统?
  • 【LeetCode】大厂面试算法真题回忆(121) —— 经典屏保
  • 并发编程——09 CountDownLatch源码分析
  • Spring Boot 后端接收多个文件的方法
  • 项目管理常用的方法有哪些
  • 三菱 PLC的中断指令/中断指针
  • 构建现代化的“历史上的今天“网站:从API到精美UI的全栈实践
  • 北方苍鹰优化算法优化的最小二乘支持向量机NGO-LSSVM多输入多输出回归预测【MATLAB】
  • 2025年06月 Scratch 图形化(二级)真题解析#中国电子学会#全国青少年软件编程等级考试
  • Robolectric如何启动一个Activity
  • 倾斜摄影是选择RGB图像还是多光谱影响进行操作?
  • Transformer:从入门到精通
  • 嵌入式Linux驱动开发:蜂鸣器驱动
  • stack queue的实现 deque的底层结构 priority_queue的实现
  • 【Java实战⑦】从入门到精通:Java异常处理实战指南
  • 漫谈《数字图像处理》之分水岭分割
  • AUTOSAR进阶图解==>AUTOSAR_TR_ClassicPlatformReleaseOverview
  • 计算机毕设项目 基于Python与机器学习的B站视频热度分析与预测系统 基于随机森林算法的B站视频内容热度预测系统
  • observer pattern 最简上手笔记
  • 如何调整Linux系统下单个文件的最大大小?
  • hadoop安欣医院挂号看诊管理系统(代码+数据库+LW)
  • 2025年高性能计算年会
  • centos7.9的openssh漏洞修复脚本
  • w嵌入式分享合集125
  • 【Day 33】Linux-MySQL 备份与恢复详解
  • 【机器学习入门】3.3 FP树算法——高效挖掘频繁项集的“树状神器”