代理模式:静态代理、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在此基础上做了大量扩展——支持切点表达式、多种通知类型(前置、后置、环绕等)、自动织入等等。但底层的原理,说白了就是代理模式。

知识体系图

代理模式知识体系 代理模式 静态代理 手动编写代理类 编译期确定代理关系 适合代理类少的场景 JDK动态代理 反射 + InvocationHandler 要求目标类实现接口 运行时生成代理对象 CGLIB代理 继承 + 字节码增强 无需接口 不能代理final类/方法 实战:手写迷你AOP框架 前置通知 → 目标方法 → 后置通知 核心:JDK动态代理 + 通知链

我的建议:在实际项目中,你不需要自己实现AOP框架,Spring AOP已经做得非常完善了。但理解代理模式的原理,能帮你更好地理解Spring AOP的底层机制。遇到问题时,你也能快速定位是代理的问题还是业务逻辑的问题。

嗯,代理模式就讲到这里。记住一句话:代理模式的核心是控制访问。静态代理适合简单场景,JDK动态代理适合有接口的场景,CGLIB代理适合无接口的场景。而AOP,就是代理模式在企业级开发中的最佳实践。

公众号:蓝海资料掘金营,微信 deep3321