专业的JAVA编程教程与资源

网站首页 > java教程 正文

设计模式篇——代理模式详解(面试再问你代理模式,这么回答他)

temp10 2024-10-24 17:23:37 java教程 17 ℃ 0 评论

代理模式的定义

现实生活中经常听到一个词“代理”,比如某某酒品牌什么什么省总代理;还有像现在的明星都有自己的经纪人,有事需要找他们的时候就要先找他们的经纪人,经纪人也相当于是一个代理;再比如打官司都需要找一个律师,有什么问题直接由律师去沟通解决,那律师就是自己的一个代理。

生活中的代理随处可见,Java中也存在一种设计模式,就叫代理模式,并且使用非常普遍,很多著名框架和开源项目的源码中都有大量使用代理模式,比如比较流行的Spring、MyBatis等,都有用到代理模式。特别是MyBatis,代理模式随处可见。那到底什么是代理模式呢?

设计模式篇——代理模式详解(面试再问你代理模式,这么回答他)

代理模式也叫做委托模式,是指为其他对象提供一种代理以控制对这个对象的访问。代理模式可以分为静态代理和动态代理,动态代理又分为JDK提供的动态代理和CGLIB动态代理,具体实现及原理下面会进行详细介绍。

代理模式的三个角色

代理模式中存在三个角色:

1. 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

2. 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

3. 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

这三个角色之间的关系可以用一张简单的类图来表示:

静态代理

静态代理很简单,代理角色和真实角色继承同一个父类或实现相同的接口,使用时只需要把真实角色传递给代理角色,代理角色调用真实角色的方法就可以了。我们举一个游戏代练的例子,我们找游戏代练一般会找专业的代练公司,代练公司有很多代练人员,在这里,我就相当于客户端,代练公司相当于代理角色,代练人员就是真实执行任务的真实角色。我们设计出了如下类图:

顶层接口 IGamePlayer 中定义了待实现的业务方法:

代理类源代码:

真实角色 GamePlayer 类的源代码:

客户端进行调用的源代码:

以上就是使用代理模式模拟的一个游戏代理的场景。

静态代理的优缺点

优点

1. 真实角色类(业务类)只需要关注业务逻辑本身,这是代理模式的共有优点;

2. 代理类可以在调用业务类的处理方法之前和之后做一些增强性的操作,比如记录日志、管理事务等,这也是代理模式共有的优点。

缺点

1. 代理类和业务类实现了相同的接口,并且实现了相同的方法,代码冗余。如果接口增加一个方法,所有的代理类和所有的实现类都需要增加这个方法的实现,不易维护;

2. 代理对象只能代理同一种类型的对象,如果要对多种类型的对象进行代理,就要写多个代理类,这就会大大增加类文件的数量,不适合在大规模程序中使用。

针对静态代理的这些缺点,JDK 提供了动态代理。

JDK动态代理

动态代理的代理类是在程序运行的时候由Java反射机制动态生成的。我们在上面静态代理的例子中,代理类GamePlayerProxy是自己定义好的,在程序运行之前就已经编译完成。我们先来看下如何实现JDK的动态代理。

实现JDK动态代理

Java的 java.lang.reflect 包下提供了一个Proxy类和一个InvocationHandler接口,JDK动态代理的实现主要依靠这两个对象实现。

1. 创建一个类,实现 InvocationHandler 接口,并重写它的 invoke 方法:

这个类中持有一个被代理对象的实例 target,还有一个 invoke 方法,这个方法中通过反射机制调用被代理对象target的method方法,所有执行代理对象的方法都会被替换成执行invoke方法。

2. 创建被代理对象的接口,此处还以上面的游戏代练为例子:

3. 创建实际要被代理的对象:

4. 创建代理对象,并执行要代理的方法:

执行之后的结果:

通过以上4步,我们就通过动态代理实现了一个代练游戏的场景。

JDK动态代理实现原理

动态代理使用起来很方便,那它底层是如何实现的呢?在上面的例子中,我们通过调用 Proxy 类的 newProxyInstance 方法创建了一个代理对象,我们首先来看下这个方法的源码:

其中有一行比较关键的代码,通过getProxyClass0()方法生成代理类Proxy的Class对象。这个方法的源码如下:

proxyClassCache.get 方法就是通过工厂动态生成代理类文件,并将其放到缓存中,具体实现就不跟进去看了。我们可以通过如下代码查看生成的代理类文件长什么样:

通过反编译工具对这个生成的class文件进行反编译,看到内容如下:

JDK为我们生成了一个“$Proxy0”的代理类,其中0代表编号,如果有多个代理类会依次递增。这个类继承了Proxy类,并且实现了我们定义的IGamePlayer接口,所以它会实现我们接口中定义的 playGame 方法,在这个方法中,会调用 InvocationHandler 类中的 invoke 方法,通过上面分析我们知道,InvocationHandler 的 invoke 方法会通过反射调用被代理对象 target 的 method 方法,这就是代理类调用的真相。这个文件生成之后是放在内存中的,我们在创建代理类对象的时候,就是通过反射获得这个类文件的构造方法,然后创建代理实例。

我们可以把 InvocationHandler 看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

CGLib动态代理

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法。这是它与JDK动态代理的一个主要区别。

CGLib动态代理的实现

1. 引入CGLib的依赖。

2. 编写方法拦截器。这个拦截器实现了MethodInterceptor接口

3. 创建被代理的目标类。

4. 通过Enhancer对象创建代理类,执行被代理方法。

通过以上4步我们就完成了CGLib动态代理实现游戏代练的场景。

CGLib动态代理原理

首先,要实现CGLib的动态代理,我们必须要实现一个MethodInterceptor(方法拦截器)接口,其源码如下:

这个接口只有一个intercept方法,这个方法有4个参数:

1.第一个参数表示增强的对象,也就是实现这个接口的类的一个对象。

2.第二个参数表示要被拦截的方法。

3.第三个参数表示要被拦截的方法的参数。

4.第四个参数表示要触发父类的方法对象。

其次,另外一个比较重要的对象是Enhancer。通过上面的例子我们看到,我们的代理类是通过Enhancer的create()方法生成的:

create方法中调用了一个比较核心的方法 createHelper 方法,这个方法中通过 newInstance 方法创建 Enhancer key对象,然后利用这个 key 调用父类 AbstractClassGenerator 的create方法生成代理对象:

上面方法中,真正创建代理对象方法在nextInstance()方法中,该方法为抽象类AbstractClassGenerator的一个方法,由具体子类去实现,我们去它的子类Enhancer中找到这个方法:

我们看到最后data.newInstance(argumentTypes, arguments, callbacks)方法,这个方法有三个入参,第一个参数为代理对象的构造器类型,第二个为代理对象构造方法参数,第三个为对应回调对象。最后根据这些参数,通过反射生成代理对象:

JDK动态代理与CGLib动态代理的区别

JDK动态代理:代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理,只能够代理实现了接口的委托类,底层使用反射机制进行方法的调用。

CGLib动态代理:代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理。不能对final类以及final方法进行代理,底层将方法全部存入一个数组中,通过数组索引直接进行方法。


点个关注吧!进入我的主页查看更多干货!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表