这是一篇详细且容易理解的代理模式讲解!
--------------------------------------------正文分割线-------------------------------------------------------------------
代理模式(Proxy Pattern)是程序设计中的一种设计模式,它的特征是代理类和委托类实现同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。总结为一句话:其他对象提供一个代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如:有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代理模式要素:有共同接口、代理对象、目标对象。
代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。
代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。
优点:
1)中间隔离了一层,更加符合开闭原则。
2)通过代理类处理委托类(目标类)的非核心工作,委托类可以专注做自己的事情,二者解耦,提高代码的可维护性。
3)无需干涉委托类的情况下,代理对委托类进行增强作用。
两种常用的代理模式:静态代理模式和动态代理模式。
1. 静态代理模式
代理对象与目标对象一起实现相同的接口或者继承相同父类,由程序员创建或特定工具自动生成源代码,即在编译时就已经确定了接口,目标类,代理类等,因此在程序运行之前,代理类的 .class 文件就已经生成。
要求:
1)代理类与委托类实现相同的接口
2)委托类必须要通过代理类来处理请求
来看代码例子:一个典型的案例就是老板通过秘书来处理日程安排的事情。
/**
* 定义一个代理接口
* @author hhfounder
* @date 2023-11-16
*/
public interface BossSchedule {
//会面
void meeting(String name);
}
/**
* 定义一个委托类Boss,实现了代理接口
* @author hhfounder
* @date 2023-11-16
*/
public class Boss implements BossSchedule{
@Override
public void meeting(String name) {
System.out.println("5.请求发送给老板");
System.out.println("6.老板收到秘书发来的信息,在"+LocalDate.now()+"日15点见"+name+"客户");
try {
Thread.sleep(200);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 定义一个代理类Secretary,实现了代理接口
* @author hhfounder
* @date 2023-11-16
*/
public class Secretary implements BossSchedule{
//代理类持有一个委托类的对象引用
private Boss hhfounder;
//代理类构造函数,对委托类对象(Boss类对象)进行初始化
public Secretary(Boss hhfounder)
{
this.hhfounder=hhfounder;
}
@Override
public void meeting(String name) {
System.out.println("2.请求发送给秘书");
System.out.println("3.秘书接待来访客户:"+name+",预约"+ LocalDate.now()+"日与老板会面");
//鉴权
if (name.equals("cory")){
System.out.println("4.核实身份后,秘书将预约信息发送给老板,通知老板在" +LocalDate.now()+"日与"+name+"客户会面");
//调用委托类方法
hhfounder.meeting(name);
} else {
System.out.println("7.老板出差");
}
System.out.println("8.会面完成");
}
}
/**
* 定义main方法,程序入口
* @author hhfounder
* @date 2023-11-16
*/
public class MainTest {
public static void main(String[] args){
System.out.println("1.客户要见老板");
//将委托类对象实例化传给代理类
Secretary bossProxy=new Secretary(new Boss());
//调用代理类的meeting方法
bossProxy.meeting("cory");
}
}
结果:
1.客户要见老板
2.请求发送给秘书
3.秘书接待来访客户:cory,预约2023-11-16日与老板会面
4.核实身份后,秘书将预约信息发送给老板,通知老板在2023-11-16日与cory客户会面
5.请求发送给老板
6.老板收到秘书发来的信息,在2023-11-16日15点见cory客户
8.会面完成
上面的代码用详细日志标出了调用流程,相信大家很容易看懂。用一个简单的流程图总结一下静态代理模式的调用关系:
这样做的优点显而易见,同一个代理类可以代理多个相似属性的委托类来处理业务请求,同时又兼有代理类的3大优点(见上文列出的代理模式优点)。
但是静态代理也有很明显的缺点,静态代理在编译时就已经确定了接口,委托类(目标类),代理类等,如果代理接口内的方法发生改变,所有接口相关的代理类和委托类的方法都要改变,代码的维护成本比较高,而且在实际的业务逻辑中,业务类的接口通常是不一样的,委托类也差异较大,可能很简单的需求因此需要写很多个不同的代理类和不同的接口,这样很容易造成代码和架构冗余,比如这样:
有没有更好的方法?可以让程序在运行时根据委托类(目标类)来动态生成代理类。
2. 动态代理模式
代理类在程序运行时才创建的代理方式被称为动态代理。在Java中有2种动态代理实现方式:JDK动态代理和CGLIB动态代理。例如Spring 中的AOP就是依靠动态代理来实现切面编程的。
2.1 JDK动态代理
JDK动态代理依靠反射来实现,代理类和委托类不需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理,代理类在JVM运行时动态生成,而不是编译期就能确定。
如何实现JDK动态代理呢?我们需要JDK java.lang.reflect 包下的 Proxy 类 和 InvocationHandler接口,在调用委托类方法前通过调用代理类 InvokeHandler 的 invoke 方法来处理。
要点:
1)动态的生成代理类(.class文件),不需要手动写代理类,只需要维护委托类,接口以及代理处理类(InvocationHandler接口实现类,下文例子中的Secretary)的代码。
2)委托类(目标类)必须实现接口。
3)调用目标类的方法是通过JDK内部反射的方式调用的。
继续用上文中老板通过秘书来处理日程安排的例子,将上述例子改写为JDK动态代理模式,具体代码如下:
接口:接口代码不变。
/**
* 代理接口
* @author hhfounder
* @date 2023-11-16
*/
public interface BossSchedule {
//会面
void meeting(String name);
}
委托类:委托类代码不变
/**
* 委托类,实现了代理接口
* @author hhfounder
* @date 2023-11-16
*/
public class Boss implements BossSchedule{
@Override
public void meeting(String name) {
System.out.println("5.请求发送给老板");
System.out.println("6.老板收到秘书发来的信息,在"+LocalDate.now()+"日15点见"+name+"客户");
try {
Thread.sleep(200);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代理处理类:需要实现JDK动态代理InvocationHandler接口,重写invoke()方法,并通过method.invoke反射的方式调用委托类的方法,代码如下。
/**
* 代理处理类,实现了JDK动态代理的InvocationHandler接口
* @author hhfounder
* @date 2023-11-16
*/
public class Secretary implements InvocationHandler {
//代理对象持有一个委托类的对象引用,这里也可以将其类型定义为Object类型,因为InvocationHandler类型为Object
private Boss hhfounder;
//private Object hhfounder;
//构造函数,对委托类对象(Boss类对象)进行初始化
public Secretary(Boss hhfounder) {
this.hhfounder=hhfounder;
}
//重写InvocationHandler类中的invoke()方法,在invoke()方法中可以写代理类代码的处理逻辑
//通过method.invoke()反射的方式调用委托类的处理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("2.请求发送给秘书");
System.out.println("3.秘书接待来访客户:" +args[0]+",预约"+LocalDate.now()+"日与老板会面");
//鉴权
if (args[0].equals("cory")){
System.out.println("4.核实身份后,秘书将预约信息发送给老板,通知老板在" +LocalDate.now()+"日与"+"客户会面");
//通过method.invoke反射调用委托类方法并传入参数
Object result=method.invoke(hhfounder,args);
System.out.println("8.会面完成");
return result;
} else {
System.out.println("7.老板出差");
return null;
}
}
}
主程序请求入口:先获取委托类的代理对象,将代理对象作为参数传给JDK动态代理,创建一个动态代理对象。
public class MainTest {
public static void main(String[] args){
//将生成的代理文件存放到项目的根目录下,注意这是JDK1.8版本之后的设置
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.out.println("1.客户要见老板");
//将委托类对象实例化传给代理类
Secretary bossProxy=new Secretary(new Boss());
//调用JDK动态代理Proxy.newProxyInstance,生成JDK动态代理对象
/**
* 调用JDK动态代理,需要传入3个参数
* @getClassLoader getClassLoader() 代理类加载器
* @getInterfaces getInterfaces() 代理的接口(即委托类实现的接口)
* @目标对象 bossProxy,即代理处理对象
*/
BossSchedule bossDynamicProxy=(BossSchedule)Proxy.newProxyInstance(Boss.class.getClassLoader(),
Boss.class.getInterfaces(),
bossProxy);
//JDK动态代理对象调用meeting方法
bossDynamicProxy.meeting("cory");
}
}
调用结果:
1.客户要见老板
2.请求发送给秘书
3.秘书接待来访客户:cory,预约2023-11-16日与老板会面
4.核实身份后,秘书将预约信息发送给老板,通知老板在2023-11-16日与客户会面
5.请求发送给老板
6.老板收到秘书发来的信息,在2023-11-16日15点见cory客户
8.会面完成
根据上例总结下JDK动态代理的调用逻辑,客户端的请求会先发给JDK动态代理Proxy,由JDKProxy动态生成代理类.class文件,然后调用InvocationHandler的实现类的对象,再通过InvocationHandler实现类中method.invoke方法反射调用委托类的方法。
可以看下JDK动态代理生成的文件$Proxy0.class,动态代理文件$Proxy0继承了Proxy并实现了目标类的接口BossSchedule,因此在客户端调用meeting方法时,会先调用JDK动态代理文件$Proxy0.class中的meeting()方法,然后再去调用InvocationHandler接口中的invoke()方法,如下代码:
super.h.invoke(this, m3, new Object[]{var1});
再通过反射调用目标类真实的meeting()方法。
package jdk.proxy1;
import Proxy.BossSchedule;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//动态代理文件$Proxy0继承了Proxy并实现了目标类的接口BossSchedule
public final class $Proxy0 extends Proxy implements BossSchedule {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;
public $Proxy0(InvocationHandler var1) {
super(var1);
}
public final int hashCode() {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void meeting(String var1) {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
ClassLoader var0 = $Proxy0.class.getClassLoader();
try {
m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");
m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));
m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");
m3 = Class.forName("Proxy.BossSchedule", false, var0).getMethod("meeting", Class.forName("java.lang.String", false, var0));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
} else {
throw new IllegalAccessException(var0.toString());
}
}
}
用一个简单的流程图总结一下JDK动态代理模式的处理逻辑:
虽然JDK动态代理解决了静态代理中代码何结构冗余的问题,但JDK 动态代理是基于接口设计实现的,而对于没有接口的场景,JDK动态代理的方式就无法实现了,而且通过反射的方式调用委托类(目标类)可能会有性能较慢的问题(JDK1.8版本之后对反射调用做了优化,性能基本上能达到CGLIB动态代理调用目标类方法的性能,在某些场景下甚至超过CGLIB代理方式)。
有没有不需要接口就能实现动态代理的方式?还真有,下面一起来学习下cglib动态代理。
2.2 CGLIB动态代理
CGLIB(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它主要利用 asm 开源包,将目标对象类的 class 文件加载进来,然后修改其字节码生成新的子类来进行扩展处理。因此,在使用cglib代理前需要先引入cglib包。
将上面的例子改写成CGLIB动态代理方式,需要注意的是,CGLIB动态代理方式采用继承委托类(目标类)的方式,因此不需要委托类(目标类)实现接口,代码例子如下:
委托类:不需要实现接口(区别于JDK动态代理)
/**
* 委托类
* @author hhfounder
* @date 2023-11-16
*/
public class Boss {
public void meeting() {
System.out.println("4.请求发送给老板");
System.out.println("5.老板收到秘书发来的信息,在"+LocalDate.now()+"日15点见客户");
try {
Thread.sleep(200);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代理类:实现MethodInterceptor接口,通过MethodInterceptor接口实现对委托类(目标)方法的拦截,CGLIB会首先调用拦截器的intercept方法,然后在该方法中调用目标方法,在目标方法调用前后,我们可以实现对目标方法的增强逻辑。
CGLIB代理类参数说明:
MethodInterceptor:拦截器接口,用于实现拦截逻辑。
Callback:是CGLIB的回调接口
MethodProxy:为实现对委托类方法的代理和拦截而设计,CGLIB会为每个方法生成对应的MethodProxy对象,用于代理委托类中的方法。
/**
* 代理类,实现了CGLIB动态代理的MethodInterceptor接口
* @author hhfounder
* @date 2023-11-16
*/
public class Secretary implements MethodInterceptor {
//代理类持有一个委托类的对象引用
private Object hhfounder;
//代理类构造函数,对委托类对象(Boss类对象)进行初始化
public Secretary(Object hhfounder)
{
this.hhfounder=hhfounder;
}
/**
* @param obj 表示要进行增强的对象
* @param method 表示拦截的方法
* @param objects 数组表示参数列表,基本数据类型需要传入其包装类型
* @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
* @return 执行结果
* @throws Throwable 异常
*/
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("2.请求发送给秘书");
System.out.println("3.核实身份后,秘书将预约信息发送给老板,通知老板在" + LocalDate.now() + "日与客户会面");
//methodProxy.invokeSuper调用的是obj(代理类,即Secretary类)的父类对象(即Boss类)
Object object = methodProxy.invokeSuper(obj, objects);
System.out.println("6.会面完成");
return object;
}
}
主程序:其执行流程参考上文CGLIB动态代理主要流程。
/** 定义main方法,程序入口
* @Enhancer 是CGLIB的核心类之一,它可以动态地生成一个子类,并且拦截父类的所有方法。
* @setSuperclass 设置父类,即需要增强的目标类
* @setCallback 设置回调函数
* @author hhfounder
* @date 2023-11-16
*/
public class MainTest {
public static void main(String[] args){
System.out.println("1.客户要见老板");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(Boss.class);
// 设置enhancer的回调对象
enhancer.setCallback(new Secretary(Boss.class));
// 调用create方法正式创建CGLIB动态代理对象
Boss bossDynamicProxy= (Boss)enhancer.create();
//通过CGLIB动态代理对象调用委托类方法
bossDynamicProxy.meeting();
}
}
执行结果:
1.客户要见老板
2.请求发送给秘书
3.核实身份后,秘书将预约信息发送给老板,通知老板在2023-11-16日与客户会面
4.请求发送给老板
5.老板收到秘书发来的信息,在2023-11-16日15点见客户
6.会面完成
梳理下CGLIB动态代理主要流程:
(1)客户端请求进来后,在实例化阶段会查找目标类上的所有非final的public类型的方法定义,通过Enhancer类创建一个代理对象,该代理对象是目标类的子类,因此可以继承目标类的所有方法和属性;
(2)将符合条件的方法定义转换成字节码;
(3)将组成的字节码转换成相应的代理的class对象;
(4)实现MethodInterceptor接口,通过MethodInterceptor接口实现对委托类(目标)方法的拦截,在拦截器中,客户端可以实现对目标类方法的增强逻辑,用来处理对代理类上所有方法的请求;(5)当代理对象调用目标方法时,CGLIB会首先调用拦截器的intercept方法,然后在该方法中调用目标方法,在目标方法调用前后,我们可以实现对目标方法的增强逻辑。最后,CGLIB会将增强后的方法返回给代理对象,从而实现对目标方法的动态代理。
上面的流程用流程图表示:
可能有同学会有疑问,methodProxy.invokeSuper()是如何调用到目标类的方法呢?
具体过程为:fastclass文件会为目标类的每一个方法(上例中的Boss类的meeting()方法)创建一个索引值,通过索引值可以快速定位到这个方法(meeting()方法),然后回到CGLIB生成的代理类.class文件(Secretary***.class),调用代理类文件中的子类(meeting)方法,再调用其父类方法(meeting()方法),用一个简单流程图说明下:
3. 总结
最后,再总结下各自优缺点。
1. 静态代理虽然逻辑简单且具备代理模式的基本优点,但很容易造成代码和架构冗余。
2. JDK的动态代理是基于接口实现的,无接口场景下不能使用,而CGLIB的动态代理是基于继承实现的,有没有接口都能使用。
3. JDK动态代理类和CGLIB动态代理都是在运行期生成字节码,但JDK动态代理字节码生成快于CGLIB。
3. JDK动态代理执行方法时是基于反射调用,而CGLIB动态代理将为方法生成代理对象MethodProxy,JDK1.8版本之前JDK动态代理效率更低,CGLIB执行效率更高,而JDK1.8之后二者执行效率无明显差异。
码字不易,希望大家都能点个赞哈,谢谢啦~~~