设计模式-结构型模式-代理模式

概述

代理模式 : Proxy Pattern : 是一种结构型设计模式.

它允许你提供一个替代对象来代表真实对象,以此控制对真实对象的访问。
通过代理对象,可以在不改变目标对象的前提下,扩展其功能或控制对其的访问。
简单理解 : 代理模式就是一个中间人,通过中间人调用真实的目标对象。
根据代理的类型,可以分为 静态代理和动态代理。

其中动态代理 又可以根据实现方式的不同分为 jdk动态代理 和 cglib 动态代理。

角色

1、抽象主题(Subject)

定义了代理和实际主题的公共接口,以便任何使用实际主题的地方都可以使用代理而无需修改代码。这通常是接口或抽象类。
2、真实主题(Real Subject)
实现了抽象主题定义的接口,包含了实际业务逻辑。这是被代理的对象。
3、代理(Proxy)

也实现了抽象主题定义的接口,并持有一个对真实主题的引用。

代理负责在必要时创建和删除真实主题实例,并在调用真实主题的方法前或后添加额外的行为(例如:权限检查、日志记录等)。

静态代理

静态代理是代理模式的一种实现方式.

它的特点是:代理类在编译时就已经存在,并且代理类与目标类都实现了相同的接口。

(代码固定、实现接口)
就是 : 通过直接写一个代理类的方式来代理目标对象。

案例描述

复制代码
有一个 UserService  的服务接口,有一个方法 queryUserInfo,可以实现 对 用户信息的查询操作。
有一个具体的实现类 UserServiceImpl ,实现该接口,完成数据库的信息查询。
此时,有一个代理类,UserServiceProxy , 实现该接口,并代理实现类 UserServiceImpl,实现在用户信息查询之前的日志打印操作。
最终,客户端调用的时候,使用 代理类完成操作。

类图

具体实现

接口

java 复制代码
public interface UserService {
    void queryUserInfo(String userId);
}

实现类-被代理类

java 复制代码
public class UserServiceImpl implements UserService{
    @Override
    public void queryUserInfo(String userId) {
        System.out.println(" 模拟查询用户信息的数据库 : 查询的是 : "+userId);
    }
}

实现类-代理类

java 复制代码
public class UserServiceProxy implements UserService{

    /**
     * 被代理的对象
     */
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }


    @Override
    public void queryUserInfo(String userId) {
        // 添加日志
        System.out.println("代理类 - 添加 日志  begin");
        // 业务逻辑 : 执行原来被代理类的业务逻辑
        userService.queryUserInfo(userId);
        // 添加日志
        System.out.println("代理类 - 添加 日志  end");
    }
}

客户端

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建目标对象(被代理对象)
        UserService userService = new UserServiceImpl();
        // 创建代理对象
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        // 通过代理对象执行对应的方法
        userServiceProxy.queryUserInfo("10001");
    }
}
复制代码
运行结果:
代理类 - 添加 日志  begin
 模拟查询用户信息的数据库 : 查询的是 : 10001
代理类 - 添加 日志  end

动态代理-JDK动态代理

JDK 动态代理 是一种基于反射机制的代理模式实现方式。

与静态代理不同,动态代理不需要手动编写代理类 ,而是在运行时动态生成代理对象 。
被代理的类,需要实现接口!

它通过 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 实现。

核心概念

Proxy 类

提供了创建动态代理对象的方法(如 Proxy.newProxyInstance())。
InvocationHandler 接口

定义了一个方法 invoke(),用于拦截对代理对象方法的调用。在 invoke() 方法中可以添加额外逻辑(如权限检查、日志记录等)。
特点
动态生成 :代理类在运行时动态生成,无需手动编写代码。
依赖接口 :目标类必须实现接口,动态代理只能代理接口中的方法。

案例描述

复制代码
有一个 UserService  的服务接口,有一个方法 queryUserInfo,可以实现 对 用户信息的查询操作。
有一个具体的实现类 UserServiceImpl ,实现该接口,完成数据库的信息查询。
此时,有一个代理类,UserServiceProxyHandler, 实现InvocationHandler接口,在 invoke 方法中调用目标类的方法。
最终,客户端调用的时候,使用 代理类完成操作。

类图

具体实现

接口

java 复制代码
public interface UserService {
    String queryUserInfoWithReturn(String userId);
}

具体实现

java 复制代码
public class UserServiceImpl implements UserService{
  
    @Override
    public String queryUserInfoWithReturn(String userId) {
        System.out.println(" 模拟查询用户信息的数据库-带返回值 : 查询的是 : "+userId);
        return "被代理类的返回值-helloworld";
    }

}

代理类

这里用到的是 reflect 反射的包中的内容。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserServiceProxyHandler implements InvocationHandler {

    /**
     * 被代理对象
     */
    private Object target;

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

    // todo 核心的代理方法
    @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;
    }
}

客户端

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建目标对象(被代理对象)
        UserService userService = new UserServiceImpl();
        // 创建代理对象-jdk动态代理 , 强转一下类型即可
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new UserServiceProxyHandler(userService));

        // 通过代理对象执行对应的方法
        String s = userServiceProxy.queryUserInfoWithReturn("10001");
        System.out.println("s = " + s);

    }
}
复制代码
运行结果:
代理类 - 添加日志 - 开始
 模拟查询用户信息的数据库-带返回值 : 查询的是 : 10001
代理类 - 添加日志 - 结束
s = 被代理类的返回值-helloworld

动态代理-CGLib代理

核心概念

CGLIB(Code Generation Library)动态代理 是一种基于字节码生成技术的代理模式实现方式。与 JDK 动态代理不同,CGLIB 不需要目标类实现接口 ,而是通过继承 的方式动态生成目标类的子类,并在子类中拦截方法调用。
CGLIB 的工作原理

CGLIB 通过 ASM 字节码生成库,在运行时动态生成目标类的子类。

子类会重写目标类的方法,并在方法调用前后插入额外逻辑(如日志记录、权限检查等)。
特点

无需接口:目标类不需要实现任何接口。

依赖继承:通过继承目标类生成代理类,因此目标类不能是 final 类或包含 final 方法。

性能较高:相比反射机制,CGLIB 的性能通常更高。
使用场景

  • 当目标类没有实现接口时,JDK 动态代理无法使用,而 CGLIB 是一个很好的替代方案。

  • 需要对目标类的所有方法进行统一增强时(如事务管理、日志记录等)。
    CGLIB 的主要组件

  • Enhancer
    CGLIB 的核心类,用于创建代理对象。

  • MethodInterceptor 接口
    定义了一个方法 intercept(),用于拦截对代理对象方法的调用。可以在此方法中添加额外逻辑。

案例描述

复制代码
有一个普通的类 UserHelper ,有一个方法 queryUserInfo,完成数据库的用户信息的查询。
有一个增强类,UserHelperInterceptor, 实现MethodInterceptor接口,在 intercept 方法中调用目标类的方法。
有一个代理类,UserHelperProxy,提供一个获取代理对象的方法,getProxy();
最终,客户端调用的时候,使用 代理类获取到代理对象,调用目标方法完成操作。

类图

被代理类

java 复制代码
public class UserHelper {

    /**
     * 一个普通的方法
     * @param userId
     * @return
     */
    public String queryUserInfoWithReturn(String userId) {
        System.out.println(" 模拟查询用户信息的数据库-带返回值 : 查询的是 : "+userId);
        return "被代理类的返回值-helloworld";
    }
}

拦截器类

这里直接使用了 spring 包中的 相关的接口和类。
也可以 单独导入 CGLib 相关的依赖包。

单独使用时,如果jdk版本较高,需要在 启动参数中添加 --add-opens java.base/java.lang=ALL-UNNAMED",这是因为高版本中增加了对未命名类的限制!

java 复制代码
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class UserHelperInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(" 方法拦截器 - begin :  "+method.getName());
        Object res =  methodProxy.invokeSuper(o, objects);
        System.out.println(" 方法拦截器 - end :  "+method.getName());
        return res;
    }
}

获取代理对象的类(代理的主要逻辑)

可以不写这个类,直接在使用代理对象的地方将 getProxy 方法中的逻辑写出来即可。

java 复制代码
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class CommonHelperProxy<T>{

    /**
     * 子类的 拦截器 的对象,增强的时候用
     */
    private MethodInterceptor methodInterceptor;

    public CommonHelperProxy(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    /**
     * 获取代理对象 : 根据类型参数创建的代理对象
     * @return
     */
    public T getProxy() {
        Enhancer enhancer = new Enhancer();
        // 设置目标类的类型
        enhancer.setSuperclass(UserHelper.class);
        // 设置回调函数
        enhancer.setCallback(methodInterceptor);
        // 创建代理对象
        return (T) enhancer.create();
    }
}

客户端

java 复制代码
public class UserHelperProxyClient {
    public static void main(String[] args) {
        // 创建拦截器对象
        UserHelperInterceptor userHelperInterceptor = new UserHelperInterceptor();
        // 获取代理对象
        CommonHelperProxy<UserHelper> proxy = new CommonHelperProxy<>(userHelperInterceptor);
        UserHelper proxyObject = proxy.getProxy();

        // 通过代理对象执行方法
        String s = proxyObject.queryUserInfoWithReturn("10001");
        System.out.println("s = " + s);
    }
}
复制代码
运行结果:
 方法拦截器 - begin :  queryUserInfoWithReturn
 模拟查询用户信息的数据库-带返回值 : 查询的是 : 10001
 方法拦截器 - end :  queryUserInfoWithReturn
s = 被代理类的返回值-helloworld

以上就是代理模式的三种写法。

相关推荐
零基础1239 分钟前
PDF 处理工具全攻略:pdf24、PDFgear、Adobe Acrobat DC 与 Stirling-PDF 深度对比
java·开发语言·pdf
SL_staff24 分钟前
制造业数字化协同:为什么甘特图只是起点,不是终点
java·开源·全栈
anew___41 分钟前
《从零手写操作系统 (29):管道与重定向进阶——命名管道、Here Document与Shell语法扩展》
java·开发语言·前端·javascript·网络
Mikko71 小时前
JVM 线上排查实战(六):JFR 怎么用?JDK 8 要不要加 UnlockCommercialFeatures、jfr 命令在哪、录的文件为什么读不了
java·运维·jvm·后端
奇牙coding1 小时前
GPT-5.5 API 报 401 但 GPT-5.4 正常怎么办?不是 Key 失效,是 Organization 头的强制校验变了
java·网络·gpt·ai
a努力。1 小时前
Context-State-Memory三重信息架构揭秘
java·服务器·前端
captain3762 小时前
Maven
java·后端·idea
liangshanbo12152 小时前
主系统登录后,子系统怎么实现自动登录?
java·网络·数据库
ShineWinsu2 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
rannn_1112 小时前
【Java面试题】高频面试题1|Java 后端、MySQL、Redis 与 RAG 面试整理
java·jvm·数据库·后端·面试