首页 > 其他分享 >动态代理与责任链模式

动态代理与责任链模式

时间:2022-12-04 19:45:07浏览次数:35  
标签:拦截器 target 对象 Object 代理 责任 动态 public

  动态代理和责任链设计模式适用范围广,在Spring和MyBatis有着重要的应用,比如SpringAOP、Mybatis的插件技术,想要搞懂当中的技术原理必须掌握上面两个设计模式。

    代理模式可以理解为您要操作一个对象,但是要经过这个对象的“代理”对象去操作。就好似你在一家软件公司做开发,客户发现程序有Bug,会找到商务对接人说,最后商务的同事再找到你去解决问题。“商务”是代理对象,“你”是真实对象。代理模式分为静态代理和动态代理,其作用是可以在真实对象访问之前或者之后加入自定义的逻辑,又或者根据自定义规则来控制是否使用真实对象。

    静态代理是真实对象与代理对象(Proxy)实现相同的接口,代理对象包含真实对象的引用,客户端通过代理对象去访问真实对象,代理对象可以在真实对象访问之前或之后执行其他操作。假设要你设计一个对外开放的商品库存信息查询接口,并且要限制调用方在一天时间内的调用次数。用代理模式代理库存接口,首先在库存查询前检查用户是否有权限访问,然后在查询后要记录用户查询日志,以便根据查询次数,判断调用上限。

 

 

    动态代理是在程序运行时,通过反射机制创建代理对象,实现动态代理方法。动态代理相比于静态代理的好处,是代理对象不用实现真实对象的接口,这样能代理更多方法。因为静态代理是一个接口对应一个类型,如果接口添加新方法,则所有代理类都要实现此方法,所以动态代理脱离了接口实现,一个代理类就能代理更多方法。一些公共代码逻辑也就可以在多个代理方法里复用,例如:数据库事务开启、提交、回滚,这些公共代码都分别是在真实方法调用的前后出现,而动态代理会帮我们把功能代码织入到方法里。在Java中最常用动态代理有两种,一种是JDK动态代理,这是JDK自带的功能;另一种是CGLIB,由第三方提供的一个技术,Spring是用了JDK代理和CGLIB两种,两者的区别是,JDK代理要提供接口作为于代理参数才能使用,而CGLIB不需要提供接口,只要一个非抽象类就能代理,适用于一些不能提供接口的场景。

1. JDK动态代理

  (1)定义真实对象接口,因为JDK动态代理要借助接口才能代理对象

public interface SayService {
    void sayHello();
}

public class SayServiceImpl implements SayService {
    @override
    public void sayHello() {
        System.out.println("Hello Friend");    
    }
}

  (2)创建代理类,实现java.lang.reflect.InvocationHandler接口

public class JdkProxyExample implements InvocationHandler {
    // 真实对象
    private Object target = null;

    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前");
        System.out.println("调用真实对象");
        Object r = method.invoke(target, args);
        System.out.println("代理后");
        return r;
    }
}

      Proxy.newProxyInstance方法的作用是创建代理对象,并建立代理对象与真实对象的关系。包含3个参数

    • 第1个是类加载器,用于把Java字节码转换成Class类实例对象,这里用了target对象所属的类加载器

    • 第2个是生成的动态代理对象需要挂载到哪些接口下,上面是取了真实对象的接口

    • 第3个是实现InvocationHandler接口的代理类,this表示当前对象

 

      InvocationHandler接口的invoke方法实现代理逻辑,invoke其中参数含义如下

    • proxy:代理对象,就是bind方法生成的对象

    • method:当前调用方法,method.invoke(target, args)调用真实对象方法

    • args:当前调用方法参数

  (3)测试JDK动态代理

public void testJdk() {
    JdkProxyExample jdkProxy = new JdkProxyExample();
    SayService proxy = (SayService) jdkProxy.bind(new SayServiceImpl());
    proxy.sayHello();
}

/*
代理前
调用真实对象
Hello Friend
代理后
*/

 

2. CGLIB动态代理

    JDK动态代理要提供接口才能代理,但在一些不能提供接口的场景下,CGLIB动态代理技术不需要提供接口,只要一个非抽象类就能动态代理。新建类实现MethodInterceptor接口(spring框架的cglib包),使用Enhacer创建代理对象。代码实现如下:

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 CglibProxyExample implements MethodInterceptor {
    public Object bind(Class classz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);
        enhancer.setSuperclass(classz);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用真实对象前");
        Object result = methodProxy.invokeSuper(proxy, args);
        System.out.println("调用真实对象后");
        return result;
    }
}

@Test
public void testCglib() {
    CglibProxyExample cglibProxy = new CglibProxyExample();
    TestService proxy = (TestService)cglibProxy.bind(TestService.class);
    proxy.sayHello();
}

/*
调用真实对象前
Hi hello
调用真实对象后
*/

 

3. 拦截器

    由于动态代理一般不好理解,通常会设计一个拦截器接口提供给开发者使用,这样只需要知道拦截器接口方法、含义和作用即可,无须知道动态代理是怎么实现。拦截器接口设计如下:

public interface Interceptor {
    boolean before(Object target);

    Object around(Object target, Method method, Object[] args);

    void after(Object target);

    void afterThrowing(Object target);

    void afterReturning(Object target);
}

  假定拦截器使用规则是:在调用真实方法前,先访问before方法,如果返回true,执行真实对象方法,否则执行around方法代替真实方法的调用。after方法在真实方法或around调用后执行,如果真实方法或around调用异常,执行afterThrowing方法,否则执行afterReturning方法。

    定义了以上规则,开发者只要知道拦截器怎样使用,不需要知道动态代理怎么实现。

    下面代码演示如何使用上面定义的拦截器

public class SayServiceInterceptor implements Interceptor {
    @Override
    public boolean before(Object target) {
        System.out.println("代理前执行before方法");
        // 不执行真实对象的方法
        return false;
    }

    @Override
    public Object around(Object target, Method method, Object[] args) {
        System.out.println("真实对象方法被替换,执行around");
        return null;
    }

    @Override
    public void after(Object target) {
        System.out.println("代理后执行after方法");
    }

    @Override
    public void afterThrowing(Object target) {
        System.out.println("真实对象方法调用异常,执行afterThrowing方法");
    }

    @Override
    public void afterReturning(Object target) {
        System.out.println("最后执行afterReturning方法");
    }
}

public void testJdk() {
    SayService say = new SayServiceImpl();
    SayServiceInterceptor interceptor = new SayServiceInterceptor();
    SayService proxy = (SayService) JdkProxyExample.bind(say, interceptor);
    proxy.sayHello();
}

/**
代理前执行before方法
真实对象方法被替换,执行around
代理后执行after方法
最后执行afterReturning方法
*/

    上面规则用动态代理实现代码如下:

public class JdkProxyExample implements InvocationHandler {
    // 真实对象
    private Object target = null;
    // 拦截器
    private Interceptor interceptor;

    public JdkProxyExample(Object target, Interceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    public static Object bind(Object target, Interceptor interceptor) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new JdkProxyExample(target, interceptor));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptor == null) {
            return method.invoke(target, args);
        }

        Object result = null;
        boolean exceptionFlag = false;
        try {
            if (interceptor.before(target)) {
                result = method.invoke(target, args);
            }else {
                result = interceptor.around(target, method, args);
            }
        }catch (Exception e) {
            exceptionFlag = true;
        } finally {
            interceptor.after(target);
        }

        if(exceptionFlag) {
            interceptor.afterThrowing(target);
        }else {
            interceptor.afterReturning(target);
        }

        return result;
    }
}

    设计拦截器能简化了动态代理的使用方法,使程序更简单。在实际使用场景中拦截器实现后,要在开发者的程序进行xml配置,或者在实现类添加注解标识等方式,来查找到拦截器实现类,然后反射创建并加载到程序里。SpringAOP也是通过@Aspect注解创建切面(拦截器),@execution定义连接点拦截方法。

 

4. 责任链模式

    设计拦截器去代替动态代理,然后将拦截器的接口提供给开发者用,从而简化开发者的开发难度,但是拦截器可能会有多个,举个例子,您要请假一周,然后在OA上提交请假申请单,要经过项目经理、部门经理、人事等多个角色的审批,每个角色都会对申请单拦截、修改、审批等。如果把请假申请单看做一个对象,则它会经过三个拦截器的拦截处理。当一个对象在一条链上被多个拦截器拦截处理时,我们把这样的设计模式称为责任链模式。前一个拦截器的返回结果会作用于后一个拦截器,代码上的实现是用后一个拦截器去代理了前一个拦截器的方法,以此类推,层层代理。最终结果如下图:

 代码实现如下:

@Test
public void testInterceptor() {
    SayService target = new SayServiceImpl();
    SayService proxy1 = (SayService)JdkProxyExample.bind(target, new SayServiceInterceptor("proxy1"));
    SayService proxy2 = (SayService)JdkProxyExample.bind(proxy1, new SayServiceInterceptor("proxy2"));
    SayService proxy3 = (SayService)JdkProxyExample.bind(proxy2, new SayServiceInterceptor("proxy3"));
    proxy1.sayHello();
}

/**
proxy3:代理前执行before方法
proxy2:代理前执行before方法
proxy1:代理前执行before方法
Hello Friends
proxy1:代理后执行after方法
proxy2:代理后执行after方法
proxy3:代理后执行after方法
*/

  before方法执行顺序是从最后一个拦截器到第一个拦截器,而after方法是从第一个拦截器到最后一个。责任链模式的优点在于我们可以在传递链中加上新的拦截器,增加拦截逻辑,但缺点是每增加一层代理反射,程序性能越差。

标签:拦截器,target,对象,Object,代理,责任,动态,public
From: https://www.cnblogs.com/linjianhui/p/16950444.html

相关文章

  • 16.【C语言进阶】动态内存管理
    为什么存在动态内存分配栈区上的内存开辟intval=20;//在栈空间上开辟四个字节chararr[10]={0};//在栈空间上开辟10个字节的连续空间这样直接在函数体中开辟内存......
  • 动态链表的创建
    一、内存的两种访问方式1、随机访问方式对于数组来说,他的所有元素在内存中是连续存储的,因而很容易计算出每个元素的内存地址,从而可以直接访问数组中的任意一个元素。2、顺序......
  • 【vue-router 4.x】使用addRoute加载动态路由时,刷新页面后出现空白页和控制台报错 [Vu
    "vue-router":"^4.1.6"遇到的问题动态路由刷新后,出现空白页动态路由刷新后,控制报错[VueRouterwarn]:Nomatchfoundforlocationwithpath"/***/index"1.动态......
  • vue中父元素动态获取iframe高度
    背景:在vue开发中(不论是vue2还是vue3),我在页面中打印都可以拿到document.getElementById('iframe')的DOM节点,但就是在页面中不显示,需要等待iframe挂载上去,再进行响应的DOM操......
  • 动态规划_最长公共子序列
    '示例1:输入:text1="abcde",text2="ace"输出:3解释:最长公共子序列是"ace",它的长度为3。'示例2:输入:text1="abc",text2="abc"输出:3解释:最长公共子序列是......
  • WeetCode3 暴力递归->记忆化搜索->动态规划
    笔者这里总结的是一种套路,这种套路笔者最先是从左程云的b站视频学习到的本文进行简单总结系列文章目录和关于我一丶动态规划的思想使用dp数组记录之前状态计算的最佳......
  • 动态规划_整数拆分
    '给定一个正整数n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。返回你可以获得的最大乘积。'示例1:'输入:2'输出:1'解释:2=1+1,1×1=1。'......
  • 财务自由之路:责任意味着什么
    没有自信的生活只能叫生存。你永远都不知道自己的潜能。你从不冒险,从不成长,从不应对处境做出反抗,从不开发自己真正的潜能。一个没有自信的人,只能是一个一事无成,一无所......
  • 动态分区分配算法
    一、首次适应算法(FirstFit)算法思想每次都从低地址开始查找,找到第一个能满足大小的空闲分区。空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(......
  • Maven 动态切换多 profiles 编译环境
    pom.xml配置<!--Maven动态切换多profiles编译环境--><profiles><!--开发环境--><profile><id>dev</id><!--默认激活--><activation......