设计模式之代理模式-从静态代理到动态代理
代理模式看似离我们的开发比较远,不像策略模式、工厂模式那么的直观,但是其实在我们日常开发的许多场景都用到了代理模式,比如:Spring AOP使用了JDK动态代理 和Cglib动态代理 ;Mybatis的插件使用了JDK动态代理 ;
代理模式的优点
见名之意,代理模式的核心就是「代理对象」,这个代理对象相当于一个中介,而我们被代理的对象叫做「目标对象」,你可以把它想象成委托人。中介起到的作用就是隔离了委托人和客户 (说不定委托人不想和客户见面-安全性),让他们免去了繁琐的流程 (比如租房吧,租客和房东不需要见面,他们只需要和房屋中介进行对接就行),这在代码中呢,就叫做解除了「调用方」和「目标对象」的的耦合度。
综上,代理模式的优点如下:
- 通过代理对象将真正的目标对象隔离起来,起到了一定的保护作用
- 解除了调用方和被调用方的直接联系,一定程度上降低了系统的耦合度
- 代理对象可以做目标对象提供功能外的一些事情,达到对目标功能的增强作用
在开发中,最常遇见的三种代理模式:静态代理、JDK动态代理、CGLIB动态代理。
静态代理
静态代理的角色:1、功能接口;2、被代理类;3、代理类
还是拿租房的例子来写对应的代码:
功能接口-把房子租出去
public interface RentOutHouse {
void rentOut();
}
被代理方-委托方,房东
public class Landlord implements RentOutHouse{
@Override
public void rentOut() {
// 核心功能-租房子出去
System.out.println("我是房东,我有一个房子需要租出去");
}
}
代理对象-中介
public class HouseProxy implements RentOutHouse{
// 中介要和房东直接接触
private Landlord landlord;
public HouseProxy(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rentOut() {
// 核心功能前的功能增强
System.out.println("我是中介,租房之前和房东签合同");
System.out.println("我是中介,租房之前和租房者签合同");
// 核心功能
landlord.rentOut();
// 核心功能后的增强
System.out.println("租出去了之后,我要多收取一些中介费");
}
客户端-租客
public class Client {
public static void process() {
HouseProxy houseProxy = new HouseProxy(new Landlord());
houseProxy.rentOut();
}
public static void main(String[] args) {
process();
}
}
结果
我是中介,租房之前和房东签合同
我是中介,租房之前和租房者签合同
我是房东,我有一个房子需要租出去
租出去了之后,我要多收取一些中介费
静态代理的缺点:
1、每个被代理类都需要一个代理类与之对应,因为代理类中是要包含这个被代理类对象的,所以假如再来一个房东需要将自己的房子租出去,还需要创建一个新的中介去和这个新房东对接,而其实中介对于两个房东要做的增强事情是一样的,所以会造成冗余。
2、
再有,如果我房东现在是把房子租出去了,如果我想取消出租呢,这时候就需要顶层的那个接口再加一个取消出租的抽象方法,这时候就芭比Q了,因为你所有的房东和房屋中介都需要去重写这个方法,hhh。
JDK动态代理
JDK动态代理运用了反射的技术,很好的解决了上述静态代理的不足,先看看代码和执行的结果:
功能接口-把房子租出去
public interface RentOutHouse {
void rentOut();
}
被代理方-委托方,房东
public class Landlord implements RentOutHouse{
@Override
public void rentOut() {
// 核心功能-租房子出去
System.out.println("我是房东,我有一个房子需要租出去");
}
}
方法拦截器-InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 核心功能前的功能增强
System.out.println("我是中介,租房之前和房东签合同");
System.out.println("我是中介,租房之前和租房者签合同");
// 核心功能方法调用
Object invoke = method.invoke(object, args);
// 核心功能后的增强
System.out.println("租出去了之后,我要多收取一些中介费");
return invoke;
}
}
客户端-租客
public class Client {
public static void main(String[] args) {
RentOutHouse landlord = new Landlord();
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(landlord);
RentOutHouse proxy = (RentOutHouse)Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);
proxy.rentOut();
}
}
注意,这里的Proxy.newProxyInstance(Landlord.class.getClassLoader(), Landlord.class.getInterfaces(), myInvocationHandler);
是用来生成代理对象的,三个参数分别是:1、类加载器;2、类的全部接口;3、自定义的方法拦截器;
InvocationHandler
是方法拦截器,它会将被代理类的方法进行拦截,并加上自己的一些增强,核心就是它里面的invoke()
方法。
我这里写的InvocationHandler
比较简单,其实可以在它里面的invoke()
方法中做一些复杂的判断,比如 "rentout".equals(method.getName())
来指定只有方法名为rentout
时我才去进行增强。
这时你会说,不是说用到了反射么,在哪里?别急,先看看我们实际生成的代理对象到底是个什么东东:
在租客类中打个断点:
看代理类的类型:@Proxy0@513
,这个类你是找不到的,我们来把它反编译出来,你就会知道反射用在哪里了~~~
/**
* 生成字节码
*/
private static void saveProxyClass(String path) {
byte[] $proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", Landlord.class.getInterfaces());
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(path + "$Proxy0.class"));
fileOutputStream.write($proxy0s);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
try {
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
看看生成的class文件:
public final class $Proxy0 extends Proxy implements RentOutHouse {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
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 rentOut() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.hss.spring.jdkdynamic.RentOutHouse").getMethod("rentOut");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
像equals、hashCode那几个方法不看,我们就看我们代理的方法rentOut()
,你会看到,当我们调用代理对象的rentOut()
方法的时候,它内部是走了super.h.invoke(this, m3, (Object[])null);
,这里,super
是指父类,很容易看到是Proxy
类,super.h又是什么?跟进去看看:
可以看到,h其实就是我们在Client里面通过 Proxy.newProxyInstance()
生成代理对象时传入的自定义的 InvocationHandler
!!!
知道了h是什么,再来看看这里的m3是什么:
哈哈,这里就是通过反射拿到我们接口的方法rentOut()
,这样就把这个代理类对象搞懂了,最后一步,就是走到了InvocationHandler
的invoke()
方法中:
这就是JDK动态代理的全部流程了,需要注意的是:JDK动态代理要求被代理的类必须实现一个接口。
那么问题来了:为什么JDK动态代理要求被代理的类要实现接口?
答案:首先,要生成代理对象,是必须去知道被代理的类有哪些方法的,可以让被代理类实现接口,或者代理类去继承被代理类,这样代理类就可以得到被代理类中的所有方法。而JDK动态代理之所以需要被代理类实现接口,我们可以从反编译的class文件中看到,代理类对象已经继承了Proxy
类,java它又是单继承的,所以说就要求被代理类去实现接口,让我们能够成功的生成代理类。
CGLIB动态代理
CGLIB动态代理和JDK动态代理的本质区别就是:CGLIB不需要被代理类去实现接口(实不实现接口都可以)。
先引入cglib依赖
cglib
cglib
3.2.7
被代理方-委托方,房东
public class Landlord {
@Override
public void rentOut() {
// 核心功能-租房子出去
System.out.println("我是房东,我有一个房子需要租出去");
}
}
回调-Interceptor
public class MyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 核心功能前的功能增强
System.out.println("我是中介,租房之前和房东签合同");
System.out.println("我是中介,租房之前和租房者签合同");
Object obj = methodProxy.invokeSuper(o, objects);
System.out.println("租出去了之后,我要多收取一些中介费");
return obj;
}
}
客户端-租客
public class Client {
public static void main(String[] args) {
Landlord landlord = new Landlord();
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./p");
// 通过CGLIB获取代理对象
Enhancer enhancer = new Enhancer();
// 设置代理对象的父类
enhancer.setSuperclass(Landlord.class);
// 设置回调
enhancer.setCallback(new MyInterceptor());
Landlord landlordProxy = (Landlord) enhancer.create();
// 通过代理对象调用目标方法
landlordProxy.rentOut();
}
}
可以看到整体的代码和JDK动态代理非常的类似,区别就是被代理类不做实现接口的要求,Interceptor替换了InvocationHandler,然后在Client中有部分的代码不一样。
核心方法和增强方法的逻辑都是写在Interceptor中的。
这时候你会问,不是说基于继承么,怎么体现的?
看看我们生成的class文件(太多了,只看核心的部分):
内部调用了Interceptor的intercept()方法。
over!!!