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

目录

一、基础概念

二、UML类图

1、静态代理类图

2、动态代理类图

三、角色设计

四、案例分析

1、静态代理

2、JDK动态代理 

3、Cglib动态代理

五、总结


一、基础概念

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

二、UML类图

1、静态代理类图

2、动态代理类图

三、角色设计

角色描述
抽象主题角色定义了实际主题和代理对象必须实现的接口
实际主题角色实现了抽象主题中的业务逻辑,是代理对象所代表的实际对象
代理角色内部含有对实际主题的引用,从而可以操作实际主题对象;代理对象提供与实际主题相同的接口,其接口的实现方式可以在执行前后做一些额外的操作
客户端角色通过抽象主题角色访问实际主题角色,这样可以在任何时刻都用代理对象替换实际主题对象

四、案例分析

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();}@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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方法,会调用目标对象的方法@Overridepublic 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 {@Testvoid 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.hqwc.cn/news/21964.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python+docx实现python对word文档的编辑

前言&#xff1a; 该模块可以通过python代码来对word文档进行大批量的编辑。docx它提供了一组功能丰富的函数和方法&#xff0c;用于创建、修改和读取Word文档。下面是docx模块中一些常用的函数和方法的介绍&#xff1a; 安装&#xff1a;pip install docx 一、准备一个word文档…

MYSQL执行一条SELECT语句的具体流程

昨天CSDN突然抽风 我一个ctrlz把整篇文章给撤掉了还不能复原 直接心态崩了不想写了 不过这部分果然还是很重要,还是写出来吧 流程图 这里面总共有两层结构Server层 储存引擎 Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现&#xff0c;主要包…

WebSocket理解

WebSocket理解 WebSocket定义与HTTP关系相同点:不同点&#xff1a;联系总体过程 HTTP问题长轮询Ajax轮询 WebSocket特点 WebSocket 定义 本质上是TCP的协议 持久化的协议 实现了浏览器和服务器的全双工通信&#xff0c;能更好的节省服务器资源和带宽 与HTTP关系 相同点: 基于…

【bug】flameshot在ubuntu上的4K屏幕,双屏幕上用不了截图

问题 直接在4K屏幕上运行flameshot截图&#xff0c;直接黑屏 主屏 &#xff1a;4K 副屏&#xff1a;2k 解决 2.1长按1-2秒开机键&#xff0c;先回到桌面。 2.2 设置主屏缩放为125% 2.3 设置键盘快捷键命令为env QT_AUTO_SCREEN_SCALE_FACTOR1 flameshot gui 替代flameshot的…

zeppelin的hive使用

zeppelin的hive使用 配置项 default.driver org.apache.hive.jdbc.HiveDriver default.url jdbc:hive2://192.168.xxx.xxx:10000 default.user hiveHive使用&#xff1a;点击create new note Default Interpreter选择hive

动态表单实现原理

目录 动态表单是什么 动态表单的关键 前后端职责 数据库与表结构 功能实现与改进建议 动态表单是什么 静态表单是很常见&#xff0c;也是常规做法&#xff0c;其表单的结构是固定的&#xff0c;通常情况下一个表单对应数据库的一张表&#xff0c;表单中一个数据项对应数据表的一…

idea创建webapp文件夹

结果的图片&#xff1a; 第一步&#xff1a; file-》project structure 第二步&#xff1a; 修改路径&#xff0c;点击右侧“Deloyment descriptors”下面的笔进行修改。 // 增加了src\main // web修改为了webapp C:\Users\www12\Desktop\huwantiku2\src\main\webapp\WEB-IN…

【Java】JVM执行流程、类加载过程和垃圾回收机制

JVM执行流程执行引擎本地方法接口运行时数据区方法区堆虚拟机栈(线程私有)本地方法栈(线程私有)程序计数器(线程私有) 堆溢出问题类加载类加载的过程加载连接验证准备解析 初始化 双亲委派机制 垃圾回收死亡对象的判断算法引用计数算法可达性分析算法 垃圾回收的过程标记-清除算…

Linux常用命令——emacs命令

在线Linux命令查询工具 emacs 功能强大的全屏文本编辑器 补充说明 emacs命令是由GNU组织的创始人Richard Stallman开发的一个功能强大的全屏文本编辑器&#xff0c;它支持多种编程语言&#xff0c;具有很多优良的特性。有众多的系统管理员和软件开发者使用emacs。 语法 e…

C语言动态获取设备的网络接口名称和状态以及对应的IP地址

一、目的 在实际项目中需要获取设备的IP地址然后通过广播的形式通知局域网内的其他设备。 二、介绍 方法一 通过ioctl方式获取SIOCGIFADDR信息 /** C Program to Get IP Address*/ #include <stdio.h> #include <string.h> #include <sys/types.h> #includ…

【数学建模】统计分析方法

文章目录 1.回归分析2. 逻辑回归3. 聚类分析4. 判别分析5. 主成分分析6. 因子分析7. 对应分析 1.回归分析 数据量要多&#xff0c;样本总量n越大越好——>保证拟合效果更好&#xff0c;预测效果越好 一般n>40/45较好 方法 建立回归模型 yiβ0β1i……βkxkiεi 所估计的…

Unity 编辑器-查找所有未被使用的Prefab

需求 接到一个需求&#xff0c;将Res里所有特效相关的prefab检查一下&#xff0c;没有使用的移除。 分析 先拆解一下需求&#xff0c;如下 #mermaid-svg-YiTzyE1BvQ0ZTgLj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#merm…