浅谈代理模式

2022年04月06日 第一版
2023年12月13日 第二版本更新,学而时习,思则常新

1. 代理模式

抛开精密的学术用语,就是在不修改原来对象的前提下,提供额外的功能与操作。

2. 分类

下面先展示几个类,JDKCGLIB 共同会用:一个接口+ 一个实现类

/**
 * @author: handsometaoa
 * @description
 * @date: 2023/12/13 11:43
 */
public interface UserService {
    String showMsg();
}
/**
 * @author: handsometaoa
 * @description
 * @date: 2023/12/13 11:44
 */
@Service
public class UserServiceImpl implements UserService {
    @Override
    public String showMsg() {
        System.out.println("展示消息");
        return "返回信息";
    }
}

2.1 静态代理:

现在我有一个功能,想要在方法A前后加日志、加控制权限、加审计,但是我不想修改A方法。

虽然你只添加了日志,出事都找你👻

那就可以把 【日志+被代理对象.方法(旧方法)+日志】作为新方法。

代理类:

public class UserServiceLogProxy implements UserService {
    private final UserService userService;
    
    public UserServiceLogProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void sayHello() {
        System.out.println("开始记录日志");
        userService.showMsg();
        System.out.println("结束记录日志");
    }
}

缺点:

比如说,我现在有100个类似的方法,都需要加日志,我需要写100个代理类。

按道理(开始讲道理了)日志信息是功能相同的,可以复用。


2.2 动态代理

我们设想可以把日志方法抽象出去,动态插入各个想记录日志的方法,热插拔(这个词妙,生动形象),美哉。

2.2.1 JDK 动态代理

实现步骤:

  1. 定义一个接口及其实现类
  2. 实现 InvocationHandler (java.lang.reflect.InvocationHandler) 并重写 invoke 方法,在 invoke 方法中我们会调用原生方法(被代理类方法)并增加自定义逻辑(日志等功能)
  3. 通过 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) 方法创建代理对象

    JDK代理有一个缺点就是只能代理实现接口的类

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler);
    

代码:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author: handsometaoa
 * @description JDK动态代理,基于接口
 * @date: 2023/12/13 11:55
 */
public class LogProxyJdk implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始记录日志");
        Object result = method.invoke(target, args);
        System.out.println("结束记录日志");
        return result;
    }

    /**
     * 获取代理对象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}

使用:

@RestController
@RequestMapping("dynamic")
public class DynamicController {
    @Resource
    private UserService userService;

    @RequestMapping("/jdk")
    String jdk() {
        LogProxyJdk logProxyJdk = new LogProxyJdk(userService);
        UserService proxy = (UserService) logProxyJdk.getProxy();
        return proxy.showMsg();
    }
}

缺点:

JDK动态代理只能代理有接口的方法,如果被代理类未实现接口,我们可以用 CGLIB 动态代理机制来实现。

2.2.2 CGLIB 动态代理

实现步骤:

  1. 定义一个类
  2. 实现 MethodInterceptor (org.springframework.cglib.proxy.MethodInterceptor) 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法
  3. 通过 Enhancer 类的 create() 方法创建代理类 ,CGLIB 通过 fast-class 来提高效率,首先将方法都建立索引,然后通过索引快速查找,来调用方法

代码:

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author: handsometaoa
 * @description CGLIB 动态代理
 * @date: 2023/12/13 11:43
 */
public class LogProxyCglib implements MethodInterceptor {

    private final Class clazz;

    public LogProxyCglib(Class clazz){
        this.clazz = clazz;
    }

    /**
     * @param object      表示要拦截的对象,即被代理的对象。
     * @param method      表示要拦截的方法,即被代理对象的方法。
     * @param args        表示方法的参数列表,即被代理对象方法的参数。
     * @param methodProxy 表示用于调用原始方法的代理对象。
     */
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("开始记录日志");
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("结束记录日志");
        return result;
    }

    public Object getProxy(){
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }
}

使用

@RestController
@RequestMapping("dynamic")
public class DynamicController {
    @RequestMapping("/cglib")
    String cglib(){
        LogProxyCglib logProxyCglib = new LogProxyCglib(UserServiceImpl.class);
        UserService proxy = (UserService) logProxyCglib.getProxy();
        return proxy.showMsg();
    }
}

3. 参考文献 站在前人的脚上,踏步前进。

  1. 漫画:AOP 面试造火箭事件始末
  2. Java动态代理-最通俗易懂的动态代理
  3. cglib动态代理原理和源码
posted @   帅气的涛啊  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示

目录