前言

最近在写项目的时候,又写到很久没写的 AOP 切面实现一个需求,又想到上次同学面试的时候被问到了 Spring AOP 的实现原理是什么,以前就知道是用了代理模式,但是也没有进行过多的去研究,刚好碰到了也就研究一下代理模式。

什么是代理模式

代理模式就是通过一个代理对象来间接访问目标对象,这样可以在不改变目标对象的情况下,为它添加一些额外的功能或行为。简单来说,代理就是“替身”,它在幕后帮目标对象做一些额外的事。要扩展功能,就不需要更改源目标的代码了,只需要在代理类增加就可以了。

graph LR
    A[访客] --> B[代理类 增加额外功能]
    B --> C[目标类]

代理模式的分类

  1. 静态代理:代理类在编译时就已经确定,通常需要手动创建代理类。
  2. 动态代理:代理类在运行时动态生成,不需要提前创建,代理对象的创建由代理框架(如 JDK 动态代理或 CGLIB)控制。
特点静态代理动态代理
代理类创建时机代理类在编译时已经确定,需要手动创建代理类代理类在运行时动态生成,不需要提前创建
实现方式手动编写代理类,代理类与目标类关系固定使用代理框架(如 JDK 动态代理、CGLIB)在运行时创建代理类
目标类要求目标类必须实现接口(或继承某个类)JDK 动态代理:目标类必须实现接口;CGLIB:不需要接口

静态代理的实现

静态代理就是在程序编译时就创建好代理类,并让代理类和目标类实现相同的接口。代理类通过调用目标类的方法来完成任务,同时可以在调用前后添加一些额外的操作。

classDiagram
    UserService <|.. UserServiceImpl
    UserServiceProxy --> UserServiceImpl : delegates to
    UserService : +save()
    UserService : +update()
    UserService : +delete()
    UserServiceImpl : +save()
    UserServiceImpl : +update()
    UserServiceImpl : +delete()
    UserServiceProxy : +save()
    UserServiceProxy : +update()
    UserServiceProxy : +delete()

示例:静态代理模式

接口定义:

public interface UserService {
    void save(String username);
    void update(Long id, String username);
    void delete(Long id);
}

目标类实现:

public class UserServiceImpl implements UserService {
    @Override
    public void save(String username) {
        System.out.println("增加用户: " + username);
    }

    @Override
    public void update(Long id, String username) {
        System.out.println("编辑用户: " + username);
    }

    @Override
    public void delete(Long id) {
        System.out.println("删除用户: " + id);
    }
}

静态代理类:

public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;

    public UserServiceProxy(UserServiceImpl userService) {
        this.userService = userService;
    }
    @Override
    public void save(String userName) {
        // 在调用实际方法之前,做一些操作(如记录日志)
        System.out.println("日志:在添加用户之前");
        userService.save(userName);  // 调用真实对象的方法
        System.out.println("日志:在添加用户之后");
    }

    @Override
    public void update(Long id, String userName) {
        System.out.println("日志:在更新用户之前");
        userService.update(id, userName); 
        System.out.println("日志:在更新用户之后");
    }

    @Override
    public void delete(Long id) {
        System.out.println("日志:在删除用户之前");
        userService.delete(id);  // 调用真实对象的方法
        System.out.println("日志:在删除用户之后");
    }
}

使用代理

public class Main {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserService userServiceProxy = new UserServiceProxy(userService);

        userServiceProxy.save("yunzhi");
        userServiceProxy.update(1L, "yunzhi");
        userServiceProxy.delete(1L);
    }
}
打印结果:
  日志:在添加用户之前
  增加用户: yunzhi
  日志:在添加用户之后

  日志:在更新用户之前
  编辑用户: yunzhi
  日志:在更新用户之后

  日志:在删除用户之前
  删除用户: 1
  日志:在删除用户之后

结论:

静态代理的实现方式相对直观,代码也较为简洁易懂。然而,这种模式的缺点也非常明显:当需要代理的接口数量增多时,每增加一个接口就必须创建一个相应的代理类,这样会导致大量的代理类代码,造成系统的臃肿和维护上的困难。

动态代理的实现

与静态代理不同,动态代理是在运行时通过代理框架(如 JDK 动态代理或 CGLIB)来生成代理对象,不需要提前编写代理类。

JDK动态代理

JDK 动态代理,当目标类实现了接口时,并将接口的方法委托给目标类的实现。

JDK动态代理的实现步骤:

1.定义接口和实现类。
2.创建InvocationHandler实现,负责目标方法的拦截和增强
3.使用 Proxy 类创建代理对象。

创建一个 JdkLoggingInvocationHandler 类,统一实现日志处理代理

public class JdkLoggingInvocationHandler implements InvocationHandler {
    private Object target;

    public JdkLoggingInvocationHandler(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("日志:执行 " + method.getName() + " 方法完成");
        return result;
    }
}

使用代理

    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);

        UserService proxy = (UserService) Proxy.newProxyInstance(
                UserService.class.getClassLoader(),
                new Class<?>[]{UserService.class},
                handler);

        proxy.save("yunzhi");
        proxy.update(1L, "yunzhi");
        proxy.delete(1L);
    }
打印结果:
  日志:调用 save 方法
  增加用户: yunzhi
  日志:执行 save 方法完成

  日志:调用 update 方法
  编辑用户: yunzhi
  日志:执行 update 方法完成

  日志:调用 delete 方法
  删除用户: 1
  日志:执行 delete 方法完成

注意点

这里我们要注意当前的代理对象不是原始对象了,通过 Proxy.newProxyInstance 创建的代理对象,实际上并不是 UserServiceImpl 或其他实现类的实例,它是通过 Proxy 类在运行时生成的,实现了目标接口,并通过 InvocationHandler 实现了方法的增强逻辑。

image.png

目标类没有接口就不能使用JDK代理

这里我们把 UserService 移除掉,不是实现接口的形式

public class UserServiceImpl {
}

使用代理

UserServiceImpl userService = new UserServiceImpl();
JdkLoggingInvocationHandler handler = new JdkLoggingInvocationHandler(userService);

UserServiceImpl proxy = (UserServiceImpl) Proxy.newProxyInstance(
        UserServiceImpl.class.getClassLoader(),
        new Class<?>[]{UserServiceImpl.class},
        handler);

image.png

这里我们可以发现,如果目标类不是接口的话就没办法进行代理增强。

结论

在静态代理中,每个目标类都需要一个单独的代理类,代理类的代码重复且难以维护。

在JDK动态代理中,你只需要编写一个 InvocationHandler 来统一处理所有目标类的代理方法,增强逻辑可以复用,但是必须要求目标类是有接口实现。

CGLIB 动态代理

CGLIB 代理可以对任何没有实现接口的类进行代理,因为它是通过继承目标类并重写方法来实现的,在实际的场景中,有一些业务不总是实现接口,为了增强这些没有接口的类,所以 Spring 使用 CGLIB 的方式实现动态代理。

截取 Spring 项目中使用 AOP 的对象

image.png

CGLIB 动态代理的实现步骤:

1.定义目标类
CGLIB 代理是基于类的,而不是接口的,所以目标类不需要实现接口。

2.创建 MethodInterceptor 实现
MethodInterceptor 用于拦截目标方法,并进行增强逻辑。这个类相当于 JDK 动态代理中的 InvocationHandler。

3.使用 Enhancer 创建代理对象
Enhancer 是 CGLIB 的核心类,用于创建动态代理对象。它通过继承目标类并重写目标方法来实现代理。

创建一个 CglibLoggingMethodInterceptor 类,统一实现日志处理代理

public class CglibLoggingMethodInterceptor implements MethodInterceptor {
    private Object target;

    public CglibLoggingMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("日志:调用 " + method.getName() + " 方法");
        Object result = proxy.invokeSuper(obj, args);  // 调用父类的方法
        System.out.println("日志:执行 " + method.getName() + " 方法完成");
        return result;
    }
}

使用代理

    public static void main(String[] args) {
        UserServiceImpl userServiceImpl = new UserServiceImpl();

        // 创建 CGLIB 代理的 Enhancer 对象
        Enhancer enhancer = new Enhancer();

        // 设置代理的目标类(即 UserServiceImpl 类)
        // CGLIB 通过继承这个目标类来生成代理类
        enhancer.setSuperclass(UserServiceImpl.class);

        // 设置方法拦截器,代理会调用这个拦截器
        // 这里使用了一个自定义的 CglibLoggingMethodInterceptor 来拦截方法并添加日志增强
        enhancer.setCallback(new CglibLoggingMethodInterceptor(userServiceImpl));

        // 创建代理对象
        // 通过 Enhancer.create() 创建代理对象,此对象会继承 UserServiceImpl 类并重写其方法
        UserServiceImpl proxy = (UserServiceImpl) enhancer.create();

        proxy.save("yunzhi");
        proxy.update(1L, "yunzhi");
        proxy.delete(1L);
    }
打印结果:
  日志:调用 save 方法
  增加用户: yunzhi
  日志:执行 save 方法完成

  日志:调用 update 方法
  编辑用户: yunzhi
  日志:执行 update 方法完成

  日志:调用 delete 方法
  删除用户: 1
  日志:执行 delete 方法完成

注意点

这里我们要注意当前的代理对象不是原始对象了,通过 Enhancer 类来创建代理对象。这个代理对象是通过继承目标类 UserServiceImpl 来创建的,代理对象并不是原始的 UserServiceImpl 对象,而是通过继承生成的新类。

image.png

总结:

  • 静态代理

    • 适用于简单场景,代码重复且不灵活。
  • JDK 动态代理

    • 代理对象实现了目标接口,实际的目标对象没有改变。
    • 代理对象通过 Proxy 类动态生成,并通过 InvocationHandler 处理方法增强。
  • CGLIB 代理

    • 代理对象继承自目标类并重写目标方法,实际的目标类没有改变。
    • 代理对象通过 Enhancer 动态生成,并通过 MethodInterceptor 处理方法增强。

kexb
573 声望26 粉丝

引用和评论

0 条评论