设计模式第4讲——代理模式(Proxy)

news/2025/1/31 7:23:35/

目录

一、什么是代理模式 

二、特点

三、应用场景 

四、代理模式的实现 

4.0 代码结构

4.1 静态代理 

4.2 JDK动态代理

4.3 Cglib代理

 五、总结


一、什么是代理模式 

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

二、特点

优点:

  1. 代理模式可以隐藏真是对象的实现细节,使客户端无需知晓真实对象的工作方式和结构。
  2. 通过代理类来间接访问真实类,可以在不修改真实类的情况下,对其进行扩展、优化或添加安全措施。
  3. 代理模式实现起来简单,易于扩展和维护,符合面向对象设计原则中的开闭原则。

缺点:

  1. 代理模式可能会引入额外的复杂性和间接性,增加程序设计和维护的难度。
  2. 对象代理可能会降低系统性能,特别是在处理大数据量或频繁调用的情况下,因为代理需要额外的计算和网络通信开销。

三、应用场景 

  • 远程代理: 在客户端与远程对象之间建立一个代理对象,代理对象通过网络与远程对象进行通信,隐藏了底层的网络细节,使得客户端可以像调用本地对象一样访问远程对象。
  • 虚拟代理: 用于控制访问一些资源开销较大的对象,只有在需要的时候才真正去创建或加载,从而节省系统开销。
  • 安全代理: 用于控制资源的访问权限,保护真实对象的信息不被非法访问和篡改。在访问真实对象之前,代理可以先进行身份验证、鉴权等操作。
  • 缓存代理: 用于提高系统性能,将频繁访问的对象或方法的结果缓存起来,可以减少真实对象的调用次数。(比如缓存是代理、真实对象就是mysql)
  • 日志记录代理: 用于记录对象的方法调用日志,方便后续的调试和问题排查。
  • 延迟加载代理: 用于减少系统启动时间和资源占用,将部分对象或方法的加载时间推迟到真正需要的时候再进行初始化。
  • 买火车票不一定到火车站买,也可以去代售点。
  • 银行卡和支票都是真实钱的代理对象。

四、代理模式的实现 

4.0 代码结构

下面就以房东和租客为例,分别介绍一下静态代理jdk动态代理cglib代理

4.1 静态代理 

静态代理是一种在代码编写期进行代理类和被代理类的关联的代理方式。

具体实现是创建一个代理类,通常需要实现与被代理类相同的接口或继承被代理类。

房东接口类:Landlord1Service,注意,静态代理实现它的真实对象只能有一个,多个的话,代理对象不能确定哪个对象需要被代理,会导致报错,JDK动态代理没这个问题。

/*** 房东* @author Created by njy on 2023/5/30*/
public interface Landlord1Service {/*** 出租* @param money 金额* @return*/void rent(Integer money);
}

租客:TenantImpl

import org.springframework.stereotype.Component;
/*** 租客* @author Created by njy on 2023/5/30*/
@Component
public class TenantImpl implements Landlord1Service {@Overridepublic void rent(Integer money) {System.out.println("租下"+money+"元一个月的房子");}
}

静态代理:ProxyImpl

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/*** 中介* @author Created by njy on 2023/5/30*/
@Component
public class ProxyImpl implements Landlord1Service {/*** 房东有很多套房子,不想亲自出马了,于是找来了中介*/@Autowiredprivate Landlord1Service target;/*** 优点就是在不改变原来的实现类的情况下对方法实现了增强* 缺点是如果原来的接口新增了方法,那么这里也要对应实现新的方法* @param money 金额* @return*/@Overridepublic void rent(Integer money) {System.out.println("[静态代理]交中介费");target.rent(money);System.out.println("[静态代理]中介负责维修管理");}
}

测试:

/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
public class TestProxy {@Autowiredprivate TenantImpl tenant;@Autowiredprivate ProxyImpl proxy;//1.静态代理@Testvoid TestStatic(){tenant.rent(1000);System.out.println();proxy.rent(2000);}
}

适用场景:

  • 当代理对象只有一个时,可以使用静态代理
  • 当被代理的类的接口比较稳定时,可以使用静态代理
  • 当需要为多个被代理的类提供代理时,会导致代理类过多,不方便管理和维护,所以不建议使用静态代理。

4.2 JDK动态代理

JDK动态代理是一种比较常见的代理方式,它是在程序运行时动态生成代理类,也就是说我们在编写代码时并不知道具体代理的是什么类,而是在程序运行时动态生成。

房东接口类:Landlord2Service

/*** @author Created by njy on 2023/5/30*/
public interface Landlord2Service {/*** 出租* @param money* @return*/void rent(Integer money);
}

租客1:Teant1Impl

import org.springframework.stereotype.Component;/*** @author Created by njy on 2023/5/30*/
@Component
public class Teant1Impl implements Landlord2Service{@Overridepublic void rent(Integer money) {System.out.println("tenant1租下"+money+"元一个月的房子");}
}

租客2:Teant2Impl

import org.springframework.stereotype.Component;/*** @author Created by njy on 2023/5/30*/
@Component
public class Tenant2Impl implements Landlord2Service {@Overridepublic void rent(Integer money) {System.out.println("tenant2租下"+money+"元一个月的房子");}
}

JDK动态代理:JDKProxy

import org.springframework.stereotype.Component;
import java.lang.reflect.Proxy;
/*** JDK动态代理:就是把代理抽象了一下* @author Created by njy on 2023/5/30*/
public class JDKProxy {private Object target;public JDKProxy(Object target){this.target=target;}/*** 给目标对象生成代理对象* @return 代理生成的对象*/public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),//这里是要实现jdk代理InvocationHandler的接口,lambda表达式(proxy,method,args)->{//执行对象方法System.out.println("[JDK动态代理]交中介费");method.invoke(target,args);System.out.println("[JDK动态代理]中介负责维修管理");return null;});}
}

Test:

/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
public class TestProxy {@Autowiredprivate Teant1Impl teant1;@Autowiredprivate Tenant2Impl tenant2;//2.JDK动态代理@Testvoid TestJDK(){Landlord2Service proxyInstance1 = (Landlord2Service) new JDKProxy(teant1).getProxyInstance();proxyInstance1.rent(2500);System.out.println();Landlord2Service proxyInstance2 = (Landlord2Service) new JDKProxy(tenant2).getProxyInstance();proxyInstance2.rent(2500);}
}

适用场景:

  • 对象必须实现一个或多个接口
  • 代理类的代理方法不需要额外的逻辑

4.3 Cglib代理

CGLIB代理是在运行时动态生成代理类的方式,它使用的库是cglib,和JDK代理相比,它不是动态的生成一个实现了接口的代理类,而是直接在内存中构建一个被代理类的子类,并重写父类的方法来进行代理。

房东类:Landlord3Service

import org.springframework.stereotype.Component;
/*** @author Created by njy on 2023/5/30*/
@Component
public class Landlord3Service {/*** 出租房屋* @param money* @return*/public void rent(Integer money){System.out.println("租下"+money+"元一个月的房子");}
}

Cglib代理类:CglibProxy

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*** JDKProxy:cglib子类代理工厂* 1.代理的类不能为final* 2.目标对象的方法如果为final/static,那么就不会被拦截,也不会执行目标对象的业务方法* @author Created by njy on 2023/5/30*/
public class CglibProxy implements MethodInterceptor {/*** 目标对象*/private final Object target;public CglibProxy(Object target){this.target=target;}public Object getProxyInstance(){//1.工具类Enhancer en=new Enhancer();//2.设置父类en.setSuperclass(target.getClass());//3.设置回调函数en.setCallback(this);//4.创建子类(代理对象)return en.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("[Cglib代理]交中介费");method.invoke(target,objects);System.out.println("[Cglib代理]中介负责维修管理");return null;}
}

测试: 

/*** @author Created by njy on 2023/5/30*/
@SpringBootTest
@RequiredArgsConstructor
public class TestProxy {@Autowiredprivate Landlord3Service landlord3Service;//3.Cglib代理@Testvoid TestCglib(){Landlord3Service proxyInstance = (Landlord3Service) new CglibProxy(landlord3Service).getProxyInstance();proxyInstance.rent(3000);}
}

​ 

 适用场景:

  • 被代理的类没有实现接口或者无法实现接口
  • 代理类的代理方法需要进行额外的逻辑,如事务处理等。

五、总结

  • 对于没有实现接口的类,只能使用CGLIB代理
  • 对于实现了接口的类,可以使用JDK代理或者CGLIB代理,如果要求比较高的话,建议使用JDK代理。
  • 对于单个代理类的情况,并且被代理类实现了接口,可以使用静态代理。
  • 对于多个被代理类的情况,建议使用JDK代理或CGLIB代理。

http://www.ppmy.cn/news/365418.html

相关文章

python办公自动化 使用openpyxl合并excel表格

果然,只有工作才能促使我学习。 1、需求介绍 就是要把九个字段名一样且每个表数据量是固定的表合并。 内部长这样: 表格都是从数据库里跑出来的,所以格式比较固定,每个表数据量固定就是,第一个表197行,第二…

Nyoj 229

我感觉这道题很好&#xff01; 利用背包思想来做&#xff0c;用二分来逼近并找出答案&#xff01; #include<iostream> #include<cstdio> #include<cstring> using namespace std;const int MAXN 110;int Ap[MAXN]; int Bp[MAXN]; int dp[MAXN];//dp[i]表示…

NYOJ_39

题目&#xff1a; 描述 请判断一个数是不是水仙花数。 其中水仙花数定义各个位数立方和等于它本身的三位数。 输入 有多组测试数据&#xff0c;每组测试数据以包含一个整数n(100<n<1000) 输入0表示程序输入结束。 输出 如果n是水仙花数就输出Yes 否则输出No 样例输入 15…

使用myeclipse的反向工程来生成相应的hibernate映射文件和POJO类

1、 数据库设计 建立crud.student数据库表&#xff1a; 图1 数据库表 你可以使用如下语句创建该库表&#xff1a; [sql] view plain copy create database if not exists crud; USE crud; DROP TABLE IF EXISTS student; CREATE TABLE student ( id int(4) NOT NULL a…

什么是5G? 5G有多牛逼!!!!

什么是5G? 5G有多牛逼&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 时代发展的历程&#xff01; 社会发展的速度快到你无法想象&#xff01;相信很多人都是伴随着移动通讯发展的脚步走来的&#xff0c;今天就让博主带大家领略一下科技的发展&#xff0c;年…

【Cxinny】node.js

Node.js是一个JavaScript的后端运行环境&#xff0c;无法调用Ajax、DOM和BOM等浏览器内置API。 Node.js仅仅提供了基础的功能和API&#xff0c;但是基于这些基础功能&#xff0c;开发了各种框架。node.js框架 Express.js是快速开发web应用程序&#xff0c;因为它具有强大的路由…

nyoj17

单调递增最长子序列 时间限制&#xff1a; 3000 ms | 内存限制&#xff1a; 65535 KB 难度&#xff1a; 4 描述 求一个字符串的最长递增子序列的长度 如&#xff1a;dabdbf最长递增子序列就是abdf&#xff0c;长度为4 输入 第一行一个整数0<n<20,表示有n个字符串要处理…

NYOJ-49

题目&#xff1a; 描述 小明今天很开心&#xff0c;家里购置的新房就要领钥匙了&#xff0c;新房里有一间他自己专用的很宽敞的房间。更让他高兴的是&#xff0c;妈妈昨天对他说&#xff1a;“你的房间需要购买哪些物品&#xff0c;怎么布置&#xff0c;你说了算&#xff0c;只…