代理模式:静态代理、JDK动态代理、CGLIB代理、实战:AOP实现
代理模式,说白了就是给某个对象找个“替身”。你想想看,在真实项目中,我们经常需要在调用某个方法前后做些额外的事情——比如记录日志、检查权限、开启事务。如果把这些代码直接写在业务逻辑里,那代码就乱成一锅粥了。代理模式就是来解决这个问题的。
我个人习惯把代理模式分成两类:静态代理和动态代理。动态代理里又分JDK代理和CGLIB代理。嗯,咱们一个一个来看。
静态代理:最朴素的实现
静态代理,就是手动写一个代理类,让它实现和目标类相同的接口。我在项目中遇到过不少新手直接这么写,其实对于简单场景完全够用。
// 接口
public interface UserService {
void addUser(String name);
}
// 目标类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String name) {
System.out.println("[日志] 开始添加用户...");
target.addUser(name);
System.out.println("[日志] 添加用户完成");
}
}
使用起来也很直观:
UserService target = new UserServiceImpl();
UserService proxy = new UserServiceProxy(target);
proxy.addUser("张三");
核心要点:静态代理在编译期就确定了代理关系。每个目标类都需要一个对应的代理类,如果接口方法变了,代理类也得跟着改。
我曾经踩过的坑:在一个老项目中,有十几个Service都需要加日志代理。我手动写了十几个代理类,结果后来接口一改,改得我头皮发麻。所以静态代理只适合代理类很少的场景。
JDK动态代理:运行时生成代理
JDK动态代理就不需要手动写代理类了。它利用Java的反射机制,在运行时动态生成代理对象。说白了,你只需要提供一个InvocationHandler,告诉它“调用任何方法时,先做什么,再做什么”。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[日志] 调用方法:" + method.getName());
Object result = method.invoke(target, args);
System.out.println("[日志] 方法调用完成");
return result;
}
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
}
使用:
UserService target = new UserServiceImpl();
LogHandler handler = new LogHandler(target);
UserService proxy = (UserService) handler.getProxy();
proxy.addUser("李四");
注意:JDK动态代理要求目标类必须实现接口。如果目标类没有实现任何接口,那JDK代理就无能为力了。为什么会这样?因为JDK生成的代理类默认继承Proxy,Java是单继承,所以只能通过接口来扩展。
CGLIB代理:没有接口也能代理
当目标类没有实现接口时,CGLIB就派上用场了。它通过继承目标类,生成一个子类作为代理。我建议你在Spring项目中,如果目标类没有接口,Spring会自动切换到CGLIB代理。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
System.out.println("[CGLIB日志] 开始...");
Object result = method.invoke(target, args);
System.out.println("[CGLIB日志] 结束");
return result;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
我曾经踩过的坑:CGLIB通过继承实现代理,所以final类或final方法是无法被代理的。有一次我把一个Service类声明为final,结果运行时直接报错,排查了半天才发现是这个问题。
三种代理对比
| 特性 | 静态代理 | JDK动态代理 | CGLIB代理 |
|---|---|---|---|
| 实现方式 | 手动编写代理类 | 反射 + 接口 | 继承 + 字节码增强 |
| 是否需要接口 | 是 | 是 | 否 |
| 性能 | 高(编译期确定) | 中(反射有开销) | 高(字节码操作) |
| 灵活性 | 低 | 高 | 高 |
| 适用场景 | 代理类少,接口稳定 | 有接口,需要灵活代理 | 无接口,或需要高性能 |
实战:手写一个迷你AOP框架
理解了代理模式,AOP(面向切面编程)就很好理解了。AOP说白了就是代理模式的高级应用——把“切面”(比如日志、事务)织入到业务代码中。我带你手写一个迷你AOP框架,你就明白Spring AOP的原理了。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
// 通知接口
interface Advice {
void before();
void after();
}
// 日志通知
class LogAdvice implements Advice {
@Override
public void before() {
System.out.println("[AOP日志] 方法执行前");
}
@Override
public void after() {
System.out.println("[AOP日志] 方法执行后");
}
}
// 简易AOP代理工厂
class SimpleAopFactory {
public static Object getProxy(Object target, List<Advice> advices) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
for (Advice advice : advices) {
advice.before();
}
Object result = method.invoke(target, args);
for (Advice advice : advices) {
advice.after();
}
return result;
}
);
}
}
// 使用
public class AopDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
List<Advice> advices = new ArrayList<>();
advices.add(new LogAdvice());
UserService proxy = (UserService) SimpleAopFactory.getProxy(target, advices);
proxy.addUser("王五");
}
}
输出结果:
[AOP日志] 方法执行前
添加用户:王五
[AOP日志] 方法执行后
你看,这个迷你AOP框架的核心就是JDK动态代理。Spring AOP在此基础上做了大量扩展——支持切点表达式、多种通知类型(前置、后置、环绕等)、自动织入等等。但底层的原理,说白了就是代理模式。
知识体系图
我的建议:在实际项目中,你不需要自己实现AOP框架,Spring AOP已经做得非常完善了。但理解代理模式的原理,能帮你更好地理解Spring AOP的底层机制。遇到问题时,你也能快速定位是代理的问题还是业务逻辑的问题。
嗯,代理模式就讲到这里。记住一句话:代理模式的核心是控制访问。静态代理适合简单场景,JDK动态代理适合有接口的场景,CGLIB代理适合无接口的场景。而AOP,就是代理模式在企业级开发中的最佳实践。