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

news2024/12/28 10:27:17

目录

一、基础概念

二、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();
  }
  
  @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.coloradmin.cn/o/745663.html

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

相关文章

Java的运算符

运算符介绍 运算符是一种特殊的符号&#xff0c;用以表示数据的运算、赋值和比较等。 运算符介绍 算术运算符赋值运算符关系运算符 [比较运算符]逻辑运算符位运算符 [需要二进制基础]三元运算符 算术运算符 介绍 算术运算符是对数值类型的变量进行运算的&#xff0c;在 Ja…

学习记录——Transformer、ViT、Swin-Transformer、SegFormer、TopFormer、Seaformer

Transformer 2017 Computation and Language Google Self-Attention、Multi-Head Attention 位置编码 原理参考链接 ransformer网络结构&#xff1a; ViT 2020 ICLR 将transformer引入到cv领域 将输入图片224x224x3按照16x16x3大小的Patch进行划分&#xff0c;接着通过…

JAVA+Selenium最简单的处理登录弹窗的方式

在做自动化测试遇到需要处理登录弹窗情况&#xff0c;例如我的用户名为admin, 密码为admin, 那么想要登录http://10.10.168.1, 只需要使用以下链接访问即可立即登录, 并免除弹窗: http://账号:密码网址

sys V 进程间通信之共享内存

note 1.使用shmget将在内核创建一个共享内存区 2.使用shmctl才可以删除内核创建的共享内存区 3.使用shmat给当前进程开辟与内核共享内存对应的内存区p&#xff0c;用户态对p的操作会作用到内核共享内存区 code #include <stdio.h> #include <stdlib.h> #inclu…

ROS:Rviz中控制机器人模型运动

目录 一、Arbotix简介二、安装 Arbotix二、Arbotix使用案例2.1需求2.2流程2.2.1创建新功能包&#xff0c;准备机器人 urdf、xacro 文件2.2.2添加 Arbotix 配置文件2.2.3编写 launch 文件配置 Arbotix2.2.4启动 launch 文件并控制机器人模型运动 一、Arbotix简介 Arbotix 是一款…

Python 标签(便签贴)打印,直接连接打印机打印

前言&#xff1a;一开始试了好多打印方式&#xff0c;图片打印很糊&#xff0c;docx文件打印效果最好&#xff0c;但是每次打印会打开Word 影响效率&#xff0c;PDF也会打开PDF软件&#xff0c;打印效果略差于docx文件&#xff0c;TXT文件又不能设置字体&#xff0c;找了好久&a…

8 Surprising Things You Can Do With ChatGPT 你可以用 ChatGPT 做的 8 件令人惊讶的事情

If you’ve heard about ChatGPT and think it’s just a fancy chatbot, you might be underestimating the range of what it can do. Here are some surprising things you can do with ChatGPT, whether you want to write a resume or have it dungeon-master an epic rol…

第二章(二):Django框架Model之ORM操作数据库:增、删、改、查

系列文章目录 备注&#xff1a;这里是Django系列文章的所有文章的目录 第一章(一) : Django框架之创建项目/应用/templates、连接MYSQL、配置日志LOGGING、启动django项目 第一章(二)&#xff1a;Django框架的模式、路由、视图&#xff1b; 第一章(三)&#xff1a;Django框架的…

Linux14.文件(下)

文件描述符fd&#xff0c;分配到的是从0开始最小的没有被占用的。 2.输出重定向(重定向的本质&#xff0c;就是在操作系统内部更改fd对应的内容的指向)&#xff0c;同时理解输入重定向和追加重定向。 3.重定向函数dup2(oldfd, newfd)&#xff0c;oldfd -> newfd (小技巧:一…

jennkins配置k8s动态slave

一、 首先完成jenkins的部署可参考jenkins部署 二、 如果是在同一个k8s集群下部署的则直接这样配置 在security中agent的端口也配置成50000 如果是虚拟机中部署的jenkins添加k8s需要做如下操作 echo ‘certificate-authority-data-value’ | base64 -d > ./ca.crt &#…

数字化转型中,企业如何做系统设计

随着人工智能、大数据、云计算、区块链等新一代信息化、数字化技术的高速发展&#xff0c;现阶段社会的方方面面都已经有了巨大的改变&#xff0c;各行各业的企业也都开始了新一轮的科技革命和产业革命&#xff0c;可以说是互联网时代之后&#xff0c;又一个能够从各方面影响世…

MySQL物理文件----日志文件(错误日志、通用查询日志、二进制日志、慢查询日志)

文章目录 MYSQL5.7/8.0支持的几种日志文件1、错误日志&#xff08;Error log&#xff09;2、一般或通用查询日志&#xff08;General query log&#xff09;3、二进制日志&#xff08;Binary log&#xff09;3、1 查看是否开启二进制日志3、2二进制日志开启3、3查看二进制文件位…

数据结构05:树与二叉树[C++][并查集]

图源&#xff1a;文心一言 Chat GPT生成&#xff0c;代码的核心思想与王道咸鱼老师的视频虽然类似&#xff0c;但是在具体实现上毕竟还是略有差别~~因此&#xff0c;如果对考研方向的并查集代码感兴趣&#xff0c;可以查看—— 王道咸鱼老师的视频&#xff1a;{5.5_2_并查集_…

会议邀请|思腾合力邀您共赴MICS 2023第十届医学图像计算青年研讨会

医学图像计算青年研讨会 (Medical Imaging Computing Seminar, MICS) 于2014年成立&#xff0c;宗旨是为医学影像分析领域的全球华人学者提供学术交流平台&#xff0c;增进本领域科研人员和医学专家的交流和合作。 第十届医学图像计算青年研讨会 (MICS 2023) 将于2023年7月14-1…

form表单禁止浏览器自动填充密码

因为用户修改密码的时候,谷歌浏览器、edge等浏览器,总是自动将保存的密码填充到重置密码输入框中,给用户使用带来困扰。原因是因为你在登录的时候选择记住了账号和密码了,所以就会把信息存在浏览器里面,当你在修改密码的时候,由于form表单的 type="password" 所…

【Arduino小车实践】PID算法简介

一、介绍 1. 特点 流量稳定、改变流量&#xff08;水阀&#xff09;&#xff1a;测量当前流量&#xff0c;与预期流量进行比对&#xff0c;不相等则进行相应的调整。 2. 适用系统 适用线性系统&#xff08;二阶以内的线性系统&#xff09;&#xff1a;齐次性、叠加性 3. 宏…

ASEMI-A7二极管可用什么代替,M7二极管正负极判断

编辑-Z 在电子设备的世界中&#xff0c;二极管是最常见的组件之一。它们在各种设备中发挥着重要的作用&#xff0c;包括电源适配器、电源供应器、电池充电器等。在这篇文章中&#xff0c;我们将探讨A7二极管可用什么代替&#xff0c;以及M7二极管正负极判断。 首先&#xff0c…

解决Vue 报错error:0308010C:digital envelope routines::unsupported问题

解决Vue 报错error:0308010C:digital envelope routines::unsupported问题 问题原因方法一&#xff1a;修改配置方法二&#xff1a;尝试卸载Node.js 17版本并重新安装Node.js 16版本&#xff0c;然后再重新启动方法三&#xff1a;package.json增加配置&#xff08;大多数解决问…

【自动驾驶汽车量子群粒子过滤器】用于无人驾驶汽车列车定位的量子粒子滤波研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Doris单机安装部署

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、前期准备1.下载安装包2.修改limits文件 二、安装1.配置启动FE2.配置启动BE3.web测试4.连接测试、添加BE 结尾 前言 Apache Doris 是一个基于 MPP 架构的高…