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

代理模式 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 版本中会被完全禁止。

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

相关推荐
乐之者v6 小时前
时区相关的问题,开发如何自测?
java
我不是8神6 小时前
消息队列(MQ)核心知识点总结
java·开发语言
BullSmall6 小时前
Tomcat 9 证书最佳存放路径指南
java·tomcat
一起养小猫6 小时前
《Java数据结构与算法》第四篇(一)Java.util包中的树结构实现详解
java·开发语言·数据结构
nbsaas-boot6 小时前
Java 还是 Go?——从工程规模到长期演进的技术选型思考
java·开发语言·golang
代码不停6 小时前
Java递归综合练习
java·开发语言·算法·回归
张哈大6 小时前
读懂大模型核心:Transformer 与 AI 发展的底层逻辑
java·神经网络·机器学习
魔镜前的帅比6 小时前
(开源项目)xsun_workflow_jira
java·jira
TH_16 小时前
15、IDEA可视化操作代码分支
java