文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试官问 Spring AOP 中两种代理模式的区别,我懵圈了

2024-12-11 21:24

关注

[[330874]]

基本介绍

代理模式是一种结构型设计模式。为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,并允许在将请求提交给对象前后进行一些处理。

被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

代理模式主要有三种不同的形式:

问题为什么要控制对于某个对象的访问呢?举个例子:有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。

图:refactoringguru.cn

你可以实现延迟初始化:在实际有需要时再创建该对象。对象的所有客户端都要执行延迟初始代码。不幸的是, 这很可能会带来很多重复代码。

在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现:比如类可能是第三方封闭库的一部分。

解决方案

代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。

图:refactoringguru.cn

代理将自己伪装成数据库对象, 可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。

这有什么好处呢?如果需要在类的主要业务逻辑前后执行一些工作, 你无需修改类就能完成这项工作。由于代理实现的接口与原类相同, 因此你可将其传递给任何一个使用实际服务对象的客户端。

代理模式结构

图:refactoringguru.cn

  1. 服务接口 (Service Interface) 声明了服务接口。代理必须遵循该接口才能伪装成服务对象。
  2. 服务 (Service) 类提供了一些实用的业务逻辑。
  3. 代理 (Proxy) 类包含一个指向服务对象的引用成员变量。代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。通常情况下, 代理会对其服务对象的整个生命周期进行管理。
  4. 客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。

打游戏有代练、买卖房子有中介代理、再比如一般公司投互联网广告也可以找代理公司,这里的代练、中介、广告代理公司扮演的角色都是代理。

这里举个更接近程序员的例子,比如有些变态的公司不允许在公司刷微博,看视频,可以通过一层代理来限制我们访问这些网站。

废话不多说,先来个静态代理。

静态代理

定义网络接口

  1. public interface Internet { 
  2.     void connectTo(String serverHost) throws Exception; 

真正的网络连接

  1. public class RealInternet implements Internet{ 
  2.  
  3.     @Override 
  4.     public void connectTo(String serverHost) throws Exception { 
  5.         System.out.println("Connecting to "+ serverHost); 
  6.     } 

公司的网络代理

  1. public class ProxyInternet implements Internet { 
  2.  
  3.     //目标对象,通过接口聚合 
  4.     private Internet internet; 
  5.  
  6.     // 通过构造方法传入目标对象 
  7.     public ProxyInternet(Internet internet){ 
  8.         this.internet = internet; 
  9.     } 
  10.     //网络黑名单 
  11.     private static List bannedSites; 
  12.  
  13.     static 
  14.     { 
  15.         bannedSites = new ArrayList(); 
  16.         bannedSites.add("bilibili.com"); 
  17.         bannedSites.add("youtube.com"); 
  18.         bannedSites.add("weibo.com"); 
  19.         bannedSites.add("qq.com"); 
  20.     } 
  21.  
  22.     @Override 
  23.     public void connectTo(String serverhost) throws Exception { 
  24.         // 添加限制功能 
  25.         if(bannedSites.contains(serverhost.toLowerCase())) 
  26.         { 
  27.             throw new Exception("Access Denied:"+serverhost); 
  28.         } 
  29.         internet.connectTo(serverhost); 
  30.     } 

客户端验证

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Internet internet = new ProxyInternet(new RealInternet()); 
  5.         try { 
  6.             internet.connectTo("so.com"); 
  7.             internet.connectTo("qq.com"); 
  8.         } catch (Exception e) { 
  9.             System.out.println(e.getMessage()); 
  10.         } 
  11.     } 

输出

  1. Connecting to so.com 
  2. Access Denied:qq.com 

不能访问娱乐性网站,但是可以用 360 搜索,SO 靠谱,哈哈

静态代理类优缺点

优点:

在不修改目标对象的前提下,可以通过代理对象对目标对象功能扩展

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,RealInterner() 可以应用工厂将它隐藏。

缺点:

代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理

静态代理会产生很多静态类,所以我们要想办法可以通过一个代理类完成全部的代理功能,这就引出了动态代理。

JDK原生动态代理

在 Java 中要想实现动态代理机制,需要 java.lang.reflect.InvocationHandler 接口和 java.lang.reflect.Proxy 类的支持

Coding

网络接口不变

  1. public interface Internet { 
  2.     void connectTo(String serverHost) throws Exception; 

真正的网络连接,也不会改变

  1. public class RealInternet implements Internet{ 
  2.  
  3.     @Override 
  4.     public void connectTo(String serverHost) throws Exception { 
  5.         System.out.println("Connecting to "+ serverHost); 
  6.     } 

动态代理,需要实现 InvocationHandler,我们用 Lambda 表达式简化下

  1. public class ProxyFactory { 
  2.  
  3.      
  4.     private Object target; 
  5.  
  6.      
  7.     public ProxyFactory(Object target) { 
  8.         this.target = target; 
  9.     } 
  10.  
  11.     public Object getProxyInstance() { 
  12.  
  13.          
  14.         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { 
  15.             @Override 
  16.             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  17.                 if(bannedSites.contains(args[0].toString().toLowerCase())) 
  18.                 { 
  19.                     throw new Exception("Access Denied:"+args[0]); 
  20.                 } 
  21.                 //反射机制调用目标对象的方法 
  22.                 Object obj = method.invoke(target, args); 
  23.                 return obj; 
  24.             } 
  25.         }); 
  26.     } 
  27.  
  28.     private static List bannedSites; 
  29.  
  30.     static 
  31.     { 
  32.         bannedSites = new ArrayList(); 
  33.         bannedSites.add("bilibili.com"); 
  34.         bannedSites.add("youtube.com"); 
  35.         bannedSites.add("weibo.com"); 
  36.         bannedSites.add("qq.com"); 
  37.     } 

客户端

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.         Internet internet = new ProxyInternet(new RealInternet()); 
  5.         try { 
  6.             internet.connectTo("360.cn"); 
  7.             internet.connectTo("qq.com"); 
  8.         } catch (Exception e) { 
  9.             System.out.println(e.getMessage()); 
  10.         } 
  11.     } 

动态代理的方式中,所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。

cglib代理

静态代理和 JDK 代理模式都要求目标对象实现一个接口,但有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候就可以使用目标对象子类来实现代理,这就是 cglib 代理。

Coding

添加 cglib 依赖

  1.  
  2.     cglib 
  3.     cglib 
  4.     3.3.0 
  5.  

不需要接口

  1. public class RealInternet{ 
  2.  
  3.     public void connectTo(String serverHost) { 
  4.         System.out.println("Connecting to "+ serverHost); 
  5.     } 

代理工厂类

  1. public class ProxyFactory implements MethodInterceptor { 
  2.  
  3.     private Object target; 
  4.  
  5.     public ProxyFactory(Object target){ 
  6.         this.target = target; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 
  11.         System.out.println("cglib 代理开始,可以添加逻辑"); 
  12.         Object obj = method.invoke(target,objects); 
  13.         System.out.println("cglib 代理结束"); 
  14.         return obj; 
  15.     } 
  16.  
  17.  
  18.     public Object getProxyInstance(){ 
  19.         //工具类,类似于JDK动态代理的Proxy类 
  20.         Enhancer enhancer = new Enhancer(); 
  21.         //设置父类 
  22.         enhancer.setSuperclass(target.getClass()); 
  23.         //设置回调函数 
  24.         enhancer.setCallback(this); 
  25.         //创建子类对象,即代理对象 
  26.         return enhancer.create(); 
  27.     } 

客户端

  1. public class Client { 
  2.  
  3.     public static void main(String[] args) { 
  4.  
  5.         //目标对象 
  6.         RealInternet target = new RealInternet(); 
  7.         //获取代理对象,并且将目标对象传递给代理对象 
  8.         RealInternet internet = (RealInternet) new ProxyFactory(target).getProxyInstance(); 
  9.         internet.connectTo("so.cn"); 
  10.     } 

输出

  1. cglib 代理开始,可以添加逻辑 
  2. Connecting to so.cn 
  3. cglib 代理结束 

代理模式适合应用场景

使用代理模式的方式多种多样, 我们来看看最常见的几种。

代理可仅在客户端凭据满足要求时将请求传递给服务对象。

代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端, 检查它们是否仍在运行。如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

代理还可以记录客户端是否修改了服务对象。其他客户端还可以复用未修改的对象。

AOP 中的代理模式

AOP(面向切面编程)主要的的实现技术主要有 Spring AOP 和 AspectJ

AspectJ 的底层技术就是静态代理,用一种 AspectJ 支持的特定语言编写切面,通过一个命令来编译,生成一个新的代理类,该代理类增强了业务类,这是在编译时增强,相对于下面说的运行时增强,编译时增强的性能更好。(AspectJ 的静态代理,不像我们前边介绍的需要为每一个目标类手动编写一个代理类,AspectJ 框架可以在编译时就生成目标类的“代理类”,在这里加了个冒号,是因为实际上它并没有生成一个新的类,而是把代理逻辑直接编译到目标类里面了)

Spring AOP 采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP 提供了对 JDK 动态代理的支持以及 CGLib 的支持。

默认情况下,Spring对实现了接口的类使用 JDK Proxy方式,否则的话使用CGLib。不过可以通过配置指定 Spring AOP 都通过 CGLib 来生成代理类。

具体逻辑在 org.springframework.aop.framework.DefaultAopProxyFactory类中,使用哪种方式生成由AopProxy 根据 AdvisedSupport 对象的配置来决定源码如下:

  1. public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { 
  2.     public DefaultAopProxyFactory() { 
  3.     } 
  4.  
  5.     public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 
  6.         if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { 
  7.             return new JdkDynamicAopProxy(config); 
  8.         } else { 
  9.             Class targetClass = config.getTargetClass(); 
  10.             if (targetClass == null) { 
  11.                 throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation."); 
  12.             } else { 
  13.                 //如果目标类是接口且是代理类, 使用JDK动态代理类,否则使用Cglib生成代理类 
  14.                 return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config)); 
  15.             } 
  16.         } 
  17.     } 
  18.  
  19.     private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) { 
  20.     } 

具体内容就不展开了,后边整理 SpringAOP 的时候再深入。

参考与感谢https://refactoringguru.cn/design-patterns/proxy https://www.geeksforgeeks.org/proxy-design-pattern/

 

来源: JavaKeeper内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯