设计模式-代理模式

# 代理模式

1.简介

代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

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

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

2.UML图

  • 服务接口(Service Interface) 声明了服务接口,代理必须遵循该接口才能伪装成服务对象。
  • 服务(Service)类提供了一些实用的业务逻辑。
  • 代理(proxy) 类包含一个指向服务对象的引用成员变量。代理完成其任务,后台会将请求传递给服务对象
  • 客户端(Client) 能通过同一接口与服务或代理进行交互,所以你可以在一切需要服务对象的代码中使用代理。

3.代码示例

首先我们需要知道有3种代理模式。静态代理、jdk的动态代理和 cglib的动态代理。

假设现在某一个打工人需要打官司来讨回亏欠的薪资,那么首先它需要找一名代理律师来负责处理诉讼等相关事宜。这里相当于代理律师会全权代理这个打工人。

静态代理:是指预先确定了代理和被代理者的关系。假如打工人A的代理律师确认为为B,相当于代理类和被代理类的依赖关系在编译期间就需要被确认。

​ 定义一个诉讼的接口:

java 复制代码
package com.gs.designmodel.daili.jingtai;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:12
 *
 *  诉讼接口
 **/
public interface ILawSuit {

    /**
     * 提起诉讼
     */
    void submit(String proof);

    /**
     * 法律辩护
     */
    void defend();
}

​ 打工人小明,需要实现 ILawSuit接口:

java 复制代码
package com.gs.designmodel.daili.jingtai;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:15
 *
 * 小明诉讼类型
 **/

public class XiaoMing implements ILawSuit{

    @Override
    public void submit(String proof) {
        System.out.println("老板带着小姨子欠薪跑路了,证据如下: " + proof);
    }

    @Override
    public void defend() {
        System.out.println("审判结果,必须还钱");
    }
}

​ 代理律师类 也需要实现 ILawSuit接口并持有打工人对象的引用:

java 复制代码
package com.gs.designmodel.daili.jingtai;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:19
 *
 * 代理律师诉讼类
 **/
public class ProxyLawyer implements ILawSuit{

    /**
     * 需要代理的对象
     */
    private ILawSuit plaintiff;

    public ProxyLawyer(ILawSuit plaintiff) {
        this.plaintiff = plaintiff;
    }

    @Override
    public void submit(String proof) {
        plaintiff.submit(proof);
    }

    @Override
    public void defend() {
        plaintiff.defend();
    }
}

产生代理对象的静态代理工厂类:

java 复制代码
package com.gs.designmodel.daili.jingtai;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:21
 **/
public class ProxyFactory {

    public static ILawSuit getProxy() {
        return new ProxyLawyer(new XiaoMing());
    }
}

测试类:

java 复制代码
package com.gs.designmodel.daili.jingtai;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:22
 **/
public class Test {
    public static void main(String[] args) {
        ProxyFactory.getProxy().submit("工资卡流水");
        ProxyFactory.getProxy().defend();
    }
}

结果:

makefile 复制代码
老板带着小姨子欠薪跑路了,证据如下: 工资卡流水
审判结果,必须还钱

这样的好处是我们可以简单的进行扩展,比如说我们需要在原先接口的基础上加上证据校验、法官谈判等流程,就不需要改变原本类的代码。

但是缺点也很明显,静态代理需要为每一个对象都创建一个代理类,增加了维护成本以及开发成本。那么为了解决这个问题,动态代理的 概念就出来了,不需要在固定为每一个需要代理的类创建一个代理类,而是在运行时通过反射机制创建。

JDK动态代理:

首先要理解:Proxy可以理解为调度器,用来创建动态代理类实例对象的,只有得到了这个对象我们才能调用那些需要代理的方法,InvocationHandler 增强服务接口可以理解为代理器,是给动态代理类实现的,负责处理被代理对象操作的。

在业务的层面上可以理解为小明的代理律师其实一直都没有确认,直到开庭的时候才为他匹配一个律师,说明他们的关系是在运行期间确认的。

小华诉讼类:

java 复制代码
package com.gs.designmodel.daili.dongtai;

import com.gs.designmodel.daili.jingtai.ILawSuit;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:24
 *
 * 小华诉讼类
 **/
public class XiaoHua implements ILawSuit {
    @Override
    public void submit(String proof) {
        System.out.println("老板带着小姨子欠薪跑路了,证据如下: " + proof);
    }

    @Override
    public void defend() {
        System.out.println("审判结果,10号之前必须还钱");
    }
}

构建一个动态代理类:

java 复制代码
package com.gs.designmodel.daili.dongtai;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:25
 *
 * 动态代理类
 **/
public class DynProxyLawyer implements InvocationHandler {

    private Object target;

    public DynProxyLawyer(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("法庭公告案件进展: " + method.getName());
        // 执行
        Object result = method.invoke(target, args);
        return result;
    }
}

静态工厂方法:

java 复制代码
package com.gs.designmodel.daili.dongtai;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:28
 **/
public class DongProxyFactory {
    public static Object getDynProxy(Object target) {
        InvocationHandler handler = new DynProxyLawyer(target);
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
    }
}

测试类:

java 复制代码
package com.gs.designmodel.daili.dongtai;

import com.gs.designmodel.daili.jingtai.ILawSuit;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:29
 **/
public class Test {

    public static void main(String[] args) {
        ILawSuit proxy = (ILawSuit) DongProxyFactory.getDynProxy(new XiaoHua());
        proxy.submit("工资卡流水如下:");
        proxy.defend();
    }
}

结果:

java 复制代码
法庭公告案件进展: submit
老板带着小姨子欠薪跑路了,证据如下: 工资卡流水如下:
法庭公告案件进展: defend
审判结果,10号之前必须还钱

JdK的动态代理实现方法是依赖于接口的,首先使用接口来定义好操作的规范,然后通过Proxy类产生的代理对象调用被代理对象的方法,而这个操作又被分发给 InvocationHandler接口的 invoke方法具体执行。

java 复制代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • proxy:代表当前动态代理对象
  • method:代表正在执行的方法
  • args:代表当前执行方法传入的实参

cglib动态代理:

由于 JDK只能针对实现了接口的类做动态代理,而不能对没有实现的类做操作,所以还是有一定的局限性。CGLib(Code Generation Library)是一个强大、高性能的Code生成类库,它可以在程序运行期间动态扩展类或接口,它的底层是使用java字节码操作框架ASM实现。

​ 定义业务类,被代理的类没有实现任何接口:

java 复制代码
package com.gs.designmodel.daili.cglib;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:33
 *
 * 定义业务类,被代理的类没有实现任何接口
 **/
public class Frank {
    public void submit(String proof) {
        System.out.println("老板带着小姨子欠薪跑路了,证据如下: " + proof);
    }

    public void defend() {
        System.out.println("审判结果,10号之前必须还钱");
    }
}

​ 定义拦截器:在调用目标方法时,会回调 MethodInterceptor接口方法拦截,来实现你自己的代理逻辑:

java 复制代码
package com.gs.designmodel.daili.cglib;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:34
 *
 * 拦截器 类似jdk中的InvocationHandler
 **/
public class CgLibDynProxyLawyer implements MethodInterceptor {


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("submit")) {
            System.out.println("案件提交成功,证据如下: " + Arrays.asList(objects));
        }
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}

​ 定义动态代理工厂,生成动态代理:

java 复制代码
package com.gs.designmodel.daili.cglib;

import org.springframework.cglib.proxy.Enhancer;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:37
 **/
public class CgProxyFactory {

    public static Object getCgLibDynProxy(Object targe) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targe.getClass());
        enhancer.setCallback(new CgLibDynProxyLawyer());
        Object targetProxy = enhancer.create();
        return targetProxy;
    }
}

​ 测试类:

java 复制代码
package com.gs.designmodel.daili.cglib;

/**
 * @author: Gaos
 * @Date: 2023-08-07 10:38
 **/
public class Test {

    public static void main(String[] args) {
        Frank cProxy = (Frank) CgProxyFactory.getCgLibDynProxy(new Frank());
        cProxy.submit("银行卡记录在此");
        cProxy.defend();
    }
}

​ 结果:

css 复制代码
案件提交成功,证据如下: [银行卡记录在此]
老板带着小姨子欠薪跑路了,证据如下: 银行卡记录在此
审判结果,10号之前必须还钱

可以看到 cglib对没有实现任何接口的类做了动态代理,达到了和之前一样的效果。

它的原理是动态生成一个要代理类的子类,子类重写要代理的类所有不是final修饰的方法。在子类种采用方法拦截的技术拦截所有父类方法的调用,然后走自己的逻辑。它要比java反射的jdk动态代理快。

4.总结

JDK和Cglib的区别:

jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

cglib动态代理是利用ASM开源包,对被代理对象类的class文件加载进来,通过修改其字节码生成子类来处理

ASM: 一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

动态代理的常见的运用是在大名鼎鼎的Aop当中。使用了动态代理后,不会侵入业务代码中,在以后的维护过程中对代码毫无影响,广泛地应用到了日志记录、性能统计、安全控制、异常处理等场景中。

相关参考文章:

juejin.cn/post/698123...
blog.csdn.net/ShuSheng000...

refactoringguru.cn/design-patt...

相关推荐
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ几秒前
mapper.xml 使用大于号、小于号示例
xml·java·数据库
一直学习永不止步10 分钟前
LeetCode题练习与总结:迷你语法分析器--385
java·数据结构·算法·leetcode·字符串··深度优先搜索
2401_8712905831 分钟前
Scala 中的Set
开发语言·后端·scala
Tech Synapse32 分钟前
Java将Boolean转为Json对象的方法
java·开发语言·json
小冉在学习44 分钟前
day55 图论章节刷题Part07([53.寻宝]prim算法、kruskal算法)
java·算法·图论
伴野星辰1 小时前
网站视频过大,加载缓慢解决方法【分段加载视频】
java·服务器·音视频
让生命变得有价值1 小时前
k8s 启用 ValidatingAdmissionPolicy 特性
java·容器·kubernetes·kubelet
morning_judger1 小时前
【设计模式系列】享元模式(十五)
java·设计模式·享元模式
Afanda…1 小时前
Git超详细教程
运维·后端·gitee
I_Am_Me_1 小时前
【JavaEE进阶】HTML
java·java-ee·html