设计模式之代理模式
代理模式的定义:代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。这种类型的设计模式属于结构型模式。
比如一个餐厅要上线外卖功能,第一种方法是有顾客自己打电话到餐厅,进行点餐。但可能很少人知道能餐厅上线了外卖功能。第二就是在美团上上线外卖功能上美团,美团帮餐厅去接单。
代理又分两种,静态代理和动态代理;
静态代理在程序运行前,代理类已经提前写好。
动态代理则是在程序运行时,动态的创建代理类。其中又分为JDK动态代理和CGLIB动态代理,JDK动态代理是基于接口进行代理的,被代理类必须实现自一个接口,没有接口的类可以使用CGLIB动态代理。
静态代理
代码样例如下:
Order.java
public interface Order {
void order();
}
MeiTuan.java
```java
public class MeiTuan implements Order {
private Restaurant restaurantA = new Restaurant();
public void order() {
System.out.println("收到一笔A餐厅的外卖订单");
restaurantA.order();
}
}
Restaurant.java
public class Restaurant implements Order {
public void order() {
System.out.println("A餐厅,收到来自美团的订单");
}
}
Main.java
public class Main {
public static void main(String[] args) {
//打开美团客户端
Order order = new MeiTuan();
//下单
order.order();
}
}
JDK动态代理
接口Order.Java
public interface Order {
void order();
}
Restaurant.java
public class Restaurant implements Order {
public void order() {
System.out.println("A餐厅,收到来自美团的订单");
}
}
Main.java
public class Main {
public static void main(String[] args) {
Restaurant restaurant = new Restaurant();
Order proxyMeietuan = (Order) Proxy.newProxyInstance(Order.class.getClassLoader(), new
Class[]{Order.class}, new MyInvovationHandler(restaurant));
proxyMeietuan.order();
}
}
Proxy.java
public class MyInvovationHandler implements InvocationHandler {
private Restaurant restaurant;
public Proxy (Restaurant restaurant) {
this.restaurant = restaurant;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before order");
method.invoke(restaurant,args);
System.out.println("after order");
return null;
}
}
如何查看动态代理所生成的类
使用txt,把引号内容换成自己java_home对应的路径,修改完毕后把文件后缀改成.bat
java -classpath "D:\Java\jdk1.8.0_261\lib\sa-jdi.jar" sun.jvm.hotspot.HSDB
然后打开.bat文件,点击attach to hotSpot process
然后输入进程号,如不清楚进程号,可以输入jps查询
输入进程号后点击class broswer
找到proxy,
再点击
然后你就能看到生成了一个文件夹,里面就有一个class文件
把文件拉入idea,就可以看到生成的动态代理类了
public final class $Proxy0 extends Proxy implements Order {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public final void order() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public $Proxy0(InvocationHandler var1) {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.design.proxy.Order").getMethod("order");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final boolean equals(Object var1) {
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() {
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 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);
}
}
}
可以看到,所生成的proxy类继承于Proxy,由于java是单继承的,所以jdk动态代理只能用在接口上面。
结合生成出来的动态代理代码,分析一下调用order的流程。
首先从proxy的创建看起
Order proxyMeietuan = (Order) Proxy.newProxyInstance(Order.class.getClassLoader(), new
Class[]{Order.class}, new MyInvovationHandler(restaurant));
点进去newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
创建动态代理的class类
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//拿到动态代理proxy的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//通过构造方法,把invocationHandler赋值进去
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
大致的步骤就有3个,先生成动态代理类,然后拿到他的构造方法,调用构造方法把invocationHandler赋值进去,并放回通过构造方法得到的动态代理对象。
再结合动态代理所生成的$proxy的源码
public final void order() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
m3 = Class.forName("com.design.proxy.Order").getMethod("order");
可以看到,调用proxyMeietuan.order方法,就会进入到order,h就是刚刚传过来的invocationHandler,所以就会先调用MyInvovationHandler 的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before order");
method.invoke(restaurant,args);
System.out.println("after order");
return null;
}
method就是刚刚传进来的m3,restaurant是通过invocationHandler的构造器传进来的,这样通过反射,就可以调用restaurant的order方法,来实现动态代理。
动态代理在很多场景都用到了,例如spring的aop,mybatis里面通过dao接口就能调用mapper的sql,还有feign的远程调用