静态代理
"在编译期就写死代理类,实现/继承同一个接口/父类,把真实对象包一层,手动加逻辑。"
- 场景
-
对真实对象做 日志、权限、缓存 等横切增强
-
真实对象不许改动(第三方、旧代码)
-
代理类数量固定 → 适合 接口少、实现类少 的场景
代码块
// 1) 公共接口
public interface UserService {
void save(String name);
}
// 2) 真实对象
public class UserServiceImpl implements UserService {
public void save(String name) {
System.out.println("保存用户:" + name);
}
}
// 3) 代理类(编译期手写)
public class UserServiceProxy implements UserService {
private final UserService real; // 组合真实对象
public UserServiceProxy(UserService real) {
this.real = real;
}
public void save(String name) {
System.out.println("前置日志:准备保存");
real.save(name); // 调用真实对象
System.out.println("后置日志:保存完成");
}
}
// 4) 使用
UserService service = new UserServiceProxy(new UserServiceImpl());
service.save("Alice");
优缺点
|-------------|---------------------------|
| 优点 | 缺点 |
| 简单直观,无运行时开销 | 每多一个接口/实现,就要手写一个代理类 → 类爆炸 |
| 编译期即可检查类型 | 无法代理 未实现接口的方法 |
动态代理
Dynamic Proxy
|-----------------|---------------------------------------------|--------------|--------------------------|
| 方案 | 技术 | 代理目标 | 关键类 |
| JDK 动态代理 | java.lang.reflect.Proxy + InvocationHandler | 只能代理接口 | Proxy.newProxyInstance() |
| CGLIB/ByteBuddy | 字节码生成库 | 接口 + 普通类 | Enhancer.create() |
JDK 动态代理
// 订单服务接口
public interface OrderService {
void create(); // 创建订单的业务方法
}
// 订单服务实现类
public class OrderServiceImpl implements OrderService {
public void create() {
System.out.println("创建订单"); // 真正的业务逻辑
}
}
// 日志处理器:实现 InvocationHandler 接口,用来在真实方法前后附加日志
public class LogHandler implements InvocationHandler {
private final Object target; // 被代理的"真实对象"
public LogHandler(Object target) {
this.target = target; // 构造时把真实对象传进来
}
// 每当代理对象上的任何方法被调用时,都会走到这里
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置日志"); // 1. 前置增强(如记录开始时间、打印参数等)
Object result = method.invoke(target, args); // 2. 通过反射调用真实对象的方法
System.out.println("后置日志"); // 3. 后置增强(如记录结束时间、打印返回值等)
return result; // 4. 把真实方法的返回值原样返回
}
}
// ---------------- 使用示例 ----------------
public class Main {
public static void main(String[] args) {
// 1. 用 JDK 的 Proxy 工具生成一个"代理对象"
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderService.class.getClassLoader(), // 类加载器:告诉 JVM 把代理类加载到哪个命名空间
new Class<?>[]{OrderService.class}, // 需要实现的接口列表(可多个)
new LogHandler(new OrderServiceImpl()) // 调用处理器:真正干活儿的 LogHandler
);
// 2. 通过代理对象调用方法
proxy.create(); // 控制台会依次打印:
// 前置日志
// 创建订单
// 后置日志
}
}
public Object invoke(Object proxy, Method method, Object[] args)参数含义
-
Object proxy
这是 代理对象本身 (就是
Proxy.newProxyInstance()
返回的那个对象)。-
如果你在这里面再调用
proxy.toString()
、proxy.create()
之类的方法,会再次进入invoke
,极易造成无限递归,所以通常不会直接用它。 -
主要用途:在需要判断
proxy == 某个代理实例
或打印调试信息时才会用到。
-
-
Method method
本次被调用的方法对象。
-
通过
method.getName()
可以知道调的是哪个方法(如create
)。 -
通过
method.getParameterTypes()
、method.getReturnType()
等可以拿到签名信息,用来做通用逻辑(例如所有get*
方法做缓存、所有save*
方法做事务等)。
-
-
Object[] args
本次方法调用时传进来的实参数组,按声明顺序排列。
-
如果方法无参,它就是
null
或空数组。 -
可以通过
args[i]
读取或修改参数值,实现诸如统一参数校验、脱敏、记录日志等横切逻辑。
-
CGLIB 最简示例(可代理类)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("前置");
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置");
return result;
});
UserService proxy = (UserService) enhancer.create();
proxy.save("Bob");
动态代理 vs 静态代理
|--------|------------------------------|------------|
| 维度 | 动态代理 | 静态代理 |
| 代码量 | 1 个 InvocationHandler 即可 | 每接口/实现都要手写 |
| 代理范围 | 任意接口/类(CGLIB) | 只能固定接口 |
| 性能 | 反射调用略慢(JDK)/ ASM 接近原生(CGLIB) | 直接调用,零反射 |
| 生成时机 | 运行时 | 编译期 |