WHAT
动态代理是代理模式的一种,代理模式指的是由代理人 帮助被代理对象完成本该由被代理对象去完成的事情。代理人通常具有更加强大的能力,比起被代理对象更加专业。举个生活中的例子来说明,你可以选择自己去找到房东,签约合同完成房屋租赁。但是生活大家通常都不是这么做的,而是寻求更加专业的中介来做,中介代理我们,寻找有出租意向的房东。在这例子中找房子这件事情本该由你自己完成的,就变成了由中介帮你完成,具体是怎么完成的不需要你操心,这种模式我们称之为代理模式。
WHY
为什么会有代理模式的出现?
人类的进步很大来源于分工合作,专人办专事,各司其职能够加快业务的推进,而不用要求每个人每个机构都是全才全能。每个人只要专注于自己的一亩三分地,遇到份外的事情交给更加专业的人来做就好了,你只要‘坐享其成’。
WHERE
适用于一切你不想完成的事,比如你不想买菜找叮咚帮你买等等………
HOW
以上都是思想层面的说法,具体落实到我们Java世界又是怎么实现的呢?
举个例子来写代码吧:
场景:现有懒人一枚,想找房子,委托给了中介。中介一直在帮忙寻找房子,找到最后一套房子的时候,中介拉上了懒人一起看房子。
看代码之前我们先想象一下大致情况。
Java面向对象,那么一定是生成了懒人对象,中介对象,然后中介对象和懒人对象同时都具有找房子的能力,也就是说这两个对象一定都是实现了找房子这个功能接口。
那么中介如果中途要拉上懒人一起看房子怎么办呢?
中介流动性很大,万一这个中介跑路了,我还要重写一个代理类吗?
动态代理到底是怎么个动态法?
让我们带着以上所以疑问一起开始吧
Let‘s go
来个找房子示意图
中介刚开始找房子是自己在找,最后一次拉上了懒人一起找。落地到代码层面,我们可以想到最后一次应该是调用了懒人的找房子方法,而之前没有调用。
那么这种掉不掉用懒人的方法的逻辑是在哪里实现的呢?
InvocationHandler接口
所有的中介都必须是InvocationHandler接口的实现类,需要实现Invoke方法,Invoke方法就是具体决定中介执行逻辑的方法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* InvocationHandler 就是来决定具体代理逻辑的类
* @author yhg 2021-08-22 12:13
*/
public class MyProxyHandler implements InvocationHandler {
private Object lazyPerson;
public MyProxyHandler(Object lazyPerson) {
this.lazyPerson = lazyPerson;
}
public Object getLazyPerson(){
return lazyPerson;
}
/**
*
* @return 返回中介对象
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),lazyPerson.getClass().getInterfaces(),this);
}
/**
* 所有本应该懒人发起的动作都会被拦截,拦截后执行该方法,在这里可以决定中介怎么操作的具体逻辑
* @param proxy 中介
* @param method 被代理的方法
* @param args 被代理方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String res = null;
System.out.println("中介:我开始找房子了,懒人你就坐着吧");
//如果中介决定不找懒人一起去看最后一套房子,那么整个代理找房子的过程,懒人都可以不用出现
System.out.println(中介:我已经找到只剩最后一套房子了,带上懒人一起去看看房子吧");
((FindHouse)lazyPerson).findHouse();
System.out.println("中介和懒人最终确定了房子A");
return "房子A";
}
}
Java面相对象,那么中介对象是怎么生成的呢?
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),lazyPerson.getClass().getInterfaces(),this);
}
奥秘就在上面这行代码,这是JDK提供的在运行时生成一个class文件并加载入虚拟机的方法,来看看三个参数具体啥意思
因为需要加载类文件所以需要类加载器classLoader
因为要拦截懒人的方法,所以需要知道拦截哪些方法,这些方法都被定义在了接口中
拦截了方法之后,具体要怎么执行逻辑需要InvocationHandler 告诉我,所以需要this
Java动态生成的代理类文件长啥样呢
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.yhg.FindHouse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements FindHouse {
private static Method m1;
private static Method m3;
private static Method m2;
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 findHouse() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 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"));
m3 = Class.forName("com.yhg.day3.FindHouse").getMethod("findHouse");
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());
}
}
}
可以看到所有的被生产的代理类都是Proxy的子类并根据你传入的接口类型来依次实现
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
// 下面是super的代码
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
可以看到构造方法将我们传入的InvocationHandler 保存到父类的h属性中
再回过头观察每个代理类的方法都会执行
return (XXXX)super.h.invoke(this, mXXX, (Object[])null);
说明代理类最后会去执行我们传入的InvocationHandler的invoke方法
至此所有逻辑梳理完毕
下面是剩余的代码,InvocationHandler类在上面已经贴过了
/**
* @author yhg 2021-08-22 12:12
*/
public interface FindHouse {
String findHouse();
}
/**
* @author yhg 2021-08-22 12:13
*/
public class LazyPerson implements FindHouse {
@Override
public String findHouse() {
System.out.println("懒人正在找房子");
return "";
}
}
/**
* @author yhg 2021-08-22 12:23
*/
public class FindHouseDemo {
public static void main(String[] args) {
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
// 上面两行代码中的任一行都可以将生成的代理类class文件保存在项目com\sun\proxy中,便于理解
FindHouse proxy = (FindHouse)new MyProxyHandler(new LazyPerson()).getProxy();
String house = proxy.findHouse();
System.out.println("中介找到了" + house);
}
}
代码运行结果
中介:我开始找房子了,懒人你就坐着吧
中介:我已经找到只剩最后一套房子了,带上懒人一起去看看房子吧
懒人正在找房子
中介和懒人最终确定了房子A
中介找到了房子A
到这里相信各位看官肯定心里已经对代理有个数了,回过头来看前面的三个疑问就小菜一碟了。
Q:那么中介如果中途要拉上懒人一起看房子怎么办呢?
A:代理类和被代理类通过super.h保持了连接,invoke方法决定了具体的执行逻辑
Q:中介流动性很大,万一这个中介跑路了,我还要重写一个代理类吗?
A:代理类可以复用,不用重写
Q:动态代理到底是怎么个动态法?
A:通过生成类文件并加载入虚拟机中的方法来动态的生成不确定的代理类,代理类通过实现传入的所有接口方法来实现动态代理。因为传入的对象是Object类型,所以你可以不仅仅是传懒人类,而是所有你想被代理的类都可以传,当然相应的代理逻辑你也得改,不然全都是找房子逻辑。。。
优点:
1.被代理类可以专精自己的事情(业务逻辑),无关业务的事情(入参出参数打印,事务控制……)可以完全交给被代理类来控制
2.同类事情只要实现一个代理逻辑类就可以了(InvocationHandler)
缺点
1.代理类只能代理通过结构暴露出来的方法,如果没有接口就没法用JDK动态代理,得用CgLib代理
2.反射执行方法性能不是特别好
兄弟萌都看到这了,点个赞呗
好了,我黄某人躺平了,不会的来打我吧