一文吃透 Java 反射:三大应用场景 + 动态代理底层原理
一文吃透Java反射:三大应用场景+动态代理底层原理(附可运行代码)
“要想知道什么是反射,就需要先来了解什么是‘正射’。“一般情况下,我们在使用某个类之前已经确定它到底是个什么类了,拿到手就直接可以使用 new 关键字来调用构造方法进行初始化,之后使用这个类的对象来进行操作。”
Writer writer = new Writer();
writer.setName("zhang san");
像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 new 关键字创建对象了。
我们只知道这个类的一些基本信息,就好像我们看电影的时候,为了抓住一个犯罪嫌疑人,警察就会问一些目击证人,根据这些证人提供的信息,找专家把犯罪嫌疑人的样貌给画出来——这个过程,就可以称之为反射。
在Java开发中,反射是一个“看似抽象、实则无处不在”的核心技术——Spring全家桶、MyBatis、动态代理等框架的底层,全靠反射撑起来。很多人入门时觉得反射晦涩,尤其是动态代理的底层逻辑,其实只要抓住“运行时动态操作类”这个核心,再结合可运行代码拆解,就能轻松掌握。
一、先搞懂:反射到底是什么?(一句话吃透)
反射的核心作用,一句话总结:
反射 = 程序运行时,才决定创建哪个类、调用哪个方法、操作哪个属性/注解,不用提前写死 new 类名()。
举个通俗的例子:平时我们写代码,要创建一个UserService对象,得提前写死 UserService service = new UserService();,一旦要换成OrderService,就必须修改代码、重新编译;而反射可以做到“不修改代码,只改配置”,就能动态创建不同的对象、调用不同的方法——这也是框架实现“通用性”的核心秘诀。
反射的核心API很简单,记住3个关键操作即可(后续代码会反复用到):
Class.forName(类全路径):加载类,获取Class对象(反射的入口);clazz.newInstance()/clazz.getConstructor().newInstance():通过Class对象创建类的实例;method.invoke(对象, 参数):通过Method对象,反射调用方法。
二、反射的三大核心应用场景(附可运行代码)
反射的应用场景集中在“需要动态性”的场景中,最常用的就是以下3种,每一种都对应框架的核心底层逻辑,代码极简、可直接复制运行。
场景1:开发通用框架(Spring IOC底层原理)
核心需求
开发框架时,无法提前知道用户会使用哪些类、创建哪些对象,因此需要“通过配置文件/注解”动态加载类、创建对象,保持框架的通用性——这就是Spring IOC容器的核心原理。
可运行代码(含配置文件)
- 核心Java类(ReflectDemo1.java)
import java.util.Properties;
import java.io.FileInputStream;
/**
* 反射场景1:通用框架(Spring IOC底层)
* 需求:不写死new对象,通过配置文件动态加载类、创建对象
*/
public class ReflectDemo1 {
public static void main(String[] args) throws Exception {
// 1. 从配置文件读取“类全路径”(不用写死代码)
Properties prop = new Properties();
// 加载配置文件(放在项目根目录下)
prop.load(new FileInputStream("config.properties"));
String className = prop.getProperty("class.name");
// 2. 反射核心操作:动态加载类、创建对象
Class<?> clazz = Class.forName(className); // 加载类(反射入口)
Object obj = clazz.newInstance(); // 创建对象(无需new)
System.out.println("通过反射创建的对象:" + obj);
}
}
// 测试用的两个类(模拟用户自定义的业务类)
class UserService {}
class OrderService {}
- 配置文件(config.properties,放在项目根目录)
# 只需修改这里的类名,就能创建不同的对象,无需修改Java代码
class.name=UserService
代码讲解(通俗好记)
- 重点:代码中没有写死 new UserService(),所有对象的创建,都由配置文件决定;
- 运行流程:加载配置文件 → 拿到类全路径 → 反射加载类 → 反射创建对象;
- 框架关联:Spring IOC容器就是这么干的——我们写的
@Service、@Component注解,本质是告诉Spring“这个类需要被反射创建”,Spring扫描注解后,通过反射加载类、创建对象,再放进容器中管理。
扩展:为什么框架必须用反射?
如果不用反射,框架就得写死所有可能的类,比如if("UserService".equals(className)) new UserService();,这样既不通用,也无法适配用户自定义的类;而反射通过“运行时动态加载”,完美解决了“代码不写死、可配置”的问题。
场景2:动态代理(Spring AOP底层原理)
这是反射最核心、最容易看不懂的场景,尤其是Proxy.newProxyInstance()的底层逻辑,我们一步步拆解,保证一看就懂。
核心需求
不修改原业务方法的代码,给方法“偷偷加功能”——比如加日志、加权限校验、加事务,这就是Spring AOP(面向切面编程)的核心需求,而实现这一需求的底层技术,就是“动态代理+反射”。
可运行代码(极简版AOP)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 反射场景2:动态代理(Spring AOP底层)
* 需求:不修改addUser()方法,给它加前置日志、后置日志
*/
public class ReflectDemo2 {
public static void main(String[] args) {
// 1. 创建真实业务对象(原方法所在的对象)
UserService service = new UserServiceImpl();
// 2. 核心:通过反射生成“代理对象”(套壳对象)
UserService proxy = (UserService) Proxy.newProxyInstance(
service.getClass().getClassLoader(), // 类加载器(和真实对象一致)
service.getClass().getInterfaces(), // 实现的接口(和真实对象一致)
new MyInvocationHandler(service) // 拦截处理器(增强逻辑在这里)
);
// 3. 调用代理对象的方法(不是直接调用真实对象)
proxy.addUser();
}
}
// 拦截处理器(反射核心,增强逻辑写在这里)
class MyInvocationHandler implements InvocationHandler {
// 真实业务对象(需要被增强的对象)
private Object target;
// 构造方法:传入真实对象
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 所有代理对象的方法调用,都会进入这个方法
* @param proxy 代理对象本身(一般不用)
* @param method 被调用的方法(比如addUser())
* @param args 方法的参数(比如addUser()没有参数,就是null)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
// 前置增强:方法执行前做的事(比如日志、权限校验)
System.out.println("【前置增强】方法执行前,记录日志");
// 反射核心:调用真实对象的原方法(不修改原代码,通过反射调用)
Object result = method.invoke(target, args);
// 后置增强:方法执行后做的事(比如日志、事务提交)
System.out.println("【后置增强】方法执行后,日志记录完毕");
return result;
}
}
// 业务接口(动态代理必须基于接口,JDK动态代理的要求)
interface UserService {
void addUser();
}
// 业务实现类(原方法,不做任何修改)
class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("核心业务:添加用户"); // 原方法,没有任何增强逻辑
}
}
运行结果(关键看增强效果)
【前置增强】方法执行前,记录日志
核心业务:添加用户
【后置增强】方法执行后,日志记录完毕
可以看到:我们没有修改addUser()的任何代码,却成功给它加了前后置日志——这就是AOP的核心,而这一切的底层,全靠反射和动态代理。
重点拆解:Proxy.newProxyInstance()到底干了什么?(最易卡壳的地方)
很多人看不懂动态代理,本质是看不懂Proxy.newProxyInstance()——它并不是简单的“创建一个对象”,而是JDK通过反射,帮你自动生成了一个全新的类(代理类),并创建了这个类的对象。
你没写这个代理类,但JVM在运行时会临时生成一个,我们直接把这个“自动生成的代理类”写出来,一看就懂:
// 这个类是 JDK 自动生成的!你没写过,运行时临时创建!
// 类名一般是$Proxy0、$Proxy1(数字递增),实现了UserService接口
public class $Proxy0 implements UserService {
// 持有我们写的MyInvocationHandler对象
private InvocationHandler h;
// 构造方法:传入MyInvocationHandler对象
public $Proxy0(InvocationHandler h) {
this.h = h;
}
// 实现UserService接口的addUser()方法(和真实对象的方法名一致)
@Override
public void addUser() {
try {
// 核心:什么都不做,直接调用MyInvocationHandler的invoke()方法
Method method = UserService.class.getMethod("addUser");
h.invoke(this, method, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
动态代理完整执行流程(一步不差)
结合自动生成的代理类,整个流程就彻底通了:
- 我们创建真实对象:
UserService service = new UserServiceImpl();(原方法在这); - 调用
Proxy.newProxyInstance(),JDK自动生成代理类$Proxy0,并创建它的对象proxy; - 我们调用
proxy.addUser(),本质是调用$Proxy0.addUser(); $Proxy0.addUser()中,直接调用MyInvocationHandler.invoke();- 进入
invoke()方法,先执行前置增强,再通过method.invoke(target, args)反射调用真实对象的addUser(),最后执行后置增强; - 整个过程,真实对象的方法没有任何修改,增强逻辑全在代理类和拦截处理器中。
框架关联:Spring AOP就是这么干的
我们写的@Before、@After、@Transactional注解,本质是告诉Spring:“在哪个方法的前后,加什么增强逻辑”;Spring底层会通过动态代理,生成代理对象,把增强逻辑放进invoke()方法中,从而实现“不修改业务代码,增强功能”。
场景3:注解(Spring注解底层原理)
注解本身只是一个“标记符”,没有任何实际逻辑——比如我们写的@Controller、@Service、@GetMapping,如果没有反射,这些注解就只是一句“无用的注释”,而反射就是让注解“生效”的核心。
核心需求
给类/方法加一个自定义注解,程序运行时,通过反射识别这个注解,自动执行对应的逻辑(比如自动创建对象、绑定路由)——这就是Spring注解的底层原理。
可运行代码(自定义注解+反射解析)
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 反射场景3:注解解析(Spring注解底层)
* 需求:自定义注解@MyComponent,反射识别注解并自动创建对象
*/
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
// 1. 反射获取UserController的Class对象(反射入口)
Class<UserController> clazz = UserController.class;
// 2. 反射核心:判断这个类是否有@MyComponent注解
if (clazz.isAnnotationPresent(MyComponent.class)) {
System.out.println("检测到@MyComponent注解,自动创建对象");
// 3. 反射创建对象(和场景1的核心操作一致)
Object obj = clazz.newInstance();
System.out.println("自动创建的对象:" + obj);
}
}
}
// 自定义注解(模拟Spring的@Service、@Controller)
// @Retention(RetentionPolicy.RUNTIME):必须加,让注解在运行时可见(反射才能读取)
@Retention(RetentionPolicy.RUNTIME)
@interface MyComponent {}
// 给类加注解(标记这个类需要被自动创建对象)
@MyComponent
class UserController {}
运行结果
检测到@MyComponent注解,自动创建对象
自动创建的对象:UserController@6d06d69c
代码讲解(通俗好记)
- 注解本身:
@MyComponent只是一个标记,没有任何逻辑,就像给类贴了一个“标签”; - 反射的作用:通过
clazz.isAnnotationPresent(MyComponent.class),读取这个“标签”,判断类是否需要执行特定逻辑; - 框架关联:Spring启动时,会扫描项目中所有的类,通过反射判断类是否有
@Service、@Controller等注解,有就通过反射自动创建对象,放进IOC容器中——这就是Spring“注解驱动”的核心。
关键注意点
- 注解必须加
@Retention(RetentionPolicy.RUNTIME),否则注解在运行时不可见,反射无法读取; - 反射不仅能读取类上的注解,还能读取方法、字段上的注解(比如
@GetMapping、@Autowired),底层逻辑一致,都是通过getAnnotation()方法读取。
三、反射与Spring全家桶的关联(一句话总结)
反射是Spring全家桶的“底层基石”,没有反射,就没有Spring的各种便捷功能:
- Spring IOC:靠反射加载类、创建对象(场景1);
- Spring DI(依赖注入):靠反射给属性赋值(比如
@Autowired,反射获取字段,再赋值); - Spring AOP:靠反射+动态代理,实现方法增强(场景2);
- Spring注解驱动:靠反射解析注解,执行对应逻辑(场景3)。
一句话概括:反射让Spring实现了“不写死代码、自动装配、面向切面”,成为Java开发中最常用的框架。
四、核心总结(复习重点)
- 反射核心:运行时动态加载类、创建对象、调用方法、解析注解,解决“代码写死”的问题;
- 三大应用场景(必记):
- 通用框架:通过配置/注解动态创建对象(Spring IOC);
- 动态代理:不修改原代码,增强方法功能(Spring AOP);
- 注解解析:让注解生效,执行特定逻辑(Spring注解);
- 动态代理关键:
Proxy.newProxyInstance()会自动生成代理类,所有方法调用都会转发到invoke()方法,反射method.invoke()调用真实方法; - 核心API(必掌握):
Class.forName(类全路径):加载类;clazz.newInstance():创建对象;method.invoke(对象, 参数):调用方法;clazz.isAnnotationPresent(注解类):判断是否有指定注解。
五、补充说明
如果后续复习时忘记了动态代理的底层,只需记住:动态代理就是JDK帮你自动写了一个代理类,所有方法都转发给invoke(),反射调用真实方法——不修改原代码,却能加功能。
更多推荐



所有评论(0)