动态代理——拦截器——责任链——AOP面向切面编程底层原理详解(迪丽热巴版)

9 篇文章 120 订阅
2 篇文章 60 订阅

   

目录

                               动态代理模式详解

前言

什么是代理模式

如何进行代理

静态代理

动态代理

JDK动态代理

CGLIB动态代理

拦截器

责任链模式

博客文章版权申明


                               动态代理模式详解

前言


代理模式是设计模式中非常重要的一种设计思维,对于SSM框架中的spring也好,mybatis也好,无不体现出了动态代理的重要应用。因此,在学习SSM框架之前,能够理解动态代理是非常重要的。

什么是代理模式


代理的意义在于生成一个占位(又称代理对象),来代理真实的对象,从而控制真实对象的访问。

那么,通俗的来讲,代理到底是什么意思呢?

假设,你是一家大公司的大老板,现在需要推销你公司的一款产品,你需要请一位“明星”作为你产品的代言人,例如,现在当红的“迪丽热巴”。

首先,我们需要能够联系得到“迪丽热巴”,我们在网上搜一搜热巴的相关信息,然而我们却并没有发现热巴在网上留下自己的联系方式(当然是不可能留下的,不然电话都要被打爆),仅仅只留下了一个工作联系的邮箱:

这个邮箱应该就是热巴的经纪人的工作邮箱了,实际上我们并不能够直接联系上热巴小姐姐,但是我们可以通过她经纪人的邮箱,先联系她的上经纪人。

假设我们已经联系上了热巴小姐姐的经纪人,并且先询问了经纪人热巴小姐姐最近是否有档期啊?代言的价钱大概是多少呢?未来还有什么合作方式可以继续呢?如果经纪人告诉你,热巴小姐姐最近有档期,并且代言的价格你也可以接受,就可以顺利的约上我们的主角“迪丽热巴”了,如果经纪人告诉你热巴小姐姐最近需要休息,不接广告,商演,并且价格你也难以接受,那么你还没有见到热巴小姐姐就已经失败了。

假设,你已经通过了经纪人这一步,顺利的见到了热巴的真人啦!

接下来,你就可以直接命令热巴小姐姐帮代言你的产品啦!

正当你以为事情已经结束了,产品可以大卖特卖啦!别急,经纪人还找你有事呢!拍完广告,该向经纪人支付代言费用了,并且为了后续能够更好的合作,你可能还需要和热巴的经纪人签订一些合同等等.......。

上述讲的这个案例中,热巴小姐姐的经纪人其实也就是热巴的代理人,那么为什么需要一个代理人呢?因为这样热巴就能专心的演戏干自己的事情,而不用再操心其他与演戏无关的一些商业细节,这些商业细节,谈判全部由代理人做了,只有与代理人谈判成功了,你才能见到真正的“迪丽热巴”。

因此,我们可以总结一下代理的作用啦:代理的作用是,在真实对象访问之前或者之后加入对应的逻辑,或者根据其他规则控制是否使用真实对象,显然在上述这个例子里,经纪人控制了需要代言产品的公司对迪丽热巴的直接访问。

如何进行代理


经过上面的论述,我们知道经纪人和热巴是代理和被代理的关系,需要商演的公司是通过经纪人去访问明星的。此时,商演公司就是程序中的调用者,经纪人就是代理对象,明星就是真实对象。我们需要在调用者调用对象之前产生一个代理对象,而这个代理对象需要和真实对象建立代理关系,所以代理必须分为两个步骤:

1.代理对象和真实对象建立代理关系。

2.实现代理对象的代理逻辑方法。

而在代理中,又分为两种类型的代理方法,一种叫做“静态代理”,还一种叫做所谓的“动态代理”。接下来就让我们来看一看这两种代理是如何实现的,又各自具有怎样的特点呢?

静态代理


首先,我们先定义一个Advertisement接口,该接口中只有一个display方法,为广告代言主要实现方法,接口定义如下:

接下来,我们的在定义一个热巴小姐姐类Dilireba,并且实现Advertisement接口中的display广告代言方法:

,接下来,我们试一试直接调用迪丽热巴的display方法看看是怎样的:

我们发现,此时迪丽热巴小姐姐的display()方法竟然被直接调用了,那么我们为了保护好热巴小姐姐,需要给她请一个经纪人,作为她的代理对象,以后有公司需要商演或者代言产品时,可以直接调用经纪人(代理对象)的display()方法,经纪人会帮助热巴小姐姐做好商演前的准备工作和善后工作的,因此,经纪人应该也需要和热巴小姐姐实现同样的一个Advertisement接口,并实现其中的display方法:

public class Agent implements Advertisement {
    Dilireba dlrb;

    public Agent(Dilireba dlrb) {
        this.dlrb = dlrb;
        //经纪人和热巴建立代理关系
    }

    @Override
    public void display() {
        //经纪人的代言方法,实际上内部是调用了热巴的代言方法
        System.out.println("迪丽热巴的经纪人正在与您谈判中.....");
        //代理逻辑
        dlrb.display();
        //真实对象(迪丽热巴)的代言方法
        System.out.println("迪丽热巴的经纪人正在完成后续工作......");
        //代理逻辑
    }
}

以后,商业公司如果需要热巴小姐姐的代言,只需调用她的经纪人(代理对象)的display方法即可了:

public class StaticProxy {
    public static void main(String[] arg){
        Dilireba dlrb=new Dilireba();
        Agent agent=new Agent(dlrb);
        //代理对象和真实对象建立代理关系
        agent.display();
        //直接调用经纪人的display方法,防止热巴的display方法被直接调用

    }
}

我们在来看一下现在的结果:

我们发现,热巴小姐姐成功的完成了代言工作,并且经纪人也保护了热巴,在热巴执行display()方法之前进行了一系列的谈判工作,在代言结束后也执行了善后工作,因此,我们在来看一下上面讲的代理模式的意义是啥:

代理的意义在于生成一个占位(又称代理对象),来代理真实的对象,从而控制真实对象的访问。

大致的结构如下图所示:

这样看上去,静态代理貌似能够很好的胜任代理工作,但是,静态代理由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。所以为了提高程序员的工作效率,动态代理就由此而生了。

动态代理


在Java中有多种动态代理技术,比如JDK,CGLIB,Javassist,ASM,但是其中最常用的动态技术是这两种:一种是JDK动态代理,这是JDK自带的功能,还有一种就是CGLIB动态代理技术,这是第三方提供的一种技术。目前,Spring常用JDK和CGLIB,而MyBaits使用了Javassist,无论使用哪种动态代理技术,其实他们的理念都是很相似的。

我们主要讨论最常用的两种动态代理模式:JDK和CGLIB代理。

那么,这两种动态代理方式有什么区别呢?什么时候用JDK代理,什么时候又用CGLIB代理模式呢?

在JDK动态代理中,我们必须使用接口,而CGLIB不需要,所以使用CGLIB会更简单一些,下面依次讨论这两种最常用的动态代理。

JDK动态代理


JDK动态代理是java.lang.reflect.*包提供的方式,我们前面讲了,JDK代理的一个显著的要求就是被代理的类一定是实现了接口的类,才能够被生成代理对象。所以我们先定义一个接口(还是用我们上面静态代理的Advertisement接口吧):

public interface Advertisement {
    public void display();//广告代言的主要实现方法
}

这应该是最简单的Java接口和实现类的关系了吧。那么接下来我们就可以开始动态代理了。按照我们之前的分析,先要建立起代理对象和真实对象的关系,然后在实现代理逻辑,所以一共分为两个步骤。

首先对于“建立起代理对象和真实对象的关系”这个条件,我们很简单的就能够实现,只要在设计代理对象的类时,在其中添加一个真实对象的引用就好了,则“代理对象”想什么时候调用真实对象,想怎么调用都可以为所欲为了。那么,又如何实现代理逻辑呢?

别怕,Java给我们提供了一个方案,那就是提供一个代理逻辑的接口,任何类只要实现了这个“代理逻辑接口”,则这个类就具有了“代理逻辑”,这个代理逻辑的接口就是java.lang.reflect.InvocationHandler,让我们来看看这个接口长啥样吧!

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

这个接口貌似没有我们想象的那么复杂的嘛,里面只有一个invoke方法需要我们去实现,让我们来看看参数,首先proxy为代理对象,method为当前调度方法,args是当前调度方法的参数。那让我们来建立起第一个代理逻辑吧,假设为热巴小姐姐的【广告】代理逻辑。

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

public class AdvertisementLogic implements InvocationHandler {
        private Dilireba dlrb=null;
        //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定

    public AdvertisementLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //绑定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在与您谈判有关迪丽热巴【广告代言】的前期具体细节......");
        method.invoke(dlrb,args);
        //调用真实对象的方法
        //运用了JAVA中反射的知识,还不了解反射的同学建议先去学习JAVA的反射再来理解
        System.out.println("代理正在与您进行迪丽热巴【广告代言】的后期善后工作......");
        return null;
    }
}

此时,我们就把一个【代理逻辑】和一个【真实对象】进行了绑定,那么肯定有同学要问了,你绑定代理逻辑和真实对象有啥用?我要的代理对象呢???你这不是瞎扯淡吗?别急别急!我们只要在调用一下java.lang.reflect包中Proxy类的静态方法newProxyInstance方法即可,该方法的声明如下:

其中,loader表示加载真实对象的类加载器,interfaces表示真实对象已经实现了的所有接口组成的数组,h就是我们上面讲的代理逻辑。

接下来让我们来完成最后一步吧,产生代理对象,并调用代理对象的display方法而不是真实对象的display方法:

import java.lang.reflect.Proxy;


public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dlrb=new Dilireba();
        //实例化一个迪丽热巴出来
        AdvertisementLogic advertisementLogic=new AdvertisementLogic(dlrb);
        //把【广告代理】的逻辑类与真实对象迪丽热巴绑定起来
        Advertisement jingjiren= (Advertisement) Proxy.newProxyInstance(dlrb.getClass().getClassLoader(),dlrb.getClass().getInterfaces(),advertisementLogic);
        //因为JDK为我们生成的动态代理对象也会去实现真实对象实现了的所有的接口
        //所以此处我们可以直接使用Advertisement接口来接受JDK动态代理生成的代理对象【经纪人】
        jingjiren.display();//执行代理对象【经纪人】的display()方法
    }
}

来看一下看最后输出的结果是怎样的:

这样,JDK的动态代理技术不仅使生成的代理对象也实现了真实对象的所有接口,并且把代理对象与代理逻辑也绑定在一起了。

热巴小姐姐自从有了经纪人的代理后,业务效率大幅提神,也能够专心提升演技了,这不,热巴小姐姐不仅仅接广告代言了,还开始接“演电视剧”了,可是,演电视剧和广告代言这两种业务的差距太大了,原来的经纪人不能够执行“演电视剧”的代理逻辑。

那么,为了能够让热巴小姐姐成功的接演电视剧,是不是需要再按上面的步骤另外重新设计一个经纪人(代理对象)呢?当然不用这么麻烦了,让我们来看一看JDK动态代理具体是怎么运行的吧!

还记得我们曾经调用过经纪人的display()方法吗?就是下面这一行代码:

 jingjiren.display();//执行代理对象【经纪人】的display()方法

实际上,当代理对象的任意方法被执行后,代理对象就会调用下面这个函数,并且把自身作为proxy参数,把代理对象调用的方法作为method参数,代理对象调用方法的参数作为args,一起给传入代理逻辑类AdvertisementLogic中的invoke()方法里作为参数,这样,在invoke()方法里,就能够通过反射调用到真实对象的真实方法(method.invoke(dlrb,args)),并且在调用真实方法前后可以加入一些其他的控制逻辑。

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

public class AdvertisementLogic implements InvocationHandler {
        private Dilireba dlrb=null;
        //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定

    public AdvertisementLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //绑定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在与您谈判有关迪丽热巴【广告代言】的前期具体细节......");
        method.invoke(dlrb,args);
        //调用真实对象的方法
        //运用了JAVA中反射的知识,还不了解反射的同学建议先去学习JAVA的反射再来理解
        System.out.println("代理正在与您进行迪丽热巴【广告代言】的后期善后工作......");
        return null;
    }
}

大致的调用过程如下图所示:

看到没有?在上图中,真正实现代理逻辑的是代理逻辑类,当代理对象的方法被调用时,【代理对象】并不直接调用【真实对象】,而是直接调用【代理逻辑类】并传给它需要的参数,由【代理逻辑类】来实现真正的代理逻辑,而【代理逻辑类】不仅可以执行自己的代理逻辑,还可以随时调用【真实对象】的方法,从而完成了整个的代理。因此,热巴小姐姐如果需要【拍电视剧】的话,我们只要在去实现一个【拍电视剧】的【代理逻辑类】就好了,最后把它交给JDK的动态代理方法【Proxy.newProxyInstance()】就好了,接下来我们在看一看【拍电视剧】的【代理逻辑类】是如何实现的:

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


public class TvPlayLogic implements InvocationHandler {

    private Dilireba dlrb=null;
    //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定

    public TvPlayLogic(Dilireba dlrb) {
        this.dlrb = dlrb;
        //绑定操作
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理正在与您谈判有关迪丽热巴【拍电视剧】的前期具体细节......");
        method.invoke(dlrb,args);
        //调用真实对象的方法
        //运用了JAVA中反射的知识,还不了解反射的同学建议先去学习JAVA的反射再来理解
        System.out.println("代理正在与您进行迪丽热巴【拍电视剧】的后期善后工作......");
        return null;
    }
}

接下来,我们在测试一下此时这个【逻辑代理类】的效果:

import java.lang.reflect.Proxy;

/**
 * @author 刘扬俊
 * @description
 * @date Created in 2018/10/23 20:10
 */
public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dlrb=new Dilireba();
        //实例化一个迪丽热巴出来
    TvPlayLogic tvPlayLogic=new TvPlayLogic(dlrb);
        //把【拍电视剧】的逻辑类与真实对象迪丽热巴绑定起来
        Advertisement jingjiren= (Advertisement) Proxy.newProxyInstance(dlrb.getClass().getClassLoader(),dlrb.getClass().getInterfaces(),tvPlayLogic);
        //因为JDK为我们生成的动态代理对象也会去实现真实对象实现了的所有的接口
        //所以此处我们可以直接使用Advertisement接口来接受JDK动态代理生成的代理对象【经纪人】
        jingjiren.display();//执行代理对象【经纪人】的display()方法
    }
}

运行一下【电视剧代理对象】的方法试一试的结果如下:

这样,我们就成功的实现了动态代理的【代理逻辑】的改变,这给我们以后使用动态代理带来很大的方便,并且在一些框架的底层很好的体现了。

这就是JDK动态代理,它是一种最常用的动态代理,十分重要。接下来,就让我们来了解一下不用依靠【接口】就能实现的动态代理方式:CGLIB动态代理。

CGLIB动态代理


在知道了JDK的动态代理原理后,学习CGLIB动态代理就不难了,因为它们的原理是相似的。JDK的动态代理逻辑是交给一个实现了【invocationHandler】接口的类来完成的,那么,CGLIB代理其实也是把代理逻辑交给一个实现了【MethodInterceptor】接口的类来完成的。

我们可以想一想,这个接口会不会和JDK的【invocationHandler】接口长得很像呢?emm......,让我们眼见为实吧!

首先,因为CGLIB动态代理不是JDK自带的,因此我们需要先获取到CGLIB的Jar包导入我们的工程中,但是CGLIB包又依赖asm包,且不同版本的CGLIB包也依赖于不同版本的asm版本包,因此我建议会Maven的同学尽量的去用Maven构建项目,实在不会Maven的同学,我已经把所有的Jar包打包好了,需要下载的童鞋请点击此处开始下载。

我已经使用Maven导入好了,这个Jar包的结构如下:

废话少说,让我们直接看一看这个【MethodInterceptor】接口:

哈哈,这个接口是不是很简单,并且和JDK动态代理的【invocationHandler】接口长的很像,里面只有一个intercept方法需要我们去实现,或许有同学又要问了,为啥这个接口还多出了一个extends Callback东西呢?实际上Java基础好一点的同学就知道了,接口是可以继承接口的,因此,【MethodInterceptor】接口是继承了【Callback】接口的,那【Callback】接口又是什么鬼?搞得这么复杂?别怕,我们来看一看【Callback】接口的真面目!

肯定有童鞋有疑问了。。。。

这接口咋啥都没有呢?其实,这种接口广泛的存在于Java中,他们被叫做【标记接口】,何为【标记接口】,就是这个接口中没有任何方法,更不需要实现类去实现方法了,那么类为什么又要去实现这种【标记接口】呢?因为如果有某各类实现了这种【标记接口】,则这个类不需要实现任何方法就被打上了相应的标记,仅仅只是做标记作用,供后续其他类调用该类时做识别作用。比如我们的【Serializable接口】内部也是没有任何方法的,某个类实现它只是为了给自己标上自己能被【序列化】的标记而已,所以,【Callback】接口在这里也是起一个“标记”作用,表示这是一个“回调”类。

首先,我们先写一个CGLIB的代理逻辑类,并让它实现【MethodInterceptor】接口。

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibLogic implements MethodInterceptor {
    //实现CGLIB的逻辑代理接口
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //o为代理对象,method为被调用的方法,objects为被调用的方法的参数素组,methodProxy为方法代理
        System.out.println("代理正在与您谈判有关迪丽热巴演出的【前期工作】");
        Object result = methodProxy.invokeSuper(o, objects);
        //CGLIB反射调用真实的对象
        System.out.println("代理正在与您谈判有关迪丽热巴的【后期工作】");
        return null;
    }
}

接下来,我们在重新写一个Dilireba2类,这个类不实现任何接口

public class Dilireba2 {
    public void display(){
        System.out.println("大家好!我是迪丽热巴!");
    }
}

然后,我们再用CGLIB动态代理来生成一个代理对象,并测试一下它的代理方法:

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba2 dilireba2 = new Dilireba2();
        //产生一个无实现接口的Dilireba2对象
        CglibLogic cglibLogic = new CglibLogic();
        //产生一个CGLIB的代理逻辑类
        Dilireba2 cglibproxyobject = null;
        //cglibproxyobject用来接收生成的代理对象
        Enhancer enhancer = new Enhancer();
        //产生一个CGLIB enhancer增强类对象
        enhancer.setSuperclass(Dilireba2.class);
        //设置需要被增强的类的class对象
        enhancer.setCallback(cglibLogic);
        //设置相应的代理逻辑类,该类必须实现MethodIntercepeor接口
        cglibproxyobject = (Dilireba2) enhancer.create();
        //利用enhancer创建出代理对象
        cglibproxyobject.display();

    }
}

执行的结果如下:

此时,没有实现接口的Dilireba2类也能够通过CGLIB代理生成代理对象来啦!我们在回头想一想,CGLIB的代理是不是和JDK的代理很像呢!他们都是先制定好代理的逻辑类,然后生成代理对象。而代理逻辑类要实现一个接口的一个方法,那么这个接口定义的方法就是代理对象的逻辑方法,它可以控制真实对象的方法。

拦截器


看这一步的同学,相信有一部分已经“晕”啦。肯定有同学要担心了,是不是以后写代码都要这样去实现动态代理呢?我记性不好咋办?记不清这么多方法啊!不用急,框架的设计者早就想到了这一步,框架出现的原因是什么?就是现有的实现方法太繁琐,通过框架能够屏蔽底层的实现细节,简化逻辑,让程序员专心于业务,减少技术带来的障碍。因此由于动态代理一般都比较难理解,程序设计者会设计一个拦截器接口供开发者使用,开发者只要知道拦截器接口的方法,含义和作用即可,无需知道动态代理是怎么实现的。所以我们可以用刚刚学的JDK动态代理来实现一个拦截器的逻辑,为此先定义一个拦截器接口Interceptor,代码如下所示:

public interface Interceptor {
    public boolean before(Object proxy, Object target, Method method,Object[] args);

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

    public void after(Object proxy, Object target, Method method,Object[] args);
}

在拦截器接口里我们定义了3个方法,分别是before,around,after方法。

3个方法的参数为:proxy为生成的代理对象,target为真实对象,method为被调用的方法对象,args为被调用的方法的参数。

那么我们再定义一下这3个方法分别在什么时候调用吧!假设我们定义的规则如下:

before方法返回boolean值,它在真实对象前调用。当返回为true时,则反射真实对象的方法;当返回为false时,则调用around方法。

在before方法返回为false的情况下,调用around方法。

无论before方法返回的是true还是false,最后一定会调用after方法。

拦截器的拦截逻辑如下:

接下来,我们就写一个实现Interceptor的MyInterceptor类:

public class MyInterceptor implements Interceptor {
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【before方法】正在与迪丽热巴的经纪人洽谈,谈判成功返回true,失败返回false");
        return false;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【around方法】与经纪人人谈判失败!本方法被调用!无法见到迪丽热巴!");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【after方法】无论谈判成功或失败,本方法都会被调用!");
    }
}

这个类实现了所有的Interceptor接口的所有方法,并且使用了JDK的动态代理,就可以去实现这些方法在适当时的调用逻辑了。

我们同样再来写一个动态代理逻辑类,并在这个类中把我们新设计好的拦截器加进去,使得拦截器能更好地服务于代理逻辑:

public class InterceptorLogic implements InvocationHandler {
    private Dilireba dlrb = null;
    //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定
    private MyInterceptor myInterceptor = null;
    //现在的代理逻辑类加入了一个我们设计好的拦截器,用来更好地帮我们丰富代理逻辑


    public InterceptorLogic(Dilireba dlrb, MyInterceptor myInterceptor) {
        //绑定真实对象和拦截器
        this.dlrb = dlrb;
        this.myInterceptor = myInterceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (myInterceptor == null) {
            //如果没有给代理逻辑类配置一个拦截器的话,则直接反射真实对象的方法
            return method.invoke(dlrb, args);
        }

        if (myInterceptor.before(proxy, dlrb, method, args) == true) {
            //调用前置方法,如果前置方法返回true则可以通过反射调用真实对象的方法
             method.invoke(dlrb, args);
        } else {
            myInterceptor.around(proxy, dlrb, method, args);
            //如果前置方法返回false,则不能调用真实对象的方法,而是调用around方法
        }
        myInterceptor.after(proxy, dlrb, method, args);
        //无论前置方法返回true或false,after方法在最后一定会被调用
        return null;
    }
}

我们的代理逻辑类已经写好了,接下来就来生成一个动态代理对象测试一下看看(请注意我们在实现before方法时默认返回的是false,即真实对象的方法不会被调用):

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dilireba = new Dilireba();
        //实例化真实对象
        MyInterceptor myInterceptor = new MyInterceptor();
        //实例化拦截器
        InterceptorLogic interceptorLogic = new InterceptorLogic(dilireba, myInterceptor);
        //实例化带有拦截器的代理逻辑类
        Advertisement advertisement = (Advertisement) Proxy.newProxyInstance(dilireba.getClass().getClassLoader(), dilireba.getClass().getInterfaces(), interceptorLogic);
        //生成的代理对象也和真实对象实现了同样的接口,因此可用接口来接受对象
        advertisement.display();
        //调用代理对象的方法
    }
}

最后的结果如下:

我们开始设置的前置方法的返回值是false,因此真实对象的方法无法被调用,现在我们把它改成返回true试试:

  public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【before方法】正在与迪丽热巴的经纪人洽谈,谈判成功返回true,失败返回false");
        return true;
    }

再运行一下看看情况是怎样的:

此时,前置方法before返回的是true,因此真实对象的方法就会被调用到,这符合我们一开始对拦截器规则的设定,在动态代理中引入拦截器:

开发者只要知道拦截器的作用就可以编写拦截器了,编写完成后可以设置拦截器,这样就完成了任务,所以对于开发人员而言相对更加的简单了。

设计者可能是精通Java的开发人员,他来完成动态代理的逻辑。

设计者只会把拦截器接口暴露给开发者使用,让动态代理的逻辑在开发者的视野中“消失”。

因此,我们以后如果需要更改代理逻辑,只需要重新写一个类,让这个类实现【Interceptor】接口,在实现接口方法的同时定义属于自己的代理逻辑,接下来只需要把拦截器类交给框架就好了,框架会自动给我们生成代理对象,这样我们不用知道底层原理,也不用实现底层原理,也能够玩转动态代理了。

实际上,在Spring框架中的AOP思想(面向切面编程)的底层原理就是这样的,我们所谓的拦截器类在AOP思想中就叫做【切面类】,拦截器类中的每个方法叫做【通知】,被切面拦截的方法叫做【切点】,Spring的事务管理底层基本原理就是我们上面这样实现的,因此只要你搞懂了上面的【拦截器】,到后面学Spring的面向切面编程就会简单很多啦!

责任链模式


前面我们一起来研究了“拦截器”,实际上设计者往往会用拦截器去代理动态代理,然后将拦截器的接口提供给开发者,从而简化开发者的开发难度,但是拦截器也有可能有多个。比如,现在你想要约热巴为你的产品代言,但是热巴已经签约了传媒公司了,你不能够直接找到经纪人了,所以你首先得去找嘉行传媒公司,先和公司谈判,才会让你和明星的经济人慢慢谈判,最终确定是都能够见到热巴本人。所以此处我们要多加一个【传媒公司】拦截器:

当一个对象在一条链上被多个拦截器拦截处理(拦截器也可以选择不拦截处理它)时,我们把这样的设计模式称为责任链模式,它用于一个对象在多个角色中传递的场景。还是以热巴为例子,当演出请求走到【嘉行传媒公司】时,【嘉行传媒公司】可能认为酬劳太低,因此可能把酬劳由xxxx改为xxxx,从而影响了后面的谈判,后面的谈判要根据前面的结果进行。这个时候可以考虑用层层代理来实现,就是先用【经纪人代理逻辑类】为迪丽热巴先成第1个动态代理对象proxy1,然后,在用【嘉行传媒公司代理逻辑类】为proxy1生成第2个动态代理对象proxy2。如果还有其它角色需要拦截【演出请求】,以此类推即可。

理清楚了多个拦截器的代理顺序以及代理逻辑后,我们先只要写一个具有拦截逻辑的代理逻辑类,请注意!这个代理逻辑类和我们前面写的代理逻辑类有一些改变,为了能够不仅仅是代理【Dilireba】类的对象,而是可以代理【代理对象】,我们构造方法改成了如下所示:

 private Object target = null;
    //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定
    private Interceptor myInterceptor = null;
    //现在的代理逻辑类加入了一个我们设计好的拦截器,用来更好地帮我们丰富代理逻辑


    public InterceptorLogic(Object target, Interceptor myInterceptor) {
        //绑定真实对象和拦截器
        this.target =  target;
        this.myInterceptor = myInterceptor;
    }

完整的【InterxeptorLogic】类如下:

public class InterceptorLogic implements InvocationHandler {
    private Object target = null;
    //代理逻辑内添加一个真实对象的引用,从而实现代理逻辑与真实对象的绑定
    private Interceptor myInterceptor = null;
    //现在的代理逻辑类加入了一个我们设计好的拦截器,用来更好地帮我们丰富代理逻辑


    public InterceptorLogic(Object target, Interceptor myInterceptor) {
        //绑定真实对象和拦截器
        this.target =  target;
        this.myInterceptor = myInterceptor;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (myInterceptor == null) {
            //如果没有给代理逻辑类配置一个拦截器的话,则直接反射真实对象的方法
            return method.invoke(target, args);
        }

        if (myInterceptor.before(proxy,target, method, args) == true) {
            //调用前置方法,如果前置方法返回true则可以通过反射调用真实对象的方法
            method.invoke(target, args);
        } else {
            myInterceptor.around(proxy, target, method, args);
            //如果前置方法返回false,则不能调用真实对象的方法,而是调用around方法
        }
        myInterceptor.after(proxy, target, method, args);
        //无论前置方法返回true或false,after方法在最后一定会被调用
        return null;
    }
}

接下来,我们再来写【经纪人拦截器】和【传媒公司拦截器】,为了更好的展示调用过程,我们把这两个拦截器的before方法返回值都设置为true,以便能够看到真实对象被调用:

public class AgentInterceptor implements Interceptor {
    //这是经纪人拦截器的工作逻辑
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【经纪人before方法】经纪人正在与您谈判前期工作");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【经纪人around方法】经纪人before方法返回false,本方法被调用");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【经纪人after方法】无论经纪人before方法返回true或false,本方法都会被调用");
    }
}
public class CompanyInterceptor implements Interceptor {
    //这是传媒公司拦截器的拦截逻辑
    @Override
    public boolean before(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【传媒公司before方法】传媒公司正在与您谈判前期工作");
        return true;
    }

    @Override
    public void around(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【传媒公司around方法】传媒公司before方法返回false,本方法被调用");
    }

    @Override
    public void after(Object proxy, Object target, Method method, Object[] args) {
        System.out.println("【传媒公司after方法】无论传媒公司before方法返回true或false,本方法都会被调用");
    }
}

最后,让我们来写一个驱动类,试一试多层嵌套代理吧!

public class DynamicProxy {
    public static void main(String[] args) {
        Dilireba dilireba = new Dilireba();
        //实例化真实对象
        AgentInterceptor agentInterceptor = new AgentInterceptor();
        //实例化经纪人拦截器
        CompanyInterceptor companyInterceptor = new CompanyInterceptor();
        //实例化传媒公司拦截器
        InterceptorLogic interceptorLogic = new InterceptorLogic(dilireba, agentInterceptor);
        //把经纪人拦截器和被代理对象配置进代理逻辑类中
        Object proxy1 = Proxy.newProxyInstance(dilireba.getClass().getClassLoader(), dilireba.getClass().getInterfaces(), interceptorLogic);
        //产生了第一个代理对象proxy1
        //对第一个代理对象proxy1再进行一次动态代理
        InterceptorLogic interceptorLogic2 = new InterceptorLogic(proxy1, companyInterceptor);
        //把传媒公司拦截器和被代理对象配置进代理逻辑类中,此时的被代理对象不再是迪丽热巴了,而是proxy1了
        Object proxy2 = Proxy.newProxyInstance(proxy1.getClass().getClassLoader(), proxy1.getClass().getInterfaces(), interceptorLogic2);
        //以proxy1为真实对象,生成它的代理对象proxy2
        Advertisement finalProxyObject = (Advertisement) proxy2;
        //因为proxy1实现了dilireba的Advertisement接口,proxy2又实现了proxy1的接口
        //因此可以用接口Advertisement来接收对象
        finalProxyObject.display();
        //调用最后的代理对象的方法
    }
}

测试结果如下图所示:

请注意观察上面方法的执行顺序:

before方法按照从最外面的拦截器到最里面的拦截器的加载顺序运行,而after方法则按照从最里面的拦截器到最外面的拦截器加载顺去运行。因为代理对象是层层嵌套的,最外层的代理对象调用他所谓的“真实对象”,而这个“真实对象”可能又是别人的“代理对象”,因此调用起来就有点类似递归调用一样。

我们可以再来试一试,把最外层的拦截器【传媒公司拦截器】的before方法返回值改成false,即请求一开始就被【传媒公司拦截器】拒绝了。请你先不要看答案,用你自己的理解猜一猜最后的代理逻辑是怎样的?

是不是和你预想的一样呢?如果我们把【经纪人拦截器】的before方法返回值改为false,而【传媒公司拦截器】的before方法返回值还是给他恢复成true,又是什么情况呢?

这回和你想的又一样吗?如果还是不太理解,建议自己动手敲一下上面的代码,自己多试一试这几种情况,看一看自己是不是真的理解了动态代理。

从代码中可见,责任链模式的优点在于我们可以在传递链上加入新的拦截器,增加拦截逻辑,其缺点是会增加代理和反射,而代理和反射的性能不高。

 基础不牢?新手不友好?无人带路?关注《扬俊的小屋》公众号吧!


参考文献


【1】《Java EE 互联网轻量级框架整合开发 》杨开振 周吉文 梁华辉 谭茂华 著 中国工信出版社 电子工业出版社

博客文章版权申明



 

  • 35
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值