Java 反射与动态代理 详细笔记
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

Java 反射与动态代理 详细笔记

ASN__
2026-06-02 / 0 评论 / 7 阅读 / 正在检测是否收录...

Java 反射与动态代理 详细笔记

一、反射的核心思想

反射:在程序运行时,动态获取某个类的完整信息(类名、方法、字段、父类、接口等),并能动态创建对象、调用方法、访问字段。
  • 不需要在编译时知道具体类名,可以在运行时根据字符串类名加载类。
  • 常用于框架(Spring、MyBatis)、通用工具(IDE 自动补全)、动态代理等。

二、Class 类:一切反射的入口

2.1 每个类都有唯一的 Class 对象

  • JVM 在第一次加载一个类时,会在内存中为该类创建一个 Class 实例(俗称“说明书”)。
  • 这个 Class 实例保存了该类的全部信息(类名、包、方法、字段、父类、接口等)。
  • 无论通过哪种方式获取,同一个类的 Class 实例是同一个对象(可用 == 比较)。

2.2 获取 Class 实例的三种方法

// 方法1:通过类名.class(最直接,编译时已知类)
Class<String> cls1 = String.class;

// 方法2:通过实例的 getClass() 方法
String s = "hello";
Class<?> cls2 = s.getClass();   // 实际返回 String.class

// 方法3:通过 Class.forName(完整类名)(运行时根据字符串加载)
Class<?> cls3 = Class.forName("java.lang.String");
三者获得的是同一个 Class 对象:cls1 == cls2 == cls3

三、通过反射访问字段(Field)

3.1 获取字段的方法(返回值是 java.lang.reflect.Field

方法说明能否得到父类字段
getField(String name)获取 public 字段(包括父类)✅ 能
getDeclaredField(String name)获取当前类声明的任意字段(不限访问修饰符)❌ 不能,只看当前类
getFields()获取所有 public 字段(包括父类)✅ 能
getDeclaredFields()获取当前类声明的所有字段(不包括父类)❌ 不能

3.2 访问私有字段

  • 通过 getDeclaredField 拿到私有 Field 对象后,调用 setAccessible(true) 打破封装。
  • 然后可以 get(obj) / set(obj, value) 读写该私有字段。
class Person {
    private String name;
}
Class<Person> cls = Person.class;
Field field = cls.getDeclaredField("name");
field.setAccessible(true);   // 允许访问私有字段
Person p = new Person();
field.set(p, "Alice");
String name = (String) field.get(p);   // "Alice"

四、通过反射调用方法(Method)

4.1 获取方法的方法(返回值是 java.lang.reflect.Method

方法说明包括父类
getMethod(String name, Class<?>... parameterTypes)获取某个 public 方法✅ 包括
getDeclaredMethod(...)获取当前类声明的某个方法(不限修饰符)❌ 不包括
getMethods()获取所有 public 方法✅ 包括
getDeclaredMethods()获取当前类声明的所有方法❌ 不包括

4.2 调用方法

  • 拿到 Method 对象后,调用 invoke(Object obj, Object... args)
  • 如果方法是 static,第一个参数传 null
// 示例:调用 String 的 length() 方法
String str = "hello";
Method m = String.class.getMethod("length");
int len = (int) m.invoke(str);   // len = 5

五、通过反射调用构造器(Constructor)

5.1 获取构造器

方法说明
getConstructor(Class<?>... parameterTypes)获取某个 public 构造器
getDeclaredConstructor(...)获取任意修饰符的构造器(包括 private
getConstructors() / getDeclaredConstructors()获取所有构造器

5.2 创建实例

  • 通过 Constructor.newInstance(Object... initargs) 创建对象。
  • Class.newInstance() 更灵活(可以传参数,可以调用非 public 构造器)。
// 调用带参数的构造器
Constructor<String> cons = String.class.getConstructor(String.class);
String s = cons.newInstance("abc");   // 相当于 new String("abc")

// 调用私有构造器(单例模式等)
Constructor<MyClass> cons2 = MyClass.class.getDeclaredConstructor();
cons2.setAccessible(true);
MyClass obj = cons2.newInstance();
注意Class.newInstance() 已过时(只能调用无参 public 构造器),推荐使用 Constructor.newInstance()

六、获取继承关系

方法作用示例
Class.getSuperclass()返回父类的 Class 对象Integer.class.getSuperclass()Number.class
Class.getInterfaces()返回当前类直接实现的接口数组Integer.class.getInterfaces()Comparable, Constable, ConstantDesc
Class.isAssignableFrom(Class<?> cls)判断当前类是否可接收 cls 类型的对象(即 cls 能否向上转型为当前类)Number.class.isAssignableFrom(Integer.class)true

6.1 关于接口的特殊说明

  • 对接口调用 getSuperclass() → 返回 null
  • 获取接口的父接口必须用 getInterfaces()
  • 接口的 isAssignableFrom 用法与类一致:Serializable.class.isAssignableFrom(Integer.class)true

6.2 instanceofisAssignableFrom 的区别

  • instanceof 需要对象实例obj instanceof Number
  • isAssignableFrom 需要Class 对象Number.class.isAssignableFrom(Integer.class)

七、动态代理(Dynamic Proxy)

7.1 解决的问题

不手动编写接口的实现类,就能在运行时动态创建实现了该接口的对象。

典型应用:AOP(面向切面编程)、RPC 框架、日志/事务拦截等。

7.2 限制条件

  • 只能代理接口(不能代理普通类)。
  • 如果需要代理普通类,可使用 CGLIB 或 ByteBuddy。

7.3 核心 API

  • java.lang.reflect.Proxy
  • java.lang.reflect.InvocationHandler(函数式接口)

7.4 实现步骤

  1. 定义接口(必须,告知代理对象有哪些方法)。
  2. 实现 InvocationHandler:重写 invoke 方法,定义当接口方法被调用时实际执行的代码。
  3. 调用 Proxy.newProxyInstance 创建代理实例。
  4. 将代理实例转型为接口类型,然后正常调用方法。

7.5 示例代码(完整)

// 1. 接口
public interface Hello {
    void morning(String name);
}

// 2. 使用动态代理(不写 HelloWorld 类)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 2.1 定义调用处理器
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("morning")) {
                    String name = (String) args[0];
                    System.out.println("Good morning, " + name);
                }
                return null;  // 方法返回 void,所以返回 null
            }
        };

        // 2.2 创建代理对象
        Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(),   // 类加载器
                new Class[]{Hello.class},       // 要实现的接口列表
                handler                         // 调用处理器
        );

        // 2.3 调用方法(实际执行 handler.invoke)
        hello.morning("Bob");   // 输出:Good morning, Bob
    }
}

7.6 动态代理内部原理

  • Proxy.newProxyInstance 会在运行时动态生成一个新的类(例如 $Proxy0)。
  • 这个类实现了指定的接口(Hello),并且每个方法的实现都是:

    public void morning(String name) {
        super.h.invoke(this, method_morning, new Object[]{name});
    }
  • super.h 就是你传入的 InvocationHandler
  • 然后通过 ClassLoader 加载这个类,并实例化返回。
换句话说:动态代理 = JVM 自动帮你写了一个实现类,并用反射调用你的 handler

7.7 动态代理与反射的关系

  • 动态代理生成的新类($Proxy0)本身也是一个类,JVM 会为它创建 Class 对象。
  • 你可以对这个 Class 对象进行任何反射操作。
  • InvocationHandler.invoke 方法中收到的 Method 参数就是通过反射得到的。

所以动态代理是反射机制的一个典型应用,而不是替代品。


八、总结:什么时候用什么技术?

场景推荐技术
已知类名,需要调用它的方法/访问字段直接调用,无需反射
类名字符串在运行时才确定,需要创建实例Class.forName() + Constructor.newInstance()
需要遍历一个对象的所有字段/方法(例如写序列化工具)getDeclaredFields() / getDeclaredMethods()
不写实现类,仅通过接口定义行为动态代理
需要在方法调用前后统一添加逻辑(日志、事务)动态代理 + InvocationHandler

最后记住一句话:

反射让你在运行时“看见”并“操作”一个类;动态代理让你在运行时“凭空造出”一个实现了接口的类,并把所有方法调用委托给你写的处理器。
0

评论

博主关闭了所有页面的评论