设计模式之代理模式

定义

代理模式又叫委托模式,是为某个对象提供一个代理对象,并且由代理对象控制对原对象的访问。代理模式通俗来讲就是我们生活中常见的中介。
我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原真实对象的前提下,提供额外的功能操作,扩展真实对象的功能。比如说在真实对象的某个方法执行前后你可以增加一些自定义的操作。

类型

结构型

UML图

在这里插入图片描述

角色

抽象角色(Subject):通过接口或抽象类声明真实角色实现的业务方法。
代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

示例

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。

静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将真实目标对象注入进代理类,然后在代理类的对应方法调用真实目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对真实目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。是不是感觉和spring AOP 面向切面编程很像。

我们每次在Controller层中实现向数据库insert数据,一般都是经过Service层、Dao层,再到数据库,这里我们将Service层进行代理。

Order类,实体类。

public class Order {

    private Object orderInfo;
    private Integer userId;

    public Object getOrderInfo() {
        return orderInfo;
    }

    public void setOrderInfo(Object orderInfo) {
        this.orderInfo = orderInfo;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }
}

Dao层接口

public interface IOrderDao {
    int insert(Order order);
}

Dao层实现类

public class OrderDaoImpl implements IOrderDao {
    public int insert(Order order) {
        System.out.println("Dao层添加Order成功");
        return 1;
    }
}

Service接口

public interface IOrderService {
    int saveOrder(Order order);
}

Service实现类

public class OrderServiceImpl implements IOrderService {

    private IOrderDao iOrderDao;
    public int saveOrder(Order order) {
        // 使用Spring注解会自己注入,这里就自己直接new
        iOrderDao = new OrderDaoImpl();
        System.out.println("Service层调用Dao层添加Order");
        return iOrderDao.insert(order);
    }
}

OrderServiceStaticProxy类(Service层的静态代理类)

public class OrderServiceStaticProxy {

    private IOrderService iOrderService;

    public int saveOrder(Order order){
        //方法增强,比如分库、校验、安全等处理(前置方法)
        beforeMethod(order);

        iOrderService = new OrderServiceImpl();
        int result = iOrderService.saveOrder(order);

        //方法增强,比如释放资源等处理(后置方法)
        afterMethod();
        return result;
    }

    private void beforeMethod(Order order){
        int userId = order.getUserId();
        //分库
        int dbRouter = userId % 2;
        System.out.println("静态代理分配到【db"+dbRouter+"】处理数据");

        //todo 设置dataSource;
        DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));
        System.out.println("开始执行代理任务");
        System.out.println("静态代理 before code");

    }
    private void afterMethod(){
        System.out.println("静态代理 after code");
        System.out.println("执行完毕");
    }

}

Test(应用层)

public class Test {
    public static void main(String[] args) {
        Order order = new Order();
        order.setUserId(2);
        OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
        orderServiceStaticProxy.saveOrder(order);
    }
}

输出结果
在这里插入图片描述

动态代理

相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,通过动态代理类我们可以完成全部的代理功能。

静态代理是在代码编译后就已经确定被代理的对象了。
动态代理是在代码运行时,通过反射机制在运行时动态生成类字节码并加载到JVM中的,也就是说,在运行过程中才确立要代理的对象。这样能够代理各种类型的对象。

Spring AOP、RPC 框架的实现都依赖了动态代理,动态代理在框架中几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。

对于Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等

JDK动态代理简单介绍

JDK动态代理,java.lang.reflect.InvocationHandler接口java.lang.reflect.Proxy类是核心。

Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
  • loader :类加载器,用于加载代理对象。
  • interfaces : 被代理类实现的一些接口;
  • h : 实现了 InvocationHandler 接口的对象;

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
     /**
      * proxy :动态生成的代理类
      * method : 与代理类对象调用的方法相对应
      * args : 当前 method 方法的参数
      */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

总结起来就是:我们通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在代理方法执行前后做什么其他的事情

JDK 动态代理类使用步骤
  1. 定义一个接口及其实现类;
  2. 实现 InvocationHandler 接口 并重写invoke方法,在 invoke
    方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
    interfaces,InvocationHandler h) 方法创建代理对象
示例

OrderServiceDynamicProxy类(Service层的JDK动态代理类)。

public class OrderServiceDynamicProxy implements InvocationHandler {

    // 代理类中的真实对象
    private Object target;

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

    public Object bind(){
        Class cls = target.getClass();
        //主要通过Proxy.newProxyInstance()方法获取某个类的代理对象
        return Proxy.newProxyInstance(cls.getClassLoader(), //目标类的类加载
                cls.getInterfaces(),  // 被代理类实现的一些接口;
                this); // 实现了 InvocationHandler 接口的代理对象
    }




    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object argObject = args[0];
        // 执行代理业务方法之前,我们可以添加前置操作
        beforeMethod(argObject);

        Object object = method.invoke(target,args);
        //同理,可以添加后置操作
        afterMethod();
        return object;
    }

    private void beforeMethod(Object obj){
        int userId = 0;

        if(obj instanceof Order){
            Order order = (Order)obj;
            userId = order.getUserId();
        }
        int dbRouter = userId % 2;
        System.out.println("动态代理分配到【db"+dbRouter+"】处理数据");

        //todo 设置dataSource;
        DataSourceContextHolder.setDBType("db"+String.valueOf(dbRouter));

        System.out.println("开始执行代理任务");
        System.out.println("动态代理 before code");
    }

    private void afterMethod(){
        System.out.println("动态代理 after code");
        System.out.println("执行完毕");
    }


}

invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。

Test

public class Test {

    public static void main(String[] args) {

        Order order = new Order();
        order.setUserId(1);
        IOrderService orderServiceDynamicProxy = (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();

        orderServiceDynamicProxy.saveOrder(order);
    }
}

输出
在这里插入图片描述

CGLib动态代理

CGLib简单介绍

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,可以使用 CGLib动态代理

CGLIB (opens new window)(Code Generation Library)是一个基于ASM (opens new window)的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。

很多知名的开源框架都使用到了CGLIB (opens new window), 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

在 CGLIB 动态代理机制中 MethodInterceptor 接口Enhancer 类是核心。
需要我们自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

public interface MethodInterceptor extends Callback {
    // 拦截被代理类中的方法
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

obj :被代理的对象(需要增强的对象)
method :被拦截的方法(需要增强的方法)
args :方法入参
proxy :用于调用原始方法

我们可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

CGLIB 动态代理类使用步骤
  1. 定义一个类;
  2. 实现 MethodInterceptor 接口并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类;

不同于 JDK 动态代理不需要额外的依赖。CGLIB (opens new window)(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要添加相关依赖。我这里添加的依赖是

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.10</version>
        </dependency>
    </dependencies>
示例

OrderServiceCGLibDynamicProxy类(Service层的CGLib动态代理类)。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class OrderServiceCGLibDynamicProxy implements MethodInterceptor {

    public OrderServiceImpl bind(){
        Enhancer enhancer = new Enhancer();//生成代理对象
        enhancer.setSuperclass(OrderServiceImpl.class);//设置对谁进行代理
        enhancer.setCallback(this);//代理要做什么,设置拦截器
        OrderServiceImpl orderService = (OrderServiceImpl) enhancer.create();//创建代理对象
        return orderService;
    }
    /**
     * @param o           代理对象(增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param objects     方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforeMethod(objects[0]);
        Object object = methodProxy.invokeSuper(o,objects);
        afterMethod();
        return object;
    }

    private void beforeMethod(Object object){
        if(object instanceof Order){
            Order order = (Order)object;
            int userId = order.getUserId();
            int dbRouter = userId % 2;
            System.out.println("CGLib动态代理分配到 【db"+dbRouter+"】处理数据");
            //TODO 分库操作
        }
        //TODO 其他类型处理
        System.out.println("开始执行代理任务");
        System.out.println("CGLib动态代理 before code");
    }

    private void afterMethod(){
        System.out.println("CGLib动态代理 after code");
        System.out.println("执行完毕");
    }
}

Test

public class Test {
    public static void main(String[] args) {
        Order order = new Order();
        order.setUserId(1);
        OrderServiceCGLibDynamicProxy orderServiceCGLibDynamicProxy =
                new OrderServiceCGLibDynamicProxy();
        IOrderService iOrderService = orderServiceCGLibDynamicProxy.bind();
        iOrderService.saveOrder(order);
    }
}

输出
在这里插入图片描述

JDK 动态代理和 CGLIB 动态代理对比

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  • 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显

代理模式总结

优点

  • 实现了访问者与访问对象之间的解耦。
  • 代理模式在应用层与对象之间起到中介作用,保护了对对象的访问。
  • 职责清晰(单一职责):真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,代理模式可以在代理过程中增加逻辑,如Spring框架的AOP。

缺点

  • 增加代理会使程序请求处理变慢。
  • 类的数量变多,系统更加复杂。

应用场景

  1. 远程代理(Remote Proxy)
    为一个位于不同的地址空间的对象提供一个本地的代理对象。这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)
  2. 虚拟代理(Virtual Proxy)
    根据需要创建开销很大的对象。如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  3. 保护代理(Protection Proxy)
    控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
  4. 智能指引(Smart Reference)
    取代了简单的指针,它在访问对象时执行一些附加操作。
  5. Copy-on-Write代理
    它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆
  6. 引用计数(reference counting)指针对象。
    当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。

References:

  • https://blog.csdn.net/weixin_34007906/article/details/92172234
  • https://baike.baidu.com/item/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/8374046?fr=aladdin
  • https://javaguide.cn/java/basis/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E8%AF%A6%E8%A7%A3/#
  • https://blog.csdn.net/qq_37960603/article/details/104101825
  • https://coding.imooc.com/class/270.html?utm_term=%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F&utm_campaign=SEM&utm_medium=33&_channel_track_key=V4q7e28K&utm_source=szjineng5&bd_vid=11577451146916505204
  • https://www.cnblogs.com/jy107600/p/8657217.html
  • https://www.jianshu.com/p/0021bc657203

(写博客主要是对自己学习的归纳整理,资料大部分来源于书籍和网络资料,整理不易,但是难免有不足之处,如有错误,请大家评论区批评指正。同时感谢广大博主和广大作者辛苦整理出来的资源。)

  • 4
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值