GOF23设计模式之代理模式-Proxy

代理模式-Proxy

概述

  • 定义:为其他对象提供一种代理,已控制对这个对象的访问。

  • 代理对象在客户端和目标对象之前起到中介的作用

  • 类型:结构型设计模式

  • 适用场景:

    • 保护目标对象
    • 增强目标对象
  • 优点:

    • 代理模式能将代理对象与真实调用的目标对象分离
    • 一定程度上降低了程序的耦合性,提高可扩展性
    • 保护目标对象
    • 增强目标对象
  • 缺点:

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

    • 按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

比较

  • 装饰者模式与代理模式

    • 代理模式是控制访问
    • 装饰者模式是为对象加上行为
  • 适配器模式与代理模式

    • 适配器模式是主要改变所考虑对象的接口
    • 代理模式是不能改变代理对象的接口的

静态代理

  • Coding
    订单Service接口:

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

    订单Service实现类:

    public class OrderServiceImpl implements IOrderService {
    
        @Override
        public int saveOrder(Object order) {
            System.out.println("Service层调用Dao层添加Order");
            return 1;
        }
    
    }
    

    静态代理类:

    public class OrderServiceStaticProxy {
        private IOrderService iOrderService = new OrderServiceImpl();
    
        public int saveOrder(Object order){
            beforeMethod( );
            int result = iOrderService.saveOrder(order);
            afterMethod();
            return result;
        }
    
        private void beforeMethod(){
            System.out.println("订单保存前,静态代理处理逻辑。。。。");
        }
        private void afterMethod(){
            System.out.println("静态代理 after code");
        }
    }
    
    

    测试类:

    public class StaticProxyMain {
        public static void main(String[] args) {
            OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
            orderServiceStaticProxy.saveOrder("订单1");
        }
    }
    
    
  • 静态代理总结:

    • 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。

    • 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。

动态代理

JDk动态代理

在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。

  • coding

    
    /**
     * @Description: JDK动态代理
     * @Author: yong.zheng 
     * @Date: 2021/12/21 19:38
     **/
    public class DynamicProxyHandler implements InvocationHandler {
    
        private Object object;
    
        public DynamicProxyHandler(final Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            beforeMethod();
            Object result = method.invoke(object, args);
            afterMethod();
            return result;
        }
    
        private void beforeMethod(){
            System.out.println("订单保存前,静态代理处理逻辑。。。。");
        }
        private void afterMethod(){
            System.out.println("静态代理 after code");
        }
    }
    

    测试类

    public class DynamicProxyMain {
        public static void main(String[] args) {
            OrderServiceImpl orderService = new OrderServiceImpl();
            DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(orderService);
    
            IOrderService  proxyService = (IOrderService) Proxy.newProxyInstance(orderService.getClass().getClassLoader(),
                    orderService.getClass().getInterfaces(),
                    dynamicProxyHandler);
            proxyService.saveOrder("order ..");
        }
    }
    
    • 注意Proxy.newProxyInstance()方法接受三个参数:

      • ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
      • Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
      • InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
    • 动态代理总结:
      虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

CGlib动态代理

  • JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
  • Coding
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 CglibProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(final Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * 1. 代理对象的类是被代理类的子类
     * <p>
     * 2. 代理对象重写了父类的所有方法
     * 重写的结果时:调用代理对象的任何方法都会触发MethodInterceptor.intercept
     * <p>
     * 3. 当调用代理对象的方法时会将  代理对象自身/真实方法/调用参数/代理方法(调用时直接触发的方法,代理对象对父类方法的重写)  封装起来作为MethodInterceptor.intercept方法的入参
     * <p>
     * 4. method.invoke(真实对象,params) 等价于 methodProxy.invokeSuper(代理对象,params)
     * <p>
     * 5. 注意:第一个参数(代理对象)慎用
     * 5.1 切勿在MethodInterceptor.intercept方法中调用第一个参数proxy的任何方法,会引发无限递归最终OOM
     * proxy.toString->MethodInterceptor.intercept->proxy.toString->MethodInterceptor.intercept->......
     *
     * @param proxy       代理对象
     * @param method      被代理的类中的真实方法
     * @param objects     调用时传入的参数
     * @param methodProxy 代理对象中的代理方法
     * @return 方法返回值
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        beforeMethod();
        //这样也是允许的,等价于
        Object result = method.invoke(target, objects);

        //这样调用是允许的
        //Object result = methodProxy.invokeSuper(proxy, objects);

        //这样调用是不允许的,将会报强转异常,方法中会尝试将第一个参数强转为代理对象
        //methodProxy.invokeSuper(new T(), objects);

        //这样会导致无限递归 method.invoke(proxy, objects)->MethodInterceptor.intercept->method.invoke(proxy, objects)->MethodInterceptor.intercept->....
        //method.invoke(proxy, objects);

        //这样将会导致无限递归,原因和method.invoke(proxy, objects);相同
        //methodProxy.invoke(proxy, objects);
        afterMethod();
        return result;
    }



    private void beforeMethod() {
        System.out.println("订单保存前,静态代理处理逻辑。。。。");
    }

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


测试类

public class CglibProxyMain {
    public static void main(String[] args) {
        IOrderService orderService = new OrderServiceImpl();
        CglibProxy cglibProxy = new CglibProxy();
        OrderServiceImpl buyHouseCglibProxy = (OrderServiceImpl) cglibProxy.getInstance(orderService);
        buyHouseCglibProxy.saveOrder("order ..");
    }
}
  • Cglib动态代理注意事项:
  1. 代理对象的类是被代理类的子类
  2. 代理对象重写了父类的所有方法
    重写的结果时:调用代理对象的任何方法都会触发MethodInterceptor.intercept
  3. 当调用代理对象的方法时会将 代理对象自身/真实方法/调用参数/代理方法(调用时直接触发的方法,代理对象对父类方法的重写) 封装起来作为MethodInterceptor.intercept方法的入参
  4. method.invoke(真实对象,params) 等价于 methodProxy.invokeSuper(代理 对象params)
  5. 注意:第一个参数(代理对象)慎用
    切勿在MethodInterceptor.intercept方法中调用第一个参数proxy的任何方法,会引发无限递归最终OOM
    proxy.toString->MethodInterceptor.intercept->proxy.toString-> MethodInterceptor.intercept->…
  • CGLIB代理总结
    CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。.

Jdk动态代理与cglib动态代理对比:

  • jdk动态代理只能针对有接口接口方法进行动态代理;
  • cglib基于继承实现的动态代理,无法对static、final修饰的类进行动态代理;
  • cglib基于继承实现的动态代理,无法对static、private修饰的方法进行动态代理;

Spring AOP

  • AOP:面向切面编程。

使用详解

略!

实现原理

在这里插入图片描述

  1. 代理的创建
    创建代理工厂:拦截器数组,目标对象接口数组,目标对象。
    创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。
    当调用 getProxy 方法的时候,会根据接口数量大余 0 条件返回一个代理对象(JDK or Cglib)。
    注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器,用于控制整个 AOP 的流程。
  2. 代理的调用
    当对代理对象进行调用时,就会触发外层拦截器。
    外层拦截器根据代理配置信息,创建内层拦截器链。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器。而这个拦截器链设计模式就是职责链模式。
    当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法,最后返回。

在这里插入图片描述

代理方式

在这里插入图片描述
在这里插入图片描述

  • 如果目标对象实现了接口就默认会采用jdk的动态代理;
  • 如果目标对象没有实现接口就会采用cglib动态代理;
  • 如果目标对象实现了接口,且强制使用cglib动态代理,就采用动态代理。

Spring AOP 如何实现链式调用的?

采用了责任链模式!暂不介绍。

Spring AOP 的经典应用

  1. Spring : @transactinal
  2. Spring Security: 安全验证@PreAuthorize
  3. Spring Cache:@Cacheable

版权声明:本文为zhengyong14350原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。