当前位置:首页 > CN2资讯 > 正文内容

Spring代理模式spring代理模式有几种

1天前CN2资讯
静态代理、动态代理、总结

Spring代理模式

之前提到,Spring 的两个关键点就是 IoC(控制反转) 和 AOP(面向切面编程),IoC 已经研究过了,接下里就到 AOP 了。不过在学习 Spring AOP 前,必须要了解一下代理模式,因为代理模式是 AOP 的核心。

代理模式可以分为静态代理和动态代理,新建 Spring-08-Proxy 项目研究一下(因为在学习 Spring 的过程中,就不额外开个分类了)。

1. 静态代理

1.1 代理模式类图

代理模式( Proxy Pattern )是一个使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

代理模式的通用类图为

其中的三个角色为

  • Subject 抽象角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义
  • RealSubject 具体角色:也叫做被委托角色、被代理角色,是业务逻辑的具体执行者
  • Proxy 代理角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作

1.2 租房例子

这里用租房的例子说明一下:租房本来有房东和客户两个对象,但房东不想自己去出租房子(或客户找不到房东租房),就需要一个第三方房屋中介(出租代理)。

出租房子的类图为

用代码实现一下,首先有一个出租接口,是具体的业务

// 出租接口 // Subject:抽象角色,业务定义 public interface Rent { void rent(); }

要出租房子的房东就要实现这个接口

// 房东角色,要出租房子 // RealSubject:业务的具体执行者 public class Host implements Rent{ public void rent() { System.out.println("房东的房子租出去了!"); } }

这时候客户已经可以找房东租房了

// 我就是客户! public class Client { public static void main(String[] args){ Host host = new Host(); host.rent(); } } // 执行结果 // 房东的房子租出去了!

不过现在房东不想自己去出租房子(或客户找不到房东租房),就需要一个引入出租代理

// 出租代理,租房找他 // Proxy:代理角色,负责对真实角色的应用 public class RentProxy implements Rent{ // 要代理的对象 private Host host; public RentProxy(Host host) { this.host = host; } public void rent() { // 帮房东出租房子 host.rent(); } }

这时候客户要想租房,就可以去找代理了

// 我就是客户! public class Client { public static void main(String[] args){ // 普通代理要求不能 new 真实对象,所以这里不算 Host host = new Host(); // 房东把房子交给代理了 RentProxy rentProxy = new RentProxy(host); // 我们找代理租房就好了 rentProxy.rent(); } } // 执行结果 // 房东的房子租出去了!

在这里代理的作用就是,房东把自己的房子交给代理( +Host ),客户租房直接去找代理就行了。

这样看来代理的作用好像不大,不过上面提到

Proxy 代理角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作

没错,代理可以在业务执行前进行预处理,或者业务完成后进行善后。

让代理来点作用,出租前先带客户看看房,客户如果要租房就签合同,代理也要收钱

// 出租代理,租房找他 // Proxy:代理角色,负责对真实角色的应用 public class RentProxy implements Rent{ // 要代理的对象 private Host host; public RentProxy(Host host) { this.host = host; } public void rent() { // 客户找代理租房,先带去看房 seeHouse(); // 客户觉得可以,签合同 getContract(); // 帮房东出租房子 host.rent(); // 租完房子要收钱的 getCost(); } // 预处理 before public void seeHouse(){ System.out.println("中介带客户看房!"); } // 应该算预处理 public void getContract(){ System.out.println("确认要租,签合同了!"); } // 善后 after public void getCost(){ System.out.println("出租完成,收米!"); } }

这里想到了一个问题:客户找代理租房,是客户跟代理说 “你带我去看房”,“你跟我签合同”,“你收我钱”;还是代理跟客户说 “要租房?先去看房”,“可以就签合同吧”,“把钱付一下” 呢?显然应该是后者。

这里的区别就体现在,Client 中要主动调用 seeHouse、getContract、getCost 这种预处理和善后工作吗?显然不应该。这种工作应该让代理去负责,客户找到代理租房,代理直接一条龙服务!对应的就是在代理的 rent 方法中进行预处理和善后。

这时客户去找代理租房

// 我就是客户! public class Client { public static void main(String[] args){ // 普通代理要求不能 new 真实对象,所以这里不算 Host host = new Host(); // 房东把房子交给代理了 RentProxy rentProxy = new RentProxy(host); // 我们找代理租房,简单的租房操作其实背后有一条龙服务! rentProxy.rent(); } } // 执行结果 /* 中介带客户看房! 确认要租,签合同了! 房东的房子租出去了! 出租完成,收米! */

房东只管出租他的房子,客户只需要找代理租房,烦人的事情都让代理干完了,减轻了客户和房东的负担,这就是代理模式。

1.3 用户业务例子

这里再用一个之前项目中的 UserService 例子加深对代理模式的理解。

首先有一个 UserService 接口,它是业务层的接口,对应 Dao 层的对数据库的操作

// Subject 抽象角色 public interface UserService { // 对应 Dao 层的增删改查! public void add(); public void delete(); public void update(); public void query(); }

然后是接口的具体实现类,之前在实现类中进行了对 Dao 层的调用,这里简化一下(亿下)

// RealSubject 真实对象 public class UserServiceImpl implements UserService{ public void add() { System.out.println("增加用户!"); } public void delete() { System.out.println("删除用户!"); } public void update() { System.out.println("修改用户!"); } public void query() { System.out.println("查询用户!"); } }

好了,现在的要求是,要在执行某个操作时输出日志信息,那要怎么办呢?

如果在 UserServiceImpl 类上修改,就是

// RealSubject 真实对象 public class UserServiceImpl implements UserService{ public void add() { System.out.println("[Debug]:进行了Add操作"); System.out.println("增加用户!"); } // 同上 ... }

这样好像完成了要求。但此时的 UserServiceImpl 类承担了不应该由它来做的事情,一个业务实现类,为什么要输出日志啊?这就增加了代码的耦合性。如果要添加的奇奇怪怪的功能更多,这个业务实现类中关键的业务实现部分就更加不起眼了。

所以这时候就需要代理了!增加 UserServiceProxy 类,输出日志这种事情,属于代理的预处理或善后工作

// Proxy 角色 public class UserServiceProxy implements UserService{ // 要代理的对象! private UserService userService; // 不要直接 new 对象!添加 set 方法让 Spring 注入!连起来了! public void setUserService(UserService userService) { this.userService = userService; } public void add() { // 就当是 before 吧! printLog("[Debug]:进行了Add操作"); userService.add(); } public void delete() { printLog("[Debug]:进行了Delete操作"); userService.delete(); } public void update() { printLog("[Debug]:进行了Update操作"); userService.update(); } public void query() { printLog("[Debug]:进行了Query操作"); userService.query(); } public void printLog(String msg){ System.out.println(msg); } }

让代理来输出日志,保证了 UserServiceImpl 类的职责清晰!假设用到了增加用户的方法

// 客户端! public class Client { public static void main(String[] args){ // 这三步应该让 Spring 来干!不过不麻烦了 UserService userService = new UserServiceImpl(); UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserService(userService); // 增加用户 proxy.add(); } } // 执行结果 // [Debug]:进行了Add操作 // 增加用户!

可以看到通过调用代理来执行业务,成功输出了日志。而如果有其他的要求,也可以通过扩展代理类去实现!

这大概就是面向切面编程( AOP )的思想吧!

扩展:差点和装饰模式搞混了!在装饰模式中,装饰类的作用就是一个特殊的代理类。

1.4 静态代理小结

代理模式的优点

  • 职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

    在租房例子中体现在房东只管如何出租他房子,不用管其他乱七八糟的事情!

  • 高扩展性:具体角色是随时都会发生变化的,只要它实现了接口,无论它如何变化,代理类完全就可以在不做任何修改的情况下使用。

    租房例子中的房东只要实现了出租房子接口,无论他想出租公寓还是别墅,代理都能帮他出租!

  • 智能化:还没有体现出来,这就要看动态代理了。

代理模式的缺点

  • 这种代理其实是静态代理,问题就是每有一个真实角色,就需要一个对应的代理。当真实角色很多的时候,代码量就非常庞大了。这个问题就需要动态代理解决。

代理模式又可以扩展为普通代理强制代理,这里先不仔细研究了,到时候继续设计模式之禅。

2. 动态代理

什么是动态代理?

动态代理就是,在程序运行期间创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

动态代理的原理比较复杂,涉及到反射和 JVM 的知识(我都不会!),所以只能先照葫芦画瓢先用起来。

动态代理实现的方式有两种:JDK 和 CGLib,这里使用 JDK 实现。

2.1 代理类模板

要使用动态代理,就要用到 JDK 提供的 Proxy 类和 InvocationHandler 接口

  • Proxy 类:用于生成被代理接口的代理类对象!
  • InvocationHandler 接口:只有一个方法 invoke 要实现,就是这个方法去调用被代理接口的方法!

首先创建 ProxyHandler 类,这个类就是动态代理类,用它来动态地创建某个接口(实现该接口的对象)的代理类,所以要有一个 target 属性接收要被代理的接口

// 用它来动态生成代理类! public class ProxyHandler { // 被代理的接口 private Object target; // 用注入的方式! public void setTarget(Object target) { this.target = target; } }

然后要实现调用处理器 InvocationHandler 接口,让这个类具有调用被代理接口的方法的能力(通过反射)

// 用它来动态生成代理类! public class ProxyHandler implements InvocationHandler { // 被代理的接口 private Object target; // 用注入的方式! public void setTarget(Object target) { this.target = target; } // 处理代理实例,调用被代理对象的方法! public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在这里调用了被代理对象的方法! // 就是这里进行 面向切面编程! Object result = method.invoke(target, args); return result; } }

最后要通过 Proxy 类,获取被代理接口的代理类对象!

// 用它来动态生成代理类! public class ProxyHandler implements InvocationHandler { // 被代理的接口 private Object target; // 用注入的方式! public void setTarget(Object target) { this.target = target; } // 生成代理类 public Object getProxy(){ // 用代理类,创建代理实例 // 参数为 类加载器ClassLoader loader // 被代理的接口 Class<?>[] interfaces // 调用处理器对象 InvocationHandler h // 这个类就实现了调用处理器接口,所以传自己! return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } // 处理代理实例,调用被代理对象的方法! public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在这里调用了被代理对象的方法! // 就是这里进行 面向切面编程! Object result = method.invoke(target, args); return result; } }

这就是一个动态代理类的模板了,下面来使用一下。

2.2 租房动态代理

把租房例子用动态代理改写一下!

首先,租房接口没有变,房东也还是那个房东

public interface Rent { void rent(); } public class Host implements Rent{ public void rent() { System.out.println("房东的房子租出去了!"); } }

现在,没有手动写的代理类 RentProxy 了,要通过动态代理去获取

public class Client { @Test public void rentTest(){ // 真实角色 Rent host = new Host(); // 代理角色,不存在!怎么办?找动态代理类要! ProxyHandler ph = new ProxyHandler(); // 传入要被代理的对象 ph.setTarget(host); // 动态生成对应的代理类! Rent proxy = (Rent) ph.getProxy(); // 使用被代理对象的方法 proxy.rent(); } } // 执行结果 // 房东的房子租出去了!

我们并没有去写租房的代理类 RentProxy,也成功实现了代理!

不过这里没有扩展的功能,要想进行预处理和善后工作,就要找到调用了被代理对象方法的方法——invoke方法,在调用前后增加新操作

// 用它来动态生成代理类! public class ProxyHandler implements InvocationHandler { ... // 处理代理实例,调用被代理对象的方法! public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在这里调用了被代理对象的方法! seeHouse(); getContract(); Object result = method.invoke(target, args); getCost(); return result; } // 预处理 before public void seeHouse(){ System.out.println("中介带客户看房!"); } // 应该算预处理 public void getContract(){ System.out.println("确认要租,签合同了!"); } // 善后 after public void getCost(){ System.out.println("出租完成,收米!"); } }

这时候客户再去找代理租房,结果就和之前一样了

// Client 同上 // 执行结果 /* 中介带客户看房! 确认要租,签合同了! 房东的房子租出去了! 出租完成,收米! */

2.3 用户业务动态代理

再把用户业务的例子用动态代理改写一下,要求还是加个日志!

UserService 接口和 UserServiceImpl 实现类不变,这里省略。

在动态代理类中增加扩展的日志方法,并且在 invoke 方法中使用

// 用它来动态生成代理类! public class ProxyHandler implements InvocationHandler { ... // 处理代理实例,调用被代理对象的方法! public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在这里调用了被代理对象的方法! // 通过反射获取调用的方法的名字! printLog(method.getName()); Object result = method.invoke(target, args); return result; } // 扩展功能,输出日志 public void printLog(String msg){ System.out.println("[Debug]:进行了"+msg+"操作"); } }

在客户端中通过动态代理调用 add 方法

public class Client { @Test public void userServiceTest(){ // 真实角色 UserService userService = new UserServiceImpl(); // 找动态代理类 ProxyHandler ph = new ProxyHandler(); // 传入要被代理的对象 ph.setTarget(userService); // 动态生成对应的代理类! UserService proxy = (UserService) ph.getProxy(); // 使用被代理对象的方法 proxy.add(); proxy.delete(); } } // 执行结果 /* [Debug]:进行了add操作 增加用户! [Debug]:进行了delete操作 删除用户! */

没有写 UserServiceProxy 类,也进行了代理操作!在 invoke 方法中,还通过 method 的 getName 方法获取了外部调用方法的名字( 原理还是反射????),更加简洁了!

2.4 动态代理小结

动态代理的入口是 Proxy 类,通过其中的 newProxyInstance 方法获取被代理接口的代理类,参数为

  • ClassLoader loader:用哪个类加载器去加载代理对象(),通过 Class 对象中的 getClassLoader 方法获取
  • Class<?>[] interfaces:被代理的接口,通过 Class 对象的 getInterfaces 方法获取,可以获取到 Class 对象实现的所有接口
  • InvocationHandler h:调用处理器对象,调用被代理接口中的方法时,会通过其中的 invoke 方法去执行;修改 invoke 方法就相当于修改了代理类

动态代理的桥梁是 InvocationHandler 接口,通过其中的 invoke 方法调用被代理接口中的方法,参数为

  • Object proxy:具体的代理类对象,其中有被代理接口的方法!
  • Method method:被调用的方法
  • Object[] args:被调用方法的参数

动态代理通过 Proxy 类创建具体的代理类,代理类又通过 InvocationHandler 接口中的 invoke 方法完成对被代理接口中的方法的调用。

3. 总结

代理模式:由最简单的静态代理,可以扩展为普通代理和强制代理(这里没深入),然后是最强大的动态代理!

静态代理好是好,但要写代理类太麻烦!所以出现了动态代理,动态代理的原理有点深奥,类加载器 Java 虚拟机啥啥的,后面再研究吧!

不过现在还不知道动态代理除了添加日志外还有什么应用场景···就像上面的租房和用户业务,不还得对应不同的动态代理类的操作嘛?

麻了,后面再说吧????!

    你可能想看:

    扫描二维码推送至手机访问。

    版权声明:本文由皇冠云发布,如需转载请注明出处。

    本文链接:https://www.idchg.com/info/32114.html

    分享给朋友:

    “Spring代理模式spring代理模式有几种” 的相关文章

    如何有效解决VPS硬盘占用过高问题:优化与清理指南

    1.1 系统日志和缓存文件积累 系统日志和缓存文件是VPS硬盘占用过高的常见原因之一。每次系统运行或应用程序执行时,都会生成日志文件来记录操作和错误信息。这些日志文件随着时间的推移会逐渐积累,占用大量磁盘空间。缓存文件也是如此,它们用于加速系统或应用程序的运行,但如果不定期清理,也会占用大量空间。我...

    探索美国冷门VPS:高性价比与个性化服务的优选

    在谈论VPS(虚拟专用服务器)时,人们往往会联想到那些知名的品牌和服务,而美国冷门VPS市场却是一个值得关注的领域。这些冷门VPS提供商虽然在整体市场中的知名度较低,但却为特定的用户群体和需求提供了颇具价值的服务。我在研究这个市场时,发现不少提供商在某些方面有着相当的优势,让我对这个冷门领域充满了好...

    甲骨文云注册:详细流程与免费试用攻略

    甲骨文云注册概述 甲骨文云介绍和服务特点 我对甲骨文云的首要印象是它独一无二的服务。甲骨文云不仅提供高性能的VPS服务器,还给予用户一个轻松的起步体验。其主要服务包括两台配置为1核1G内存、50G硬盘和10T流量的AMD VPS,还有一台配置为4核24G内存、100G硬盘、10T流量的ARM VPS...

    全面解析CPU租用服务:灵活性与高效性的最佳选择

    CPU租用服务概述 在当今快速发展的科技环境中,CPU租用服务作为一种创新的计算资源提供模式,正在受到越来越多用户的关注。这种服务使得用户可以根据具体需求,灵活地租用不同配置的CPU资源,从而有效地降低了硬件采购成本。 CPU租用服务的意义不仅在于提供强劲的计算能力,更在于它的灵活性。用户不再需要一...

    SSH Client Windows 登录指南:轻松配置与高级功能使用

    SSH 客户端在 Windows 中的概述 SSH,也就是安全外壳协议,是一种用来在网络中进行安全数据传输的协议。它确保数据的机密性和完整性,这对于网络管理员和开发者来说是至关重要的。在Windows中,SSH客户端直接关系到我们如何安全地登录到远程计算机。通过SSH,用户可以安全地执行命令、传输文...

    BBR对国内网站的实际作用与应用效果分析

    BBR(Bottleneck Bandwidth and Round-trip propagation time)算法是由Google推出的一种TCP拥塞控制算法。它的设计初衷是为了优化网络连接的传输速率和稳定性,尤其是在面临高延迟和波动网络条件时表现优异。可能的很多朋友会问,BBR到底是个什么东西...