一文吃透Java反射:三大应用场景+动态代理底层原理(附可运行代码)

“要想知道什么是反射,就需要先来了解什么是‘正射’。“一般情况下,我们在使用某个类之前已经确定它到底是个什么类了,拿到手就直接可以使用 new 关键字来调用构造方法进行初始化,之后使用这个类的对象来进行操作。”

Writer writer = new Writer();
writer.setName("zhang san");

像上面这个例子,就可以理解为“正射”。而反射就意味着一开始我们不知道要初始化的类到底是什么,也就没法直接使用 new 关键字创建对象了。

我们只知道这个类的一些基本信息,就好像我们看电影的时候,为了抓住一个犯罪嫌疑人,警察就会问一些目击证人,根据这些证人提供的信息,找专家把犯罪嫌疑人的样貌给画出来——这个过程,就可以称之为反射。

在Java开发中,反射是一个“看似抽象、实则无处不在”的核心技术——Spring全家桶、MyBatis、动态代理等框架的底层,全靠反射撑起来。很多人入门时觉得反射晦涩,尤其是动态代理的底层逻辑,其实只要抓住“运行时动态操作类”这个核心,再结合可运行代码拆解,就能轻松掌握。

一、先搞懂:反射到底是什么?(一句话吃透)

反射的核心作用,一句话总结:
反射 = 程序运行时,才决定创建哪个类、调用哪个方法、操作哪个属性/注解,不用提前写死 new 类名()

举个通俗的例子:平时我们写代码,要创建一个UserService对象,得提前写死 UserService service = new UserService();,一旦要换成OrderService,就必须修改代码、重新编译;而反射可以做到“不修改代码,只改配置”,就能动态创建不同的对象、调用不同的方法——这也是框架实现“通用性”的核心秘诀。

反射的核心API很简单,记住3个关键操作即可(后续代码会反复用到):

  1. Class.forName(类全路径):加载类,获取Class对象(反射的入口);
  2. clazz.newInstance() / clazz.getConstructor().newInstance():通过Class对象创建类的实例;
  3. method.invoke(对象, 参数):通过Method对象,反射调用方法。

二、反射的三大核心应用场景(附可运行代码)

反射的应用场景集中在“需要动态性”的场景中,最常用的就是以下3种,每一种都对应框架的核心底层逻辑,代码极简、可直接复制运行。

场景1:开发通用框架(Spring IOC底层原理)

核心需求

开发框架时,无法提前知道用户会使用哪些类、创建哪些对象,因此需要“通过配置文件/注解”动态加载类、创建对象,保持框架的通用性——这就是Spring IOC容器的核心原理。

可运行代码(含配置文件)
  1. 核心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 {}
  1. 配置文件(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);
        }
    }
}
动态代理完整执行流程(一步不差)

结合自动生成的代理类,整个流程就彻底通了:

  1. 我们创建真实对象:UserService service = new UserServiceImpl();(原方法在这);
  2. 调用Proxy.newProxyInstance(),JDK自动生成代理类$Proxy0,并创建它的对象proxy
  3. 我们调用proxy.addUser(),本质是调用$Proxy0.addUser()
  4. $Proxy0.addUser()中,直接调用MyInvocationHandler.invoke()
  5. 进入invoke()方法,先执行前置增强,再通过method.invoke(target, args)反射调用真实对象的addUser(),最后执行后置增强;
  6. 整个过程,真实对象的方法没有任何修改,增强逻辑全在代理类和拦截处理器中。
框架关联: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
代码讲解(通俗好记)
  1. 注解本身:@MyComponent只是一个标记,没有任何逻辑,就像给类贴了一个“标签”;
  2. 反射的作用:通过clazz.isAnnotationPresent(MyComponent.class),读取这个“标签”,判断类是否需要执行特定逻辑;
  3. 框架关联: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开发中最常用的框架

四、核心总结(复习重点)

  1. 反射核心:运行时动态加载类、创建对象、调用方法、解析注解,解决“代码写死”的问题;
  2. 三大应用场景(必记):
    • 通用框架:通过配置/注解动态创建对象(Spring IOC);
    • 动态代理:不修改原代码,增强方法功能(Spring AOP);
    • 注解解析:让注解生效,执行特定逻辑(Spring注解);
  3. 动态代理关键:Proxy.newProxyInstance()会自动生成代理类,所有方法调用都会转发到invoke()方法,反射method.invoke()调用真实方法;
  4. 核心API(必掌握):
    • Class.forName(类全路径):加载类;
    • clazz.newInstance():创建对象;
    • method.invoke(对象, 参数):调用方法;
    • clazz.isAnnotationPresent(注解类):判断是否有指定注解。

五、补充说明

如果后续复习时忘记了动态代理的底层,只需记住:动态代理就是JDK帮你自动写了一个代理类,所有方法都转发给invoke(),反射调用真实方法——不修改原代码,却能加功能

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐