设计模式——结构型模式——代理模式(静态代理、动态代理:JDK、CGLIB)

目录

代理模式

代理模式简介

代理模式的分类

代理模式组成

代理模式的优缺点

静态代理

背景前置

编写代码

JDK动态代理

编写代码

使用Arthas分析JDK动态代理底层原理

CGLIB动态代理

编写代码

三种代理的对比

 代理模式使用场景

代理模式

代理模式简介

        代理模式属于结构型模式。指一个对象本身不做实际的操作,而是通过其他对象来获取自己想要的结果。       

        产生背景:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

        意义:目标对象只需要关注自己的实现细节,通过代理来实现功能的增强,可以扩展目标对象的功能。同时体现了非常重要的变成模式,不能随便修改目标对象的源码,如果需要修改目标对象的源码对已有功能进行增强,此时可以通过修改代理的方式实现功能的扩展。

        例子:如果某人需要租房,此时可以借助于房屋中介或租赁公司,此时房屋中介或租赁公司相当于代理,可以让租房人找到适合自己的房屋。

代理模式的分类

        静态代理:静态代理就是在程序运行之前,代理类字节码.class就已编译好,通常一个静态代理类也只代理一个目标类,代理类和目标类都实现相同的接口。

        动态代理:动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。

代理模式组成

        代理(Proxy)模式可以分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式的优缺点

        优点:保护、扩展、降低耦合度。

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

  • 代理对象可以扩展目标对象的功能;

  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

        缺点:增加系统的复杂性。

静态代理

背景前置

        火车站卖票:如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:

编写代码

        如上面背景所示,我们需要定义①抽象主题类:买票接口类。②真实主题类:代理的目标类(火车站)。③代理类:代售点,以实现对真实主题类功能的增强。

/**
 * 声明抽象类、定义公共方法
 */
public interface SallTicket {
    void sale();
}


/**
* 火车站类,实现买票接口
*/
public class TrainStation implements SallTicket{
    @Override
    public void sale() {
        System.out.println("火车站正在卖票...");
    }
}


/**
* 代理类:与目标类实现相同的接口,以达到对目标类方法的增强
*/

public class SallProxy implements SallTicket{
    private TrainStation trainStation;
    public SallProxy(TrainStation trainStation){
        this.trainStation = trainStation;
    }
    @Override
    public void sale() {
        System.out.println("代售点收取少量服务费...");
        trainStation.sale();
    }
}

/**
 * 编写客户端测试类
 */
public class Client {
    public static void main(String[] args) {
        SallProxy sallProxy = new SallProxy(new TrainStation());
        sallProxy.sale();
    }
}

接口、目标类、代理类之间的关系图:

  • 接口类SallTicket:定义了卖票的抽象公共方法sale()。
  • 目标类TrainStation:实现了接口类SallTicket,并重写方法sale()。
  • 代理类SallProxy:实现接口类SallTicket,且在方法内部传递了接口类的实现类也即目标类TrainStation。在代理类内部实现了原有卖票方法sale()的功能的增强,并且调用目标类TrainStation中的sale()方法达到了卖票的功能。
  • 测试类Client:构建代理类,并实现功能的验证。

JDK动态代理

编写代码

        JDK动态代理和静态代理不同的地方在于代理类的编写不同,因此接口类、目标类仍然沿用静态代理中创建的,编写JDK动态代理程序结构如下:

        其中代理类(ProxyFactory)和测试类(Client)代码如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
 * 获取代理对象的工厂类
 * 代理类也实现了对应的接口
 */
public class ProxyFactory {

    // 声明目标对象
    private TrainStation station = new TrainStation();

    // 获取代理对象的方法
    public SallTicket getProxyObject(){
        // 返回代理对象、代理对象也实现了目标接口
        /**
         * ClassLoader loader, 类加载器,用于加载代理类,可以通过目标对象获取类加载器
         * Class<?>[] interfaces, 代理类实现的接口的字节码对象
         * InvocationHandler h, 代理对象的调用处理程序
         */
        SallTicket proxyObject = (SallTicket) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(), // 目标对象和代理类实现了相同的接口,可以通过目标对象获取代理类实现的接口的字节码对象
                new InvocationHandler() {   // 匿名内部类
                    /**
                     * @param proxy 代理对象,就是proxyObject
                     * @param method 对接口中的方法进行封装的method对象
                     * @param args 调用方法的实际参数
                     * @return  返回值就是方法的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 对目标对象进行方法的增强
                        System.out.println("代售点收取一定的服务费用(JDK)...");
                        // 执行目标对象的方法
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}


/**
 * 客户端,测试JDK动态代理对方法的增强
 */
public class Client {
    public static void main(String[] args) {
        // 创建代理对象工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 使用proxyFactory对象的方法获取代理对象
        SallTicket proxyObject = proxyFactory.getProxyObject();
        // 调用目标对象的方法
        proxyObject.sale();
    }
}

       接口、目标类、代理类之间的关系图: 

        上图中的ProxyFactory类并不是真正的JDK动态代理类,其本质上是一个代理工厂,通过Proxy类的静态方法newProxyInstance(...),传入目标类的字节码,代理类实现的接口,invocationHandler接口的实现内部类等参数在程序运行的时候动态的创建代理类,并且借助反射获取代理对象的方法,并且实现对目标方法的增强。

使用Arthas分析JDK动态代理底层原理

        在测试类Client中添加以下代码用于获取代理类的Class对象。

        随后借助于阿里开源工具Arthas,使用反编译命令jad com.sun.proxy.$Proxy0获取已加载到 JVM 中的类的源代码如下:

package com.sun.proxy;

import com.itheima.proxy.dynamic.jdk.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

从上面的类中,我们可以看到以下几个信息:

  • 代理类($Proxy0)实现了SellTickets。

  • 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。

        可以分析代理类执行流程如下:

  1. 在测试类中通过代理对象调用sell()方法。
  2. 根据多态的特性,执行的是代理类($Proxy0)中的sall()方法。
  3. 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法。
  4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sall()方法。

CGLIB动态代理

编写代码

        CGLIB动态代理和静态代理不同的地方在于代理类的编写不同,并且不需要接口类,因此目标类仍然沿用静态代理中创建的,编写CGLIB动态代理程序结构如下:

        CGLIB是第三方提供的包,编写代码之前需要先引入jar包。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

/**
 * 代理工厂
 */
public class ProxyFactory implements MethodInterceptor {
    private TrainStation station = new TrainStation();

    // 获取代理对象
    public TrainStation getProxyObject(){
        // 创建Enhancer对象,类似于JDK动态代理的Proxy类
        Enhancer enhancer = new Enhancer();
        // 设置父类的字节码对象
        enhancer.setSuperclass(station.getClass());
        // 设置回调函数,传递的对象,是MethodInterceptor的子实现类对象
        enhancer.setCallback(this);
        // 创建代理对象
        TrainStation proxyObject = (TrainStation) enhancer.create();
        return proxyObject;
    }

    /**
     * @param o      代理对象
     * @param method 真实对象中的方法的Method实例
     * @param objects  实际参数
     * @param methodProxy 代理对象中的方法的method实例
     * @return 代理对象方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
        TrainStation result = (TrainStation) method.invoke(station, objects);
        return result;
    }
}


/**
 * 客户端
 */
public class Client {
    public static void main(String[] args) {
        // 创建代理对象工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        // 获取代理对象
        TrainStation proxyObject = proxyFactory.getProxyObject();
        proxyObject.sale();
    }
}

         接口、目标类、代理类之间的关系图:

        简单原理:通过自定义实现拦截器接口(MethodInterceptor)的类【也就是目标类】,并重写intercept()用于拦截增强被代理类的方法【类似于JDK动态代理中的invoke()方法】。通过Enhancer 类的 create()创建简单的代理类。

        CGLIB采用非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术的拦截所有父类的方法调用,顺势织入横切逻辑。(CGLIB在字节码的基础上,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

三种代理的对比

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。

  • JDK动态代理只能代理实现了接口的类,而CGLIB可以代理未实现任何接口的类。
  • JDK动态代理是实现了被代理对象所实现的接口CGLIB是继承了被代理对象。
  • JDK和CGLIB都是在运行期生成字节码,JDK是直接写Class字节码,CGLIB是使用ASM框架写Class字节码。
  • CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
  • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

 代理模式使用场景

        此部分内容暂时不是很熟悉,等以后学习到再慢慢补充。

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据可视化第五天(读取文件获得男生女生身高信息,并且可视化在一个图像)

文件 需要学生文件的可以私信我 过程 利用numpy的loadtxt文件读取学号&#xff0c;性别&#xff0c;和身高。 import numpy as np import matplotlib.pyplot as pltfilename/Users/oommnn/Desktop/python学习/数据分析/网课资料/第04天/student-data.txtuser_infonp.dtype(…

智慧公厕:数据驱动的公共厕所智慧化管理

公共厕所作为城市基础设施的重要组成部分&#xff0c;对于城市居民的生活质量和城市形象有着不可忽视的影响。然而&#xff0c;传统的公共厕所管理模式存在诸多问题&#xff0c;如设施老化、卫生状况不佳等&#xff0c;严重限制了公众对于公共厕所的使用体验。随着大数据和智能…

EDA设计学习笔记2:STM32F103C8T6最小系统板的仿绘

今日开始仿制练习一个STM32F103C8T6最小系统板&#xff0c;通过对这个最小系统板的仿制&#xff0c;达到对自己PCB设计的练习的目的&#xff0c;最终目标是自己设计出一块PCB&#xff0c;做一个OLED的桌面小摆件...... 也不知道画出来能不能用..... 目录 主控芯片的搜索与放置…

proteus示波器不弹出来

运行后示波器没有弹出来 点击调试&#xff08;Debug&#xff09;在点击Digital Oscilloscope 完成

SamFirm Reborn 0.3.6.8三星固件下载器 汉化版

介绍 在三星手机的维护和升级过程中&#xff0c;固件的获取往往成为了一个难题。幸运的是&#xff0c;有一群热爱技术的开发者们&#xff0c;他们开发了各种工具以简化这个过程。今天&#xff0c;我们要介绍的是一款名为SamFirm Reborn 0.3.6.8的三星固件下载器的汉化版。它旨在…

目前公认的一些好用AI工具

ChatGPT&#xff1a;这是一个广泛推荐和使用的AI聊天机器人&#xff0c;能够进行高效的文本生成和对话管理。它在多个证据中被提及&#xff0c;显示其普遍性和实用性。 Midjourney&#xff1a;这是一款强大的AI绘画工具&#xff0c;特别适合创意写作和图像生成。它在多个来源中…

mysql中的页和行

页 行即表中的真实行&#xff0c;‘行式数据库’的由来 虽然MySQL的数据文件&#xff08;例如.ibd文件&#xff09;中的数据页在物理上是通过链表连接的&#xff0c;但是在逻辑上&#xff0c;MySQL使用B树来组织和访问数据。 行&#xff1a;主要是dynamic类型

python 使用 MQTT

目录结构 1、py代码 offRelay12-yixing.py # _*_ coding: utf-8 _*_ # 须用到第三方库&#xff1a;paho-mqtt # 安装命令 python3 -m pip install paho-mqttimport time import json import paho.mqtt.client as mqtt# 函数&#xff1a;关闭所有房间的12路继电器模块上指定的…

正方形中的最多点数

代码实现&#xff1a; 方法一&#xff1a;遍历——超时 int maxPointsInsideSquare(int **points, int pointsSize, int *pointsColSize, char *s) {int a 0;int flag 1;int num, pre_num 0;while (flag) {num pre_num;pre_num 0;int hash[26] {0};for (int i 0; i <…

通过linux花里胡哨的控制台,学习linux基础命令

今天这个B我装定了&#xff01; 前言命令集 开始1、cowsay &#xff08;让牛说话&#xff0c;够无聊的&#xff0c;但牛说的话是你输入的&#xff0c;细思极恐&#xff01;&#xff09;Debian/Ubuntu 安装命令&#xff1a;RHEL/CentOS/Fedora 安装&#xff1a;运行解释 2、fort…

基于51单片机的多路温度检测调节串口传输系统

基于51单片机的多路温度检测调节 &#xff08;仿真&#xff0b;程序&#xff09; 功能介绍 具体功能&#xff1a; 1.采用四个DS18B20温度传感器测温,LCD1602显示四路温度值; 2.三个按键可以设置温度上下限; 3.当每路温度值超过设定的上或下限时,蜂鸣器报警,对应的指示灯亮;…

代数结构:5、格与布尔代数

16.1 偏序与格 偏序集&#xff1a;设P是集合&#xff0c;P上的二元关系“≤”满足以下三个条件&#xff0c;则称“≤”是P上的偏序关系&#xff08;或部分序关系&#xff09; &#xff08;1&#xff09;自反性&#xff1a;a≤a&#xff0c;∀a∈P&#xff1b; &#xff08;2…

【刷题篇】滑动窗口(二)

文章目录 1、水果成篮2、找到字符串中所有字母异位词3、串联所有单词的子串4、最小覆盖子串 1、水果成篮 你正在探访一家农场&#xff0c;农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示&#xff0c;其中 fruits[i] 是第 i 棵树上的水果 种类 。 你想要尽可能多…

景源畅信:抖音小店的商品怎么同步到橱窗?

在数字营销的海洋中&#xff0c;抖音小店与橱窗的同步操作无疑是商家们关注的焦点。这不仅能增加商品的曝光度&#xff0c;还能提高交易的可能性。那么&#xff0c;如何将抖音小店的商品同步到橱窗呢? 一、核心步骤解析 要实现商品从抖音小店同步到橱窗&#xff0c;你需要确保…

程序员工作中常见问题,你遇到过几个?

在赛博朋克2077玩后感中&#xff0c;我提到&#xff0c;即便是在严谨的机制下&#xff0c;依然可能出现让人匪夷所思或是贻笑大方的问题。 那么今天&#xff0c;就以后端程序员的视角&#xff0c;盘点下从设计开发到上线的常见问题&#xff0c;看看大家中过几个。 01 设计与开…

栈和队列讲解

文章目录 栈栈的实现栈的初始化压栈出栈获取栈顶元素获取栈内有效元素个数检查是否为空销毁栈栈的使用 栈全部代码队列的初始化队尾入队列队头出队列获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果…

Java设计模式-工厂

Java设计模式中&#xff0c;工厂模式主要包括普通工厂模式以及抽象工厂模式&#xff0c;普通工厂模式是用于制造输出不同类型的对象&#xff0c;抽象工厂模式是用于制造输出不同类型的普通工厂&#xff0c;本文主要描述工厂模式的基本用法。 如上所示&#xff0c;使用普通工厂模…

HCIP(BGP综合实验)--8

一&#xff1a;实验要求 二&#xff1a;实现过程 &#xff08;一&#xff09;配置IP地址&#xff1a; AR1: [AR1]int g0/0/0 [AR1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [AR1-GigabitEthernet0/0/0]int l0 [AR1-LoopBack0]ip add 172.16.0.1 32 [AR1-LoopBack0]int l1 […

C++异常详解

文章目录 前言一、回顾C语言二、异常的概念三、异常的使用1.异常的抛出和捕获2.异常的重新捕获 三.异常安全与异常规范1.异常安全2.异常规范 四.自定义异常体系五.C标准库的异常体系六.异常优缺点练习题总结 前言 在本篇文章中&#xff0c;我们将会详细介绍一下有关C异常的讲解…

Redis数据结构扩容源码分析

1 Redis数据结构 redis的数据存储在dict.中&#xff0c;其数据结构为(c源码) ypedef struct dict { dictType *type; //理解为面向对象思想&#xff0c;为支持不同的数据类型对应dictType抽象方法&#xff0c;不同的数据类型可以不同实现 void *privdata; //也可不同的数据类…