Java反射
# Java反射
# 1. 什么是代理?
代理模式(Proxy Pattern)是一种结构型设计模式,代理对象充当真实对象的替身,控制对其的访问或添加额外的行为。在 Java 中,代理常用于日志记录、性能监控、安全检查等场景。代理可以分为静态代理和动态代理两种,主要区别在于创建方式和灵活性。
# 2. 代理的工作机制
代理的工作流程如下:
- 调用者调用代理对象的方法。
- 代理对象在方法执行前或后添加额外的逻辑(如日志、权限检查)。
- 代理将请求转发给目标对象,目标对象执行实际业务逻辑。
- 返回结果可能经过代理的再次处理后返回给调用者。
这种机制的核心是接口一致性:代理对象必须实现与目标对象相同的接口,确保调用者无感知地使用代理。
# 3. 静态代理
静态代理是你手动编写的一个类,实现与真实对象相同的接口,然后在方法中调用真实对象的方法,并可能添加额外逻辑。例如:
- 你有一个
User
接口,RealUser
实现它,UserProxy
是静态代理,调用RealUser
的方法前打印日志。 - 优点:运行快,因为是编译时生成的类。
- 缺点:不够灵活,每个接口都需要写一个代理类。
静态代理是指手动编写一个代理类,实现与真实对象相同的接口,并在方法中调用真实对象的方法,同时可能添加额外的逻辑。
实现示例
考虑以下代码示例,展示一个简单的静态代理:
public interface User {
void doSomething();
}
public class RealUser implements User {
@Override
public void doSomething() {
System.out.println("Doing something");
}
}
public class UserProxy implements User {
private User realUser;
public UserProxy(User realUser) {
this.realUser = realUser;
}
@Override
public void doSomething() {
System.out.println("Before doing something");
realUser.doSomething();
System.out.println("After doing something");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在这个例子中,UserProxy
是 RealUser
的静态代理,实现了 User
接口,在调用 doSomething
方法前和后添加了打印日志。
# 4. JDK 动态代理
动态代理是在运行时通过 Java 的反射 API 创建的代理,不需要手动编写代理类。它利用 java.lang.reflect.Proxy
类生成实现指定接口的代理实例,所有方法调用都会路由到一个调用处理器(Invocation Handler)。
# 1. 实现示例
以下是一个动态代理的示例,展示如何为 User
接口创建代理并添加日志功能:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
public class LoggingProxyHandler implements InvocationHandler {
private Object realObject;
public LoggingProxyHandler(Object realObject) {
this.realObject = realObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Calling method: " + method.getName());
try {
return method.invoke(realObject, args);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Error invoking method", e);
}
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
User realUser = new RealUser();
User proxy = (User) Proxy.newProxyInstance(
realUser.getClass().getClassLoader(),
new Class[] { User.class },
new LoggingProxyHandler(realUser)
);
proxy.doSomething();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
在这个例子中,Proxy.newProxyInstance
创建了一个动态代理实例,实现了 User
接口。所有对代理的方法调用都会通过 LoggingProxyHandler
的 invoke
方法处理,打印方法名后调用真实对象的对应方法。
# 2. 动态代理的实现原理
动态代理的核心在于反射:
- 代理类的创建:
Proxy.newProxyInstance
使用反射生成一个实现指定接口的类,类名通常以$Proxy
开头,加载到 JVM 中。 - 方法调用的处理:调用处理器(
InvocationHandler
)的invoke
方法接收所有方法调用,通过Method.invoke
使用反射调用真实对象的方法。
# 3. 优点与缺点
- 优点:灵活性高,可以在运行时为任意接口创建代理,无需为每个接口编写类,减少代码重复,适合框架开发。
- 缺点:由于使用反射,性能稍慢,存在一定的运行时开销,且只能代理接口,不能直接代理具体类。如果需要代理具体类,可以使用其他库如 CGLIB,通过字节码操作实现,但这超出了标准 Java 反射的范围。
# 4. 使用场景
动态代理常用于需要通用拦截逻辑的场景,例如:
- 框架开发,如 Spring AOP 中的事务管理。
- 测试场景,创建模拟对象(Mock)来代替真实对象。
- 添加横切关注点,如日志记录、性能监控。
# 5. CGLIB 动态代理
# 1. 什么是 CGLIB?
CGLIB(Code Generation Library)是一种基于字节码增强的动态代理技术,它可以在运行时 动态生成一个类的子类,并 重写父类方法 以实现 AOP(面向切面编程)。
CGLIB 代理是 Spring AOP 的核心技术之一,主要用于 代理没有实现接口的类,弥补了 JDK 动态代理的局限性。
# 2. CGLIB 代理的原理
CGLIB 通过 继承目标类 并 重写目标方法 来增强功能,因此:
- 它不能代理 final 类,因为 final 类不能被继承。
- 它不能代理 final 方法,因为 final 方法不能被重写。
CGLIB 的核心原理是使用 ASM(Java 字节码操作框架) 来修改目标类的字节码,并生成一个新的代理子类。
# 3. CGLIB 代理的实现步骤
# 3.1 引入 CGLIB 依赖
如果你使用的是 Maven,可以添加以下依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2
3
4
5
# 3.2 CGLIB 代理示例
我们以一个 UserService
类为例,实现 CGLIB 代理。
# (1) 目标类(被代理对象)
public class UserService {
public void addUser(String name) {
System.out.println("Add user: " + name);
}
}
2
3
4
5
# (2) CGLIB 代理工厂
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBProxy implements MethodInterceptor {
private Object target;
public CGLIBProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 生成目标类的子类
enhancer.setCallback(this); // 设置拦截器
return enhancer.create(); // 创建代理对象
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method...");
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("After method...");
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# (3) 测试 CGLIB 代理
public class CGLIBTest {
public static void main(String[] args) {
UserService userService = new UserService();
CGLIBProxy cgProxy = new CGLIBProxy(userService);
UserService proxyObject = (UserService) cgProxy.getProxyInstance();
proxyObject.addUser("Alice");
}
}
2
3
4
5
6
7
8
# (4) 运行结果
Before method...
Add user: Alice
After method...
2
3
CGLIB 成功在 addUser
方法前后插入了额外的逻辑,实现了 AOP 代理。
# 4. CGLIB 代理与 JDK 代理的对比
特性 | JDK 动态代理 | CGLIB 代理 |
---|---|---|
代理方式 | 生成接口代理类 | 生成子类代理 |
是否需要接口 | 需要 | 不需要 |
是否能代理 final 方法 | 可以 | 不可以 |
是否能代理 final 类 | 可以 | 不可以 |
性能 | 代理接口时性能更好 | 代理类时性能更好 |
实现方式 | 反射 (Proxy + InvocationHandler ) | 字节码增强 (Enhancer + MethodInterceptor ) |
- JDK 代理的优点:基于接口,使用
Proxy.newProxyInstance
实现,性能在代理接口时优于 CGLIB。 - CGLIB 代理的优点:不需要目标类实现接口,适用于代理普通类,Spring AOP 默认使用 CGLIB 来代理非接口的 Bean。
# 5. CGLIB 代理的优缺点
优点
✅ 不需要接口:相比 JDK 动态代理,CGLIB 可以直接代理类,不要求实现接口。 ✅ 性能较高:CGLIB 直接操作字节码,在代理类时性能优于 JDK 代理。 ✅ Spring AOP 的重要支撑:Spring 默认使用 CGLIB 来代理没有实现接口的 Bean。
缺点
❌ 不能代理 final 方法:因为 CGLIB 通过继承实现代理,final
方法无法被重写。 ❌ 不能代理 final 类:如果目标类被 final
修饰,CGLIB 无法生成子类。 ❌ 比 JDK 代理占用更多内存:因为 CGLIB 需要动态生成子类,创建代理对象时会占用更多内存。
# 6. 反射的定义与用途
在Java编程中,反射(Reflection)是一种强大的机制,可以通过编程的方式访问类的信息例如成员变量、成员方向和构造方法的信息,并进行一些操作。这包括创建对象、调用方法,甚至访问私有成员。反射广泛应用于框架开发(如Spring AOP)、动态代理等场景。它通过java.lang.reflect
包提供的一系列类(如Class
、Method
、Field
、Constructor
)实现。反射的核心在于Class
对象,它代表运行时的类信息。
# 7. 获取Class对象的三种方式
获取Class
对象是反射的起点,有三种常见方法:
方法 | 描述 | 使用场景 |
---|---|---|
Class.forName("全类名") | 通过类名字符串加载类,抛出ClassNotFoundException | 运行时从配置文件或用户输入获取类 |
类名.class | 直接使用类的.class 属性 | 编译时已知类,代码简洁 |
对象.getClass() | 从实例对象获取其类信息 | 有对象实例时,查看其类型 |
# 8. 使用反射操作构造方法
通过Class
对象的getConstructors()
或getDeclaredConstructors()
方法,可以获取类的构造方法。getConstructors()
返回所有公共构造方法,getDeclaredConstructors()
返回所有构造方法(包括私有)。
以下是使用反射创建对象的步骤:
- 获取
Class
对象。 - 获取
Constructor
对象,例如getDeclaredConstructor()
。 - 如果构造方法是私有的,调用
setAccessible(true)
以绕过访问检查。 - 使用
newInstance()
创建对象。
代码示例:
Class<?> cls = Class.forName("com.example.MyClass");
Constructor<?> constructor = cls.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 允许访问私有构造方法
Object instance = constructor.newInstance("example");
2
3
4
这允许创建私有构造方法的对象,打破了封装,适合测试场景。
# 9. 使用反射操作成员变量
成员变量(字段)通过getFields()
或getDeclaredFields()
获取。getFields()
返回所有公共字段(包括继承的),getDeclaredFields()
返回类中声明的所有字段(包括私有)。
要获取字段的值,必须提供实例对象,并使用Field.get()
方法。如果字段是私有的,需先调用setAccessible(true)
。
示例:
Class<?> cls = Class.forName("com.example.MyClass");
Field field = cls.getDeclaredField("myField");
field.setAccessible(true);
Object instance = cls.getDeclaredConstructor().newInstance();
Object value = field.get(instance);
2
3
4
5
这展示了反射如何动态访问字段值,需注意性能开销和安全问题。
# 10. 使用反射操作成员方法
成员方法通过getMethods()
或getDeclaredMethods()
获取。getMethods()
返回所有公共方法(包括继承的),getDeclaredMethods()
返回类中声明的所有方法(包括私有)。
具体方法如下:
getMethods()
:返回Method[]
,包含所有公共方法。getDeclaredMethods()
:返回Method[]
,包含所有声明方法,不包括继承的。getMethod(String name, Class<?>... parameterTypes)
:返回单个公共方法。getDeclaredMethod(String name, Class<?>... parameterTypes)
:返回单个声明方法。
使用Method.invoke(Object obj, Object... args)
运行方法,参数包括调用对象和方法参数。
示例:
Class<?> cls = Class.forName("com.example.MyClass");
Method method = cls.getDeclaredMethod("myMethod", int.class);
method.setAccessible(true); // 如果是私有方法
Object instance = cls.getDeclaredConstructor().newInstance();
Object result = method.invoke(instance, 42);
2
3
4
5
这允许动态调用方法,适合框架如Spring的AOP实现。
# 11. 总结
反射提供了强大的动态能力,但也带来性能开销(由于使用反射调用比直接调用慢)和安全风险(可访问私有成员)。因此,在高性能场景或安全敏感场景下需谨慎使用。