反射与代理
反射
定义
java 语言提供的一种能力,允许程序在运行时(而非编译时)获取类的信息(如类的名称、方法、属性等),
并能动态创建类的实例、调用类的方法、访问或修改类的属性等。
这种机制就像 “反射” 一样,能反向探知类的内部结构,因此得名 “反射机制”。
用途
- 框架底层实现
- 动态代理与AOP
- 工具类
- 序列化与反序列化
…
一、获取Class对象四种方法
我们要实现反射,需要通过获取class来获得类的属性与方法信息。
1.通过类的class属性获得
1 | |
2.通过Class.forName()方法获得
1 | |
3.通过实例化对象的getClass()方法
1 | |
4.通过类加载器获得
1 | |
以上就是获得class对象的四个方法,这个class对象有很多方法,分别获得类的属性/方法信息。接下来我们就来介绍这些常用的方法。
二、Class 类的常用方法(获取类信息 / 结构)
Class 类提供了大量方法用于获取类的元信息(名称、父类、接口、构造器、方法、字段等)。
1. 获取类基本信息
String getName():返回类的全限定名(如java.lang.String)。String getSimpleName():返回类的简单名称(如String)。Class<?> getSuperclass():返回父类的Class对象(如Object.class是所有类的父类)。Class<?>[] getInterfaces():返回类实现的所有接口的Class数组。int getModifiers():返回类的修饰符(如public、abstract、final等,需配合Modifier工具类解析)。
2. 获取构造器(返回 Constructor 对象)
Constructor<?>[] getConstructors():获取类中所有 public 构造器(不包括私有构造器)。Constructor<?> getConstructor(Class<?>... parameterTypes):获取指定参数类型的 public 构造器(参数为构造器参数的Class数组)。Constructor<?>[] getDeclaredConstructors():获取类中 所有构造器(包括 private、protected 等,不限制访问权限)。Constructor<?> getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数类型的 任意访问权限构造器。
3. 获取方法(返回 Method 对象)
Method[] getMethods():获取类中所有 public 方法(包括从父类继承的 public 方法)。Method getMethod(String name, Class<?>... parameterTypes):获取指定名称和参数类型的 public 方法(name 是方法名,parameterTypes 是参数的Class数组)。Method[] getDeclaredMethods():获取类中 所有方法(包括 private、protected 等,仅当前类声明的,不包括父类)。Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取指定名称和参数类型的 任意访问权限方法(仅当前类声明)。
4. 获取字段(返回 Field 对象)
Field[] getFields():获取类中所有 public 字段(包括从父类继承的 public 字段)。Field getField(String name):获取指定名称的 public 字段(name 是字段名)。Field[] getDeclaredFields():获取类中 所有字段(包括 private、protected 等,仅当前类声明的,不包括父类)。Field getDeclaredField(String name):获取指定名称的 任意访问权限字段(仅当前类声明)。
5. 其他常用方法
boolean isInterface():判断当前类是否是接口。boolean isArray():判断当前类是否是数组。Object newInstance():通过类的 无参 public 构造器 创建实例(JDK 9 后 deprecated,推荐用Constructor.newInstance())。
代理
代理模式是一种结构型设计模式,核心思想是通过代理对象间接访问目标对象,从而在不修改目标对象代码的前提下,对目标对象的方法进行增强(如日志记录、权限校验、事务管理等)。根据代理对象的创建时机,可分为静态代理和动态代理,其中动态代理又以JDK 动态代理和CGLIB 动态代理最为常用。
一、静态代理
静态代理是指代理类在编译期就已确定(与目标类同时存在),代理类和目标类通常实现同一个接口,代理类持有目标对象的引用,在调用目标方法时添加增强逻辑。
实现步骤
- 定义公共接口(规范目标类和代理类的方法);
- 实现目标类(真正的业务逻辑);
- 实现代理类(实现接口,持有目标对象,在方法中增强逻辑)。
示例代码
1 | |
优缺点
- 优点:简单直观,无需依赖框架,增强逻辑清晰。
- 缺点:
- 代理类与目标类强耦合(必须实现同一接口);
- 若接口方法增多,代理类需同步修改,维护成本高(代码冗余);
- 只能代理特定接口的类,灵活性差。
二、动态代理
动态代理是指代理类在运行时动态生成(编译期不存在),无需手动编写代理类代码,可灵活代理任意类(或接口),大幅减少代码冗余。常见实现方式有两种:JDK 动态代理和CGLIB 动态代理。
1. JDK 动态代理(基于接口)
JDK 动态代理是 Java 原生支持的代理方式,必须基于接口实现,核心依赖 java.lang.reflect.Proxy 类和 InvocationHandler 接口。
实现原理
- 核心逻辑:通过 Proxy.newProxyInstance() 方法在运行时动态生成代理类的字节码,该代理类实现了目标对象的所有接口,并持有一个 InvocationHandler 实例。
- 调用流程:当调用代理对象的方法时,会自动转发到 InvocationHandler 的 invoke() 方法,在 invoke() 中可自定义增强逻辑,并通过反射调用目标对象的方法。
关键类 / 接口
- Proxy:生成代理类的工具类,核心方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),返回动态生成的代理对象。
- loader:类加载器(通常用目标类的类加载器);
- interfaces:目标对象实现的接口数组;
- h:InvocationHandler 实例(增强逻辑的载体)。
- InvocationHandler:接口中仅一个方法 invoke(Object proxy, Method method, Object[] args),用于定义增强逻辑:
- proxy:代理对象本身(慎用,避免循环调用);
- method:目标方法的 Method 对象(通过反射获取);
- args:目标方法的参数数组;
- 返回值:目标方法的返回值。
示例代码
1 | |
特点
- 必须基于接口(代理类实现接口,Java 不支持多继承,无法继承目标类);
- 无需依赖第三方库(JDK 自带);
- 代理类在运行时动态生成(类名通常为 $Proxy0、$Proxy1 等);
- 性能:JDK 8 及以上优化后性能较好,接近 CGLIB。
2. CGLIB 动态代理(基于继承)
CGLIB(Code Generation Library)是一个第三方字节码生成库,基于继承实现动态代理(无需接口),通过动态生成目标类的子类作为代理类,并重写目标方法添加增强逻辑。
实现原理
- 核心逻辑:借助 ASM 字节码框架,在运行时动态生成目标类的子类(代理类),子类重写目标类的非 final 方法,在重写方法中添加增强逻辑,并通过 MethodProxy 调用父类(目标类)的方法。
- 关键前提:目标类不能是 final(否则无法继承),目标方法不能是 final(否则无法重写)。
关键类 / 接口
- Enhancer:CGLIB 的核心类,用于生成代理类,通过 setSuperclass() 指定父类(目标类),setCallback() 设置增强逻辑(通常用 MethodInterceptor)。
- MethodInterceptor:接口中仅一个方法 intercept(Object obj, Method method, Object[] args, MethodProxy proxy),用于定义增强逻辑:
- obj:代理对象(子类实例);
- method:目标方法的 Method 对象;
- args:目标方法的参数数组;
- proxy:MethodProxy 对象(用于高效调用父类方法);
- 返回值:目标方法的返回值。
示例代码(需引入 CGLIB 依赖)
Maven 依赖:
1 | |
代码:
1 | |
特点
- 无需接口,可代理任意非 final 类;
- 基于字节码生成技术(直接操作 class 文件),性能在早期版本优于 JDK 动态代理(JDK 8 后差距缩小);
- 依赖第三方库(CGLIB);
- 代理类是目标类的子类(类名通常为 目标类名$$EnhancerByCGLIB$$随机字符串)。
三、JDK 动态代理 vs CGLIB 动态代理
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 底层原理 | 基于接口(实现目标接口) | 基于继承(生成目标类的子类) |
| 依赖 | JDK 原生(无第三方依赖) | 需引入 CGLIB 库 |
| 代理条件 | 目标类必须实现接口 | 目标类不能是 final,方法不能是 final |
| 性能(JDK 8+) | 较好(反射优化后) | 接近 JDK 动态代理 |
| 灵活性 | 仅能代理接口实现类 | 可代理任意非 final 类 |
四、动态代理的典型应用
- Spring AOP:默认对实现接口的类使用 JDK 动态代理,对无接口的类使用 CGLIB(可通过配置强制使用 CGLIB);
- MyBatis:Mapper 接口的实现类通过 JDK 动态代理生成,无需手动编写实现类;
- RPC 框架:如 Dubbo,通过动态代理生成服务接口的代理类,实现远程调用的封装;
- 权限框架:如 Shiro,通过动态代理对方法调用进行权限校验。
总结:静态代理适合简单场景,但灵活性差;动态代理通过运行时生成代理类,大幅提升灵活性,是框架设计的核心技术之一,需根据是否有接口、是否依赖第三方库等选择 JDK 或 CGLIB 实现。