深入理解Java代理模式:从静态到动态的实现与应用

news2024/9/25 11:18:54

1、引言

在Java编程中,代理模式是一种常见的设计模式,用于在不修改原始代码的情况下,为对象添加额外的功能。代理模式有两种主要类型:静态代理和动态代理。本文将全面探讨这两种代理模式,包括它们的基本概念、实现方式、应用场景及其优缺点。

2、静态代理

静态代理是在编译时确定代理类的。代理类和目标类都需要实现相同的接口,代理类持有对目标对象的引用,并在调用目标对象的方法前后插入额外的逻辑。

先给大家看一张基础设计代理的原理图

在这里插入图片描述

2.1、静态代理的示例

定义一个接口

public interface ISubject{
    void eat();
}

实际类

public class RealSubject implements ISubject{
    @Override
    public void eat() {
        System.out.println("RealSubject do eat...");
    }
}

代理对象

public class ProxyObject implements ISubject{

    private ISubject iSubject;

    public ProxyObject(ISubject iSubject) {
        this.iSubject = iSubject;
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    @Override
    public void eat() {
        preSomething();
        iSubject.eat();
        afterSomething();
    }
}

工厂类

public class ObjectFactory {

    public static ISubject getInstance(){
        return new ProxyObject(new RealSubject());
    }
    
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testProxy();
    }

    public static void testProxy(){
        ISubject iSubject = ObjectFactory.getInstance();
        iSubject.eat();
    }
}

上面这个是最简单的例子,我们一般在使用的代理模式的时候,我们只关注代理是谁,真实对象是谁即可,如何根据这两个参数(也就是类的全限定名称)进行代理呢?我们可以使用反射,下来我们重构一下工厂类和main方法,示例代码如下:

public class ObjectFactory { 

    public ObjectFactory() {}

    /**
     * 通过类名字符串创建并返回指定类型的实例
     * 
     * @param className 要创建的类的全限定名字符串
     * @return 创建的实例
     * @throws RuntimeException 如果指定的类不存在,或者实例化过程中出现错误,则抛出运行时异常
     */
    public static <T> T getInstance(String className){
        T t = null;
        try {
            // 通过反射机制创建指定类的实例
            t =  (T) Class.forName(className).newInstance();
        } catch (InstantiationException e) {
            // 如果类无法被实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果类的构造方法不可访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果类不存在,则抛出运行时异常
            throw new RuntimeException(e);
        }
        return t;
    }

    /**
     * 根据代理名和真实对象名创建并返回一个代理对象实例
     * 
     * @param proxyName 代理类的全限定名,用于动态加载和实例化代理对象
     * @param realName 真实对象类的全限定名,用于获取真实对象的实例
     * @param <T> 泛型标记,表示返回的代理对象类型
     * @return T 返回一个实现了真实对象接口的代理对象实例
     * @throws RuntimeException 如果在实例化代理对象过程中发生错误,则抛出运行时异常
     * 
     * 此方法主要用于在运行时动态创建代理对象,通过反射机制加载并实例化指定的代理类
     * 代理类需要通过构造方法接收真实对象作为参数,以便代理对象能够调用真实对象的方法
     */
    public static <T> T getInstance(String proxyName, String realName){
        // 初始化代理对象为null
        T t = null;
        // 获取真实对象的实例
        T obj = getInstance(realName);
        // 尝试通过反射机制实例化代理对象
        try {
            // 使用代理类名和接口类型来创建代理对象实例
            t = (T) Class.forName(proxyName).getConstructor(obj.getClass().getInterfaces()[0]).newInstance(obj);
        } catch (InstantiationException e) {
            // 如果代理类无法实例化,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            // 如果构造方法非法访问,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            // 如果调用构造方法时目标方法抛出异常,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            // 如果未找到符合要求的构造方法,则抛出运行时异常
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            // 如果未找到代理类,则抛出运行时异常
            throw new RuntimeException(e);
        }
        // 返回创建的代理对象实例
        return t;
    }
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testReflect();
    }

    public static void testReflect(){
        ISubject iSubject = ObjectFactory.getInstance("com.hy.proxy.ProxyObject", "com.hy.proxy.RealSubject");
        iSubject.eat();
    }
    
}

2.2 静态代理的优缺点

优点:

  1. 代码清晰,易于理解。
  2. 适合于需要明确代理逻辑的场景。

缺点:

  1. 每个代理都需要一个对应的目标类,代码冗余。
  2. 不够灵活,代理类和目标类的关系在编译时确定。

3、动态代理

动态代理是在运行时创建代理对象的,能够在运行时决定具体的代理逻辑。Java的动态代理分为两种主要实现方式:

  • JDK动态代理
  • CGLIB动态代理

在这里插入图片描述

对于动态代理,代理对象可以代理一个类中的多个实现方法,即一个真实类实现多个接口,一个代理类就可以完全代理

3.1、JDK动态代理

JDK动态代理基于反射机制,可以在运行时创建代理对象。要使用JDK动态代理,目标类必须实现一个或多个接口。通过 Proxy 类和 InvocationHandler 接口,可以创建代理对象,并在调用方法时插入额外的逻辑。

3.1.1、JDK动态代理示例代码

接口

public interface ITest {
    void play();
}

public interface ISubject{
    void eat();
}

真实类

public class DynamicRealObject implements ISubject, ITest{
    @Override
    public void eat() {
        System.out.println("DynamicRealObject eat...");
    }

    @Override
    public void play() {
        System.out.println("DynamicRealObject play...");
    }
    
}

动态代理类

public class DynamicProxy implements InvocationHandler {

    private Object target;

    /**
     * 绑定目标对象并生成代理对象
     * 本方法主要用于通过动态代理绑定一个目标对象,并返回该目标对象的代理实例
     * 
     * @param target 目标对象,即需要通过代理的对象
     * @return 返回目标对象的代理实例,该实例可以用于方法拦截和调用
     */
    public Object bind(Object target) {
        // 绑定目标对象到当前代理实例
        this.target = target;
        // 利用Java动态代理机制创建并返回目标对象的代理实例
        // 该代理实例将使用目标对象的类加载器,实现目标对象所实现的所有接口,并将当前代理实例作为调用处理程序
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    private void preSomething() {
        System.out.println("preEat......");
    }

    private void afterSomething() {
        System.out.println("afterEat......");
    }

    /**
     * 该方法通过Java动态代理机制,拦截调用目标方法的请求
     * 在调用目标方法之前和之后执行特定操作,实现环绕通知的功能
     * 这种方式可以用于实现事务管理、日志记录、权限控制等横切关注点
     *
     * @param proxy 代理对象,通常不用直接操作
     * @param method 被调用的目标方法的信息
     * @param args 调用目标方法时传递的参数
     * @return 目标方法的返回值
     * @throws Throwable 目标方法抛出的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用目标方法之前执行的操作,如开启事务、日志记录等
        preSomething();
        // 调用目标方法,并传递参数
        Object obj = method.invoke(target, args);
        // 在调用目标方法之后执行的操作,如关闭事务、日志记录等
        afterSomething();
        // 返回目标方法的执行结果
        return obj;
    }
    
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testDynamicProxy();
    }

    public static void testDynamicProxy(){
    	// 结合静态代理的反射,可以这样写
    	// ISubject iSubject = (ISubject) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
    	// ITest iTest = (ITest) new DynamicProxy().bind(ObjectFactory.getInstance("com.hy.proxy.DynamicRealObject"));
        ISubject iSubject = (ISubject) new DynamicProxy().bind(new DynamicRealObject());
        iSubject.eat();
        ITest iTest = (ITest) new DynamicProxy().bind(new DynamicRealObject());
        iTest.play();
    }
    
}

3.2、CGLIB动态代理类

CGLIB(Code Generation Library)是一个功能强大的代码生成库,允许在运行时动态创建代理类。与JDK动态代理不同,CGLIB不要求目标类实现接口,而是通过继承目标类来创建代理。

3.2.1 CGLIB动态代理示例代码

目标类

public class CglibProxy {

    public void doSomething(){
        System.out.println("Cglib do something");
    }
    
}

代理类

public class CglibInterceptor implements MethodInterceptor {

    private final Object target;

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

    private void preDoSomething() {
        System.out.println("preDoSomething......");
    }

    private void afterDoSomething() {
        System.out.println("afterDoSomething......");
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        preDoSomething();
        Object obj = method.invoke(target, objects);
        afterDoSomething();
        return obj;
    }
}

调用

public class TestProxy {

    public static void main(String[] args) {
        testCglib();
    }

    /**
     * 使用CGLIB进行动态代理的测试方法
     * 该方法通过CGLIB框架创建一个CglibProxy的子类实例,以实现方法的拦截和调用
     */
    public static void testCglib(){
        // 创建CGLIB增强器实例
        Enhancer enhancer = new Enhancer();
        // 设置代理类的父类为CglibProxy类,这样CGLIB会生成该类的子类
        enhancer.setSuperclass(CglibProxy.class);
        // 设置拦截器,传入CglibProxy实例作为拦截器的关键信息
        enhancer.setCallback(new CglibInterceptor(new CglibProxy()));
        // 通过增强器创建代理类实例
        CglibProxy cglibProxy = (CglibProxy) enhancer.create();
        // 通过代理实例调用目标方法,此调用将被CglibInterceptor拦截并处理
        cglibProxy.doSomething();
    }
}

3.3、动态代理的优缺点

优点:

  1. 灵活性高,可以在运行时创建代理对象。
  2. 不需要修改目标类代码。

缺点:

  1. JDK动态代理只能代理实现了接口的类,不能代理具体类。
  2. CGLIB动态代理在生成代理类时会继承目标类,这可能导致一些问题,如不能代理final类或方法。

3.4 CGLIB和JDK动态代理区别

具体区别如下:

  1. 实现原理: JDK动态代理是基于Java反射机制实现的,它要求目标类必须实现一个或多个接口,代理对象在运行时动态创建,通过实现目标类接口的方式来代理目标类。 CGLIB代理则是基于ASM字节码框架实现的,它可以代理没有实现接口的目标类。CGLIB在运行时通过动态生成目标类的子类来实现代理。

  2. 性能表现: JDK动态代理因为需要实现目标类接口,所以它的性能相对较低,但是它的应用场景更为广泛,适用于大多数情况下的代理需求。 CGLIB代理则因为不需要实现目标类接口,所以它的性能相对较高,但是它不能代理final类和final方法,以及一些无法生成子类的类。

  3. 应用场景: JDK动态代理适用于代理接口的场景,例如Spring中的事务处理、日志记录等。 CGLIB代理适用于代理类的场景,例如Spring中的AOP切面编程等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2067603.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

增材制造(3D打印):为何备受制造业瞩目?

在科技浪潮的推动下&#xff0c;增材制造——即3D打印技术&#xff0c;正逐步成为制造业领域的璀璨新星&#xff0c;吸引了航空航天、汽车、家电、电子等众多行业的目光。那么&#xff0c;是什么让3D打印技术如此引人注目并广泛应用于制造领域&#xff1f;其背后的核心优势又是…

VSCODE SSH连接失败

前提&#xff1a;以前连接得好好的 突然有一天就连接不上了 打开C盘下的known_hosts文件删除如下内容&#xff0c;重新登陆即可

天正如何保存低版本

打开天正cad的界面。左边找到文件布图这个菜单&#xff0c;点击进入找到图形导出这个子菜单&#xff0c;之后会出现下面这一界面。 第2步 可以看到保存类型&#xff0c;一进去是天正3文件的&#xff0c;这时候你要点开下拉选择天正6文件&#xff0c;其它可以不用修o改&#x…

Keepalived和Nginx一起在Centos7上实现Nginx高可用设计

方案概览 如需详细信息可点击下列链接进行视频观看 B站 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP 抖音 7分钟弄懂啥是高可用基石-VIP从零开始实操VIP Centos7 yum更新 安装阿里yum源 wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Cent…

TCP/UDP的对比,粘包分包抓包,http协议

服务器端&#xff1a; 一、loop 127.0.0.1本地回环测试地址 二、tcp特点 面向连接、可靠传输、字节流 粘包问题&#xff1a;tcp流式套接字&#xff0c;数据与数据之间没有套接字&#xff0c;导致可能多次的数据粘到一起 解决方法&#xff1a;&#xff08;1&#xff09;规…

Linux数据相关第1个服务_备份服务rsync

1、备份服务概述 备份服务&#xff1a;需要使用到脚本&#xff0c;打包备份&#xff0c;定时任务 备份服务&#xff1a;rsyncd 服务&#xff0c;不同主机之间数据传输 特点: rsync是个服务也是命令使用方便&#xff0c;具有多种模式传输数据的时候是增量传输 增量与全量&am…

Nginx: 配置项之http模块connection和request的用法以及limit_conn和limit_req模块

connection和request connection 就是一个连接, TCP连接 客户端和服务器想要进行通信的话&#xff0c;有很多种方式比如说, TCP的形式或者是UDP形式的通常很多应用都是建立在这个TCP之上的所以, 客户端和服务器通信&#xff0c;使用了TCP协议的话&#xff0c;必然涉及建立TCP连…

一分钟告诉你毕业季大学都在用在线版招生简章是如何制作?

毕业季临近&#xff0c;各大高校纷纷进入招生宣传的关键时期。在数字化时代背景下&#xff0c;在线版招生简章成为了高校之间竞争的焦点。一分钟带你了解&#xff0c;这些吸引眼球的在线版招生简章是如何制作出来的。 1. 准备好制作工具&#xff1a;FLBOOK在线制作电子杂志平台…

【论文分享】Graviton: Trusted Execution Environments on GPUs 2018’OSDI

目录 AbstractIntroductioncontributions BackgroundGPUSoftware stackHardwareContext and channel managementCommand submissionProgramming modelInitializationMemory allocationHost-GPU transfersKernel dispatch Sharing Intel SGX Threat ModelOverviewGraviton Archi…

World of Warcraft [CLASSIC] Engineering 335-420

World of Warcraft [CLASSIC] Engineering 工程学冲技能点 335 - 420 [冰霜冲击雷管] 335-345 [冰霜手雷] 346-358 这部分知道可以不看了 在地狱火半岛&#xff0c;萨尔玛&#xff0c;找70级工程学大师学习新的技能&#xff0c;用来充技能都不划算 回【达拉然】找80级工程…

【电子数据取证】提升案件分析准确性的去重技术

前言 紧随《AES解密侵犯隐私案件数据》一文的讨论&#xff0c;本文将深入探讨数据解密后的处理工作。解密只是数据恢复的第一步&#xff0c;确保数据的准确性和分析的有效性同样重要。本文将重点介绍数据去重技术&#xff0c;阐述在解密数据后如何细致地进行去重处理&#xff…

wx.choosemedia 无反应 不生效 不弹出图片视频

调整开发者工具基础版本&#xff1a;打开微信开发者工具 > 右上方点击详情 > 本地设置 >调试基础库&#xff08;更换版本&#xff09; 这里我试了几个&#xff0c;发现 3 开头的版本都不行。。最后选了 2.30.4版本&#xff0c;官方文档给的答案是 2.10.0 开始支持 。…

特殊管道资源采购

管道资源物料是从管道&#xff08;如输油管&#xff09;或其它线管&#xff08;如输电线&#xff09;中直接进入生产流程的物料。与寄售物料不同的是&#xff0c;管道资源物料不必一定经过“采购”获得。管道库存不是在仓库中实际可用的库存。管道中的物料始终可用&#xff0c;…

Datawhale X 李宏毅苹果书 AI夏令营-深度学习入门班-task1

机器学习就是去拟合一种函数&#xff0c;它可能在高维上&#xff0c;十分抽象&#xff0c;但是却可以有丰富的语义含义&#xff0c;从而完成一系列任务 回归任务是预测一个准确的值&#xff0c;例如拟合一条直线的时候&#xff0c;我们希望每一个点的值都能对应上 分类任务则…

JAVA-抽象类和抽象方法

目录 一、抽象类的概念 二、抽象类语法 三、抽象类特性规则 四、抽象类的作用 一、抽象类的概念 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类中没有包含足够…

宠物空气净化器是否是智商税?性价比高的宠物空气净化器十大排名

我开着一家猫咪咖啡馆&#xff0c;我们店貌美小猫可没少给我带来回头客~先给大家看看我的招财猫们 我的猫咪咖啡馆已经运营了三年了&#xff0c;直到最近才迎来了盈利的曙光。初期&#xff0c;面对种种困难与挑战&#xff0c;特别是频繁的投诉&#xff0c;几乎让我陷入了经营困…

IO进程线程8月23日

1&#xff0c;思维导图 2&#xff0c;创建子父 #include<myhead.h> int main(int argc, const char *argv[]) {pid_t pid;pidfork();if(pid>0){int fp3open("./3.txt",O_RDONLY);int fp4open("./4.txt",O_CREAT|O_TRUNC|O_WRONLY,0664);char str…

gin快速入门

gin 项目地址晓智科技晓智科技晓智文档晓智文档文档源码文档源码 快速体验 func HandlerPong(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",}) }func main() {r : gin.Default()r.GET("/ping", HandlerPong)_ r.Run(&qu…

JUnit 5和Mockito进行单元测试!

1. JUnit 5 基础 JUnit 5是最新的JUnit版本&#xff0c;它引入了许多新特性&#xff0c;包括更灵活的测试实例生命周期、参数化测试、更丰富的断言和假设等。 1.1 基本注解 Test&#xff1a;标记一个方法为测试方法。 BeforeEach&#xff1a;在每个测试方法之前执行。 AfterEac…

69 H3C SecPath F1000 (系统模块介绍-2)

28-IRF 特性简介 IRF&#xff08;Intelligent Resilient Framework&#xff0c;智能弹性架构&#xff09;是一种软件虚拟化技术。它的核心思想是将多台设备连接在一起&#xff0c;进行必要的配置后&#xff0c;虚拟化成一台设备。使用这种虚拟化技术可以集合多台设备的硬件资源…