zoukankan      html  css  js  c++  java
  • 代理模式

    一、代理模式

    1、代理模式(Proxy Pattern):指为其他对象提供一种代理,以控制对这个对象的访问。(结构型设计模式)

    Spring AOP就是用代理模式实现的,包括事务代理、非侵入式日志监听等。

    代理对象在客户端和目标对象之间起到中介作用。生活中的代理模式:(目标对象也即被代理对象)

    2、适用场景

    • 保护目标对象
    • 增强目标对象

    3、优点

    • 能将代理对象与真实被调用的目标对象分离
    • 一定程度上降低了系统的耦合度,易于扩展
    • 代理可以起到保护目标对象的作用
    • 增强目标对象的职责

    4、缺点

    • 代理模式会造成系统设计中类的数目增加
    • 在客户端和目标对象之间增加了一个代理对象,会造成请求处理速度变慢
    • 增加了系统的复杂度

    5、类结构图:

    Subject是顶层接口,RealSubject是真实对象(目标的对象),Proxy是代理对象,代理对象持有目标对象的引用,客户端调用代理对象方法,同时会也调用目标对象的方法,在代理对象前后增加一些处理。在代码中,我们想到代理,就会理解是代码增强,其实就是在原本逻辑前后增加一些逻辑,而调用者无感。代理模式属于结构型模式,有静态代理动态代理静态代理类在编译期就生成,而动态代理类则是在JVM运行时动态生成。静态代理的效率比动态代理相对高一些,但是静态代理代码冗余量大,一旦需要修改接口,代理类和目标对象类都需要修改(违背了开闭原则),动态代理则易于扩展。

    二、静态代理

    概念:显示声明目标对象。

    例子1:人到了适婚年龄,父母总是迫不及待希望抱孙子。而现在社会的人在各种压力之下,都选择晚婚晚育。于是着急的父母就开始到处为自己的子女相亲,比子女自己还着急。这个相亲的过程,就是一种我们人人都有份的代理。来看代码实现:

    定义一个顶层Person接口:

    儿子要找对象,定义一个Son实现类:

    父亲要帮儿子相亲,定义Father类:

    测试代码:

    运行结果:

    例子2:在实际的分布式业务场景中,我们通常会对数据库进行分库分表,之后使用Java操作时,就可能需要配置多个数据源,我们通过设置数据源路由来动态切换数据源。

    定义一个Order实体类:

    定义一个OrderDao持久层操作类:

    定义一个IOrderService接口:

    创建OrderServiceImpl实现类:

    接下来使用静态代理,主要完成的功能是:根据订单创建时间自动按年进行分库。根据开闭原则,原来写好的逻辑不改,通过代理对象来完成。先创建数据源路由对象,使用ThreadLocal单例实现,定义DynamicDataSourceEntry类:

    创建切换数据源的代理OrderServiceImplStaticProxy 类:

    测试代码:

    运行结果:

    类结构图:

    三、动态代理

    动态代理和静态代理对比基本思路是一致的,只不过用动态代理功能更加强大,随着业务的扩展适应性更强。如果还以找对象为例,使用动态代理相当于是能够适应复杂的业务场景。不仅仅只是父亲给儿子找对象,如果找对象这项业务发展成了一个产业,进而出现了媒婆、婚介所等这样的形式。那么,此时用静态代理成本就更大了,需要一个更加通用的解决方案,要满足任何单身人士找对象的需求。我们升级一下代码,先来看JDK实现方式:

    1、JDK Proxy实现方式

    例子1:定义一个媒婆(婚介)JDKMeipo类:

    定义一个单身客户Customer类:(JDK动态代理的目标对象一定要实现接口,要扫描接口中的所有方法进行实现和覆盖

    测试代码:

    运行结果:

    例子2:数据源动态路由业务,定义一个OrderServiceImplDynamicProxy类:

    测试代码:

    运行结果:

    依然能够达到相同的结果。但是,动态代理实现之后,我们不仅能实现Order的数据源动态路由。当然,有比较重要的约定,必须要求实现getCreateTime()方法,因为路由规则是根据时间来运算的。当然我们也可以通过接口规范来达到约束的目的,这里不再举例。

    JDK Proxy采用字节码重组,重新生成新的对象来代替原始对象,以达到动态代理的目的。

    JDK Proxy生成对象的步骤:

    ① 拿到目标对象的引用,并且通过反射获取它所有的接口

    ② JDK Proxy类重新生成一个新的类、同时新的类要实现目标类所有实现的接口

    ③ 动态生成Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)

    ④ 编译新生成的Java代码,生成新的字节码

    ⑤ 再重新加载到JVM中运行

    以上这个过程就叫字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的class文件一般都是自动生成的

    2、CGLib调用API

    简单看一下CGLib代理的使用,还是媒婆为例子,创建CglibMeipo类:

    创建单身客户Customer类:(CGlib代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现的动态代理

    测试代码:

    运行结果,同样满足要求:

    提问:为什么Cglib动态代理执行代理方法效率比JDK的高?

    因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和目标类各生成一个Class,这个Class会为代理类或目标类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位到要调用的方法直接进行调用,省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。

    Cglib和JDK动态代理对比

    ① JDK动态代理是实现了目标对象的接口,Cglib是继承了目标对象。

    ② JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

    ③ JKD调用代理方法是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

    四、代理模式与Spring

    1、代理模式在Spring源码中的应用

    ① 先看ProxyFactoryBean核心的方法就是getObject()方法,我们来看一下源码:

    在getObject()方法中,主要调用getSingletonInstance()和newPrototypeInstance()。在Spring配置中,如何不做任何设置,那么Spring代理默认生成的Bean都是单例对象。如果修改scope则每次创建一个新的原型对象。newPrototypeInstance()里面的逻辑比较复杂,以后再研究,这里先做简单的了解。

    ② Spring利用动态代理实现AOP有两个非常重要的类,分别是JdkDynamicAopProxy类和CglibAopProxy类,类图如下:

    2、Spring中的代理选择原则

    1、当Bean有实现接口时,Spring就会用JDK的动态代理。

    2、当Bean没有实现接口时,Spring会选择Cglib。

    3、Spring可以通过配置强制使用Cglib,只需在Spring配置文件中加入如下代码:

    五、静态代理和动态代理的本质区别

    1、静态代理只能通过手动完成代理操作,如果目标类增加新的方法,代理类需要同步新增,违背开闭原则。

    2、动态代理采用在运行时动态生成字节码的方式,取消了对目标类的扩展限制,遵循开闭原则。

    3、若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。

  • 相关阅读:
    尽管以C++为基础,但 Java 是一种更纯粹的面向对象程序设计语言
    Java 还是 C++?
    计划的回报
    阶段4:校订
    阶段3:开始创建
    阶段2:如何构建?
    阶段1:要制作什么?
    阶段0:拟出一个计划
    不要迷失
    分析和设计
  • 原文地址:https://www.cnblogs.com/ZekiChen/p/12498313.html
Copyright © 2011-2022 走看看