二十三种设计模式(十二)--代理模式

代理模式 Proxy

核心定义, 代理模式就是为其他对象提供一种代理, 用来控制对这个对象的访问

代理模式的关键在于分离使用者与目标实例, 作为中间层, 除了调用目标实例的所有功能外, 还能封装其他功能.

和现实情况很像, 类似于消费者和现金的关系, 消费者可以带着现金直接消费, 但是当有大额消费时, 带着很多钱, 既不安全也不方便, 这时我们需要一张银行卡, 用银行卡进行各种复杂的交易就方便了很多.

代理模式分为静态代理模式动态代理模式两种类型

静态代理模式: 通过实现接口或继承目标类的方式实现代理功能.

动态代理模式: 利用反射机制或CGLIB动态获取代理类的功能接口

静态代理类的接口随着目标类接口变化而变化, 不够灵活

动态代理类能够自动获取目标类的接口变化, 非常灵活

实际开发过程中, 动态代理模式的使用场景更多.

静态代理模式: 通过实现目标类共同接口的方式实现
java 复制代码
public class ProxyPattern {
    public static void main(String[] args) {
        // 先实例化目标对象
        TextLogger logger = new TextLogger();
        // 再实例化代理类, 并传入目标对象
        TextLogProxy proxy = new TextLogProxy(logger);

        // 使用时, 通过代理类的实例对象调用log方法
        proxy.log();
    }
}

// 目标类统一接口
interface LoggerInterface {
    void log();
}

// 目标类实现
class TextLogger implements LoggerInterface {
    // 这里使用log方法代表目标类中非常复杂的操作
    @Override
    public void log() {
        System.out.println("[text] log into text file.");
    }
}


// 代理类, 通过继承和目标类相同的接口, 实现代理
class TextLogProxy implements LoggerInterface {
    // 这里持有目标类的接口引用, 而不是TextLogger引用, 这样扩展性更好一点
    LoggerInterface textLogger;

    TextLogProxy(LoggerInterface obj) {
        this.textLogger = obj;
    }

    // 代理类相同的log方法前后会封装一些其他的逻辑
    @Override
    public void log() {
        System.out.println("[before] doing complicate things....");
        // 代理类中的log方法没有具体实现, 而是调用目标类对象中的方法
        textLogger.log();
        System.out.println("[after] job done.");
    }
}

运行结果

复制代码
[before] doing complicate things....
[text] log into text file.
[after] job done.

上述方式是静态代理模式下, 最常见也最推荐的实现方法.

但如果目标类没有继承任何接口时, 要实现代理模式就要通过继承或聚合的方式来实现, 简要示例如下:

静态代理模式: 通过继承目标类实现
java 复制代码
// 目标类:无接口,直接包含核心业务逻辑
class TextLogger {
    // 核心业务方法
    public void log() {
        System.out.println("[text] log into text file.");
    }
}

// 代理类:继承目标类,重写方法实现代理
class TextLoggerProxy extends TextLogger {
    @Override
    public void log() {
        // 前置增强逻辑
        System.out.println("[before] 校验日志权限、初始化资源...");
        // 调用父类(目标类)的核心方法
        super.log();
        // 后置增强逻辑
        System.out.println("[after] 释放资源、记录日志状态...");
    }
}
静态代理模式: 通过聚合目标对象实现
java 复制代码
// 目标类:无接口,核心业务逻辑
class TextLogger {
    public void log() {
        System.out.println("[text] log into text file.");
    }

    // 目标类的其他方法
    public void logError() {
        System.out.println("[text] log error into text file.");
    }
}

// 代理类:聚合目标类,无接口/继承
class TextLoggerProxy {
    // 聚合:持有目标类的引用
    private TextLogger target;

    // 构造方法传入目标对象
    public TextLoggerProxy(TextLogger target) {
        this.target = target;
    }

    // 定义与目标类相同的方法,实现代理
    public void log() {
        // 前置增强
        System.out.println("[before] 校验权限、初始化资源...");
        // 调用目标类的方法
        target.log();
        // 后置增强
        System.out.println("[after] 释放资源、记录状态...");
    }
}
动态代理模式: 使用Java反射机制实现
java 复制代码
// 动态代理类: 手动实现java.lang.reflect.InvocationHandler包中的InvocationHandler接口
class LogInvocationHandler implements InvocationHandler {
    // 自定义目标对象, 并将其私有化
    private Object log_obj;
    // 构造时传入目标对象
    LogInvocationHandler(Object obj) {
        this.log_obj = obj;
    }

    // 当调用log_obj对象中的方法时, 下面的invoke方法会被自动调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("[before] doing complicate thins....");
        Object target_obj = method.invoke(log_obj, args);
        System.out.println("[after] job done.");

        return target_obj;
    }

    // 生成代理对象
    public static Object createProxyObj(Object log_obj) {
        // 通过调用Proxy包的newProxyInstance方法, 根据目标对象, 动态生成代理对象
        return Proxy.newProxyInstance(
                log_obj.getClass().getClassLoader(), // log_obj对象的类加载器
                log_obj.getClass().getInterfaces(),  // log_obj对象所实现的接口(要求目标类对象必须实现自统一的接口)
                new LogInvocationHandler(log_obj)    // 调用处理器
        );
    }
}


// 定义毫不相关的两种类, 两种统一接口
interface LoggerInterface {
    void log();
}
interface PaymentInterface {
    void pay();
    void log();
}

// 接口LoggerInterface的类实现
class TextLogger implements LoggerInterface {
    // 这里使用log方法代表目标类中非常复杂的操作
    @Override
    public void log() {
        System.out.println("[text] log into text file.");
    }
}

// 接口PaymentInterface的类实现
class AliPay implements PaymentInterface {
    @Override
    public void pay() {
        System.out.println("[pay] doing Alipay success.");
    }

    @Override
    public void log() {
        System.out.println("[pay] log after pay.");
    }
}

调用实现:

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

public class ProxyPattern {
    public static void main(String[] args) {
        // 我们有两种不相关的类对象, 都可以通过自定义的动态代理类去调用它们
        TextLogger textLogger = new TextLogger();
        AliPay alipay = new AliPay();

        // 这里要注意, Java 原生的动态代理(java.lang.reflect.Proxy)是基于接口生成代理对象的,而不是基于实现类。
        // 所以这里获取代理对象后, 只能强转转为接口类型, 而不能转为实现类的类型
        // 获取textLogger的代理对象
        LoggerInterface textLoggerProxy = (LoggerInterface)LogInvocationHandler.createProxyObj(textLogger);
        // 获取alipay的代理对象
        PaymentInterface alipayProxy = (PaymentInterface)LogInvocationHandler.createProxyObj(alipay);

        textLoggerProxy.log();
        System.out.println("===================");
        alipayProxy.pay();
        alipayProxy.log();
    }
}

运行结果:

复制代码
[before] doing complicate thins....
[text] log into text file.
[after] job done.
===================
[before] doing complicate thins....
[pay] doing Alipay success.
[after] job done.
[before] doing complicate thins....
[pay] log after pay.
[after] job done.

这里注意, 接口PaymentInterface中有两个方法, 每个方法被调用时, LogInvocationHandler.invoke方法都会被调用一次

动态代理模式: 使用CGLIB库实现动态代理

Java语法规定只能单继承, 而Proxy.newProxyInstance生成的动态代理类,已经默认继承了java.lang.reflect.Proxy类(这是 JDK 动态代理的底层实现要求), 因此动态代理类无法再继承, 只能通过实现接口的方式来保证方法的一致性.

如果你的目标类没有实现任何接口, 可以使用CGLIB动态代理

CGLIB位于单独的jar依赖包中, 需要从第三方获取

基于maven包管理工具, 需要在项目依赖文件pom.xml文件中添加如下依赖:

xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

示例如下:

java 复制代码
// 动态代理类 CglibInterceptor, 代理类名称自定义, 但必须实现MethodInterceptor方法
class CglibInterceptor implements MethodInterceptor {
    Object log_obj;

    CglibInterceptor(Object obj) {
        this.log_obj = obj;
    }

    // 目标对象中的方法被调用时, 这里的intercept方法会被自动调用
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[before] some complicate code....");
        Object obj = method.invoke(log_obj, objects);
        System.out.println("[after] job done.");
        return obj;
    }

    // 生成动态代理对象
    public static Object createProxyObj(Object target) {
        Enhancer enhancer = new Enhancer();
        // 设置父类为目标类(CGLIB通过继承实现代理)
        enhancer.setSuperclass(target.getClass());
        // 设置回调方法(相当于InvocationHandler的invoke)
        enhancer.setCallback(new CglibInterceptor(target));
        // 创建代理对象
        return enhancer.create();
    }
}


// 两个毫不相关的类
class TextLogger {
    // 这里使用log方法代表目标类中非常复杂的操作
    public void log() {
        System.out.println("[text] log into text file.");
    }
}

class AliPay{
    public void pay() {
        System.out.println("[pay] doing Alipay success.");
    }

    public void log() {
        System.out.println("[pay] log after pay.");
    }
}

调用示例:

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class ProxyPattern {
    public static void main(String[] args) {
        // 创建两个毫不相关的目标类
        TextLogger textLogger = new TextLogger();
        AliPay alipay = new AliPay();

        // 通过基于CGLIB的动态代理类获取两个对象的代理对象
        TextLogger textLoggerProxy = (TextLogger) CglibInterceptor.createProxyObj(textLogger);
        AliPay alipayProxy = (AliPay) CglibInterceptor.createProxyObj(alipay);

        textLogger.log();
        System.out.println("====================");
        alipayProxy.pay();
        alipayProxy.log();
    }
}

运行结果:

复制代码
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/C:/Users/xuegu/.m2/repository/cglib/cglib/3.3.0/cglib-3.3.0.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[text] log into text file.
====================
[before] some complicate code....
[pay] doing Alipay success.
[after] job done.
[before] some complicate code....
[pay] log after pay.
[after] job done.

我是基于Java11环境运行的代码, 所以会出现上述警告, 提示使用了非法的反射

原因是JDK 版本与 CGLIB 版本的兼容性问题导致的,具体来说:

CGLIB 的底层实现依赖反射访问 JDK 的私有 / 受保护方法:CGLIB 是通过 ASM 字节码框架生成目标类的子类作为代理类,而生成字节码后需要通过ClassLoader.defineClass方法将字节码加载为 Class 对象。ClassLoader.defineClass是 JDK 中的受保护方法(原本只能被 ClassLoader 的子类调用),CGLIB 为了通用化调用这个方法,使用了反射强行突破访问权限。

JDK 9 及以上的模块化系统(JPMS)限制了非法反射访问:JDK 9 引入了模块系统(Java Platform Module System),严格限制了代码通过反射访问其他模块的非公共 API(包括 JDK 自身的内部 API、受保护 / 私有方法)。当 CGLIB 用反射调用ClassLoader.defineClass时,JDK 会检测到这种 "非法反射访问" 并抛出警告,提示这种行为在未来的 JDK 版本中会被完全禁止。

完美的解决方式暂时还没找到, 仅记录以上警告原因.

相关推荐
Moe48812 分钟前
Spring AI Advisors:从链式增强到递归顾问
java·后端
敖正炀16 分钟前
ReentrantReadWriteLock、ReentrantLock、synchronized 对比
java
cike_y27 分钟前
Java反序列化漏洞-Shiro721流程分析
java·反序列化·shiro框架
极创信息1 小时前
信创系统认证服务怎么做?从适配到验收全流程指南
java·大数据·运维·tomcat·健康医疗
格鸰爱童话1 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
白宇横流学长1 小时前
停车场管理系统的设计与实现
java
Flittly1 小时前
【SpringAIAlibaba新手村系列】(18)Agent 智能体与今日菜单应用
java·spring boot·agent
木井巳2 小时前
【递归算法】目标和
java·算法·leetcode·决策树·深度优先
亦暖筑序2 小时前
手写 Spring AI Agent:让大模型自主规划任务,ReAct 模式全流程拆解
java·人工智能·spring
敖正炀2 小时前
ReentrantLock 与 synchronized对比
java