Java设计模式之结构型-代理模式(UML类图+案例分析)

目录

一、基础概念

二、UML类图

1、静态代理类图

2、动态代理类图

三、角色设计

四、案例分析

1、静态代理

2、JDK动态代理 

3、Cglib动态代理

五、总结


一、基础概念

代理模式是一种结构型设计模式,它用一个代理对象来封装一个目标对象,通常是为了对目标对象的访问进行增强或控制。主要作用是扩展目标对象的功能,比如延迟加载、访问控制、远程访问和日志记录等。

二、UML类图

1、静态代理类图

2、动态代理类图

三、角色设计

角色描述
抽象主题(Subject)角色声明实体和代理的共同接口
实体(Real Subject)角色定义代理所代表的实体
代理(Proxy)角色含有对实体引用,从而可以操作实体,代理实体行为控制访问
客户(Client)角色通过代理间接地访问实体

四、案例分析

1、静态代理

静态代理在程序编译期间就已经存在代理类的情况下产生代理关系,这种代理关系在运行前就已经确定。

其具体实现方式如下:

1、定义一个接口及其实现类,作为被代理对象。

2、创建一个代理类,实现与原对象相同的接口。

3、在代理类中维护一个被代理对象的引用。

4、代理类中的方法实现,会调用被代理对象中的对应方法,并在前后添加其他处理。

5、客户端通过代理类访问被代理对象。

静态代理的特点:

1、代理类和被代理类实现相同的接口。

2、代理关系在程序运行前就确定了。

3、编译时生成代理类,执行效率高。

4、但不易维护,如果接口增加方法、代理类和被代理类都要修改。

5、灵活性较差,代理类只能为一个被代理类服务。

静态代理模式可以解决一些简单的代码封装、容错、访问控制等问题,但灵活性和复用性较差。

我们通过一个简单的的Demo来实现静态代理,代码如下:

定义一个图片接口(抽象主题角色),默认包含一个显示图片的方法:

interface Image {

  void display();

}

定义具体的图片实体(实体角色)去实现这个接口:

public class RealImage implements Image {

  @Override 
  public void display() {
    System.out.println("显示图片");
  }

}

定义一个图片代理类(代理角色):

public class ProxyImage implements Image{

  private RealImage image;
  
  public ProxyImage() {
    image = new RealImage();
  }
  
  @Override
  public void display() {
    System.out.println("代理开始...");
    image.display();
    System.out.println("代理结束...");
  }

}

客户端使用:

public class Client {

    public static void main(String[] args) {
        ProxyImage proxyImage = new ProxyImage();
        proxyImage.display();
    }
}

运行结果如下:

2、JDK动态代理 

JDK动态代理是在程序运行期间通过反射机制动态创建代理类及其对象,从而实现对目标对象的代理。动态代理的实现步骤:

1、定义一个接口及其实现类,作为被代理对象。

2、创建一个代理类,该类实现InvocationHandler接口。

3、实现invoke方法,调用被代理对象的方法,并在前后添加其他处理。

4、使用Proxy类的newProxyInstance方法生成被代理对象的代理类。

5、客户端通过代理类访问被代理对象的方法。

动态代理的特点:

1、代理类的生成时机是在程序运行期间,不需要实现接口。

2、动态代理类可以服务任何接口实现类,灵活性强。

3、使用反射机制,执行效率较低。

4、可以动态改变代理的逻辑实现。

动态代理通过在运行时动态创建代理,更加灵活,可以按需实现代理类,避免了代理类膨胀问题,但执行效率相对较低。

下面我们提供一个Demo案例来实现,代码如下:

定义一个Hello接口,并提供默认的sayHello方法:

public interface IHello {

  void sayHello();

}

定义一个具体的Hello实体去实现接口并重写sayHello方法: 

public class Hello implements IHello {

  @Override
  public void sayHello() {
    System.out.println("Hello World!");
  }

}

创建动态代理类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 动态代理类
public class DynamicProxy {

    // 维护目标对象
    private Object target;

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

    //给目标对象动态生成一个代理对象
    public Object getProxyInstance() {
        //利用反射里面一个类Proxy调用newProxyInstance方法
        /*
         * 这个方法三个参数
         * 1.ClassLoader loader:当前目标对象的类加载器
         * 2.Class<?>[] interface:目标对象实现的接口,使用泛型方法确认类型
         * 3.InvocationHandler h:事情处理,执行目标对象方法时,会触发
         * 事情处理器方法
         * */
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理开始...");
                        Object res = method.invoke(target, args);
                        System.out.println("代理结束...");
                        return res;
                    }
                });
    }

}

客户端使用:

public class Client {

    public static void main(String[] args) {
        IHello target = new Hello();
        IHello proxyInstance = (IHello)new DynamicProxy(target).getProxyInstance();
        proxyInstance.sayHello();
    }
}

运行结果如下:

3、Cglib动态代理

Cglib代理和JDK动态代理的区别如下:

1、JDK动态代理只能代理实现了接口的类,而Cglib可以代理未实现任何接口的类。

2、JDK动态代理是通过反射机制生成一个实现了代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而Cglib动态代理是通过生成目标类的子类来实现的。

3、JDK动态代理效率略低于Cglib动态代理,因为Cglib是通过修改字节码生成子类来避免反射调用。

4、JDK动态代理只需实现InvocationHandler接口,Cglib需要实现MethodInterceptor接口。

5、Cglib需要依赖cglib库,JDK动态代理是Java自带的。

6、JDK动态代理可直接代理标识了接口的类,Cglib可代理未标识接口的类。

如果要代理的类实现了接口可以直接用JDK动态代理,如果要代理的类没有接口则需要用Cglib。Cglib通过生成子类提高了效率,但也更复杂。在非必要时建议使用JDK动态代理。二者思想都是一样的,都是在运行时动态生成代理类。

这里我搭建了个SpringBoot项目,Spring里面集成了对应Jar包,主要代理类这边进行了变动,代码如下:

package com.example.api;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

//代理对象,Cglib代理(和jdk动态代理不同的是目标对象不需要实现接口)
public class DynamicProxy implements MethodInterceptor {

	//维护一个目标对象
	private Object target;
	
	//构造器,传入一个被代理的对象
	public DynamicProxy(Object target) {
		this.target = target;
	}

	//返回一个代理对象:是 target 对象的代理对象
	public Object getProxyInstance() {
		//1. 创建一个工具类
		Enhancer enhancer = new Enhancer();
		//2. 设置父类
		enhancer.setSuperclass(target.getClass());
		//3. 设置回调函数
		enhancer.setCallback(this);
		//4. 创建子类对象,即代理对象
		return enhancer.create();
		
	}
	

	//重写intercept方法,会调用目标对象的方法
	@Override
	public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
		System.out.println("Cglib代理模式开始...");
		Object returnVal = method.invoke(target, args);
		System.out.println("Cglib代理模式结束...");
		return returnVal;
	}
}

客户端使用: 

@SpringBootTest
class ApiApplicationTests {

    @Test
    void contextLoads() {
        //创建目标对象
        IHello target = new Hello();
        //获取到代理对象,并且将目标对象传递给代理对象
        Hello proxyInstance = (Hello) new DynamicProxy(target).getProxyInstance();
        //执行代理对象的方法,触发intecept 方法,从而实现对目标对象的调用
        proxyInstance.sayHello();
    }

}

运行结果如图:

五、总结

优点:

1、代理对象可以在目标对象操作前后增加额外功能,它像一个中介一样在目标对象和客户端之间起作用。

2、代理对象可以控制对目标对象的访问,保护目标对象的安全。

3、使用代理对象可以简化客户端代码,客户端只需要跟代理对象交互。

4、可以根据需要创建不同的代理类,扩展软件系统的灵活性。

缺点:

1、会增加系统的复杂度,需要新增代理类。

2、动态代理的效率可能比较低。

应用场景:

1、远程服务代理:本地代理代表远程服务对象,方便访问远程服务。

2、日志记录代理:代理对象在调用目标对象方法前后记录日志。

3、权限验证代理:代理检查客户端权限后再调用目标对象方法。

4、缓存代理:代理先检查缓存,缓存没命中再访问目标对象。

5、延迟加载代理:代理可以在需要时才创建目标对象。

符合的设计原则:

1、单一职责原则(Single Responsibility Principle)

代理类只负责代理的工作,目标类只负责业务自身的工作,二者职责明确,符合单一职责原则。

2、开闭原则(Open Closed Principle)

代理类和目标类均实现了相同的接口,对接口进行编程,扩展代理类不影响目标类,符合开闭原则。

3、依赖倒转原则(Dependence Inversion Principle)

抽象接口不依赖具体实现类,具体类依赖抽象接口,符合依赖倒转原则。

4、组合复用原则(Composite Reuse Principle)

代理类中维护对目标对象的引用,与目标对象建立聚合关系,符合组合复用原则。

5、迪米特法则(Law of Demeter)

代理类只与目标类交互,符合迪米特法则中的最少知道原则。

综上,代理模式通过建立中间代理层主要用于控制、增强对目标对象的访问,但也会增加系统复杂度。

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

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

相关文章

NP问题的通俗解释

本博客参考&#xff1a; https://zhuanlan.zhihu.com/p/348250098https://zhuanlan.zhihu.com/p/348020672https://zhuanlan.zhihu.com/p/260512272以及相关的1-6。 是什么 NP的全称是Non Deteministic Polynomial&#xff0c;是线性所不能判别的问题的集合。 NP这个东西是…

使用RabbitMQ

使用RabbitMQ 1 Docker安装RabbitMQ 1.1 安装RabbitMQ # 下载含有管理页面的镜像 docker pull rabbitmq:3.8.8-management# 创建容器 # 5672&#xff1a;应用访问端口&#xff1b;15672&#xff1a;控制台Web端口号&#xff1b; docker run -itd \ --namemy-rabbitmq \ --re…

第六章:YOLO v1网络详解(统一的实时目标检测)

(目标检测篇&#xff09;系列文章目录 第一章:R-CNN网络详解 第二章:Fast R-CNN网络详解 第三章:Faster R-CNN网络详解 第四章:SSD网络详解 第五章:Mask R-CNN网络详解 第六章:YOLO v1网络详解 第七章:YOLO v2网络详解 第八章:YOLO v3网络详解 文章目录 系列文章目录技…

记录一个heatmap.js在strict模式下的bug

ImageData的data属性只读&#xff0c;无法修改 出问题的在原始代码的490行~528行 var img this.shadowCtx.getImageData(x, y, width, height);var imgData img.data;var len imgData.length;var palette this._palette;for (var i 3; i < len; i 4) {var alpha imgD…

springboot项目中引入本地依赖jar包,并打包到lib文件夹中

1.springboot项目中引入本地依赖jar包&#xff0c;并打包到lib文件夹中 描述&#xff1a;下载了第三方相关jar包后&#xff0c;项目中引入本地jar&#xff0c;测试环境正常&#xff0c;打包线上报错提示为找到该jar 原因&#xff1a;应该在/WEB-INF/lib/xxx.jar&#xff0c;被…

基于深度学习的高精度刀具检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度刀具检测识别系统可用于日常生活中或野外来检测与定位刀具目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的刀具目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检测模型…

当你按下键盘A键

CPU 里面的内存接口&#xff0c;直接和系统总线通信&#xff0c;然后系统总线再接入一个 I/O 桥接器&#xff0c;这个 I/O 桥接器&#xff0c;另一边接入了内存总线&#xff0c;使得 CPU 和内存通信。再另一边&#xff0c;又接入了一个 I/O 总线&#xff0c;用来连接 I/O 设备&…

前端框架Layui的使用讲解(Layui搭建登录注册页面)

目录 一、前言 1.什么是Layui 2.Layui的背景 3.为什么要使用Layui 4.Layui的模块化 二、Layui使用讲解 1.初识Layui 2.搭建登录页面 静态效果图​ 封装引入文件页面&#xff08;公用页面&#xff09; jsp页面搭建 userDao编写 Servlet页面编写 xml文件配置 3.搭…

DAY41:贪心算法(十)监控二叉树

文章目录 968.监控二叉树思路遍历顺序空节点处理情况列举 最开始的写法debug测试&#xff1a;travelsal的输出多了1 修改版二叉树注意点时间复杂度总结 968.监控二叉树 给定一个二叉树&#xff0c;我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及…

​python接口自动化(三十一)--html测试报告通过邮件发出去——下(详解)​

简介  本篇总结了 QQ &#xff08;SSL&#xff09;邮箱和 163&#xff08;非SSL&#xff09; 邮箱发送邮件&#xff0c;专治各种不行&#xff0c;总之看完这篇以后麻麻再也不用担心我的邮件收不到了。以下代码兼容 python2 和 python3&#xff0c;运行无异常&#xff0c;放心大…

语义分割大模型SAM论文阅读(二)

论文链接 Segment Anything 开源代码链接 SAM 论文阅读 摘要 We introduce the Segment Anything (SA) project: a new task, model, and dataset for image segmentation. Using our efficient model in a data collection loop, we built the largest segmentation dat…

Vue数据项加圆点

目录 Html 样式 方法 Html <el-table-column prop"status" label"数据状态" header-align"center" width"200"><template slot-scope"scope"><div style"display: flex; justify-content: center; a…

fun函数方法体=返回值,kotlin

fun函数方法体返回值&#xff0c;kotlin var str: String "fly"fun main(args: Array<String>) {println(getMyString())println(getMyInt())str "phil"println(getMyString())println(getMyInt()) }fun getMyInt(): Int {return if (str.equals(&…

使用OpenCV在图像上绘制质心

这段代码中已经实现了在图像上绘制质心的功能。质心,也称为重心,是物体质量分布的几何中心,可以通过物体质量和位置的加权平均来求得。 在这个程序中,图像的质心(重心)是通过计算像素强度(可以被看作是“质量”)的加权平均位置得到的。图像上每一个像素都有一个位置(…

搭建SpringBoot项目 详细教程

一、搭建SpringBoot项目 这个项目&#xff0c;可以作为种子项目&#xff0c;我打算把它放置Gitee上。包含大部分web开发的相关功能&#xff0c;后期所有的Spring Boot项目都可以用这个项目&#xff0c;简单修改一下配置&#xff0c;就可以快速开发了。 选择Spring initializr…

【Java】链表LinkedList

文章目录 一、链表1.1 链表的概念1.2 链表的结构 二、LinkedList的简介三、LinkedList的使用3.1 构造方法3.2 常见操作3.3 遍历方法 四、LinkedList的模拟实现五、LinkedList 和 ArrayList 的区别 一、链表 1.1 链表的概念 链表&#xff08;Linked List&#xff09;是一种常见…

预付费智能水表远程控制系统

预付费智能水表远程控制系统是一种基于物联网技术的智能水表管理系统&#xff0c;它通过远程通信技术和云计算平台&#xff0c;实现了对水表的实时监控、数据采集、费用计算、远程控制等功能。该系统不仅可以提高水务公司的管理效率&#xff0c;还可以为用户提供更加便捷、可靠…

Todo-List案例版本二

(160条消息) Todo-List案例版本一_bubbleJessica的博客-CSDN博客 引入了localStorage&#xff0c;让案例更加完善 src/App.vue <template><div id"root"><div class"todo-container"><div class"todo-wrap"><MyHe…

emacs下相对行号的设置

全局设置 全局开启行号显示&#xff1a;global-display-line-numbers-mode t 并设置 display-line-numbers-type的样式: relative 相对 配置代码如下: (use-package emacs:ensure t:config (setq display-line-numbers-type relative) (global-display-line-numbers-mode t)…

TypeScript 学习笔记(三):函数

一、函数定义 函数是由一连串的子程序&#xff08;语句的集合&#xff09;所组成的&#xff0c;可以被外部程序调用&#xff0c;向函数传递参数之后&#xff0c;函数可以返回一定的值。 通常情况下&#xff0c;TypeScript 代码是自上而下执行的&#xff0c;不过函数体内部的代…
最新文章