2020年受疫情影响,各位小伙伴们困在家里出大门都是一种奢望,人都要憋出病了,作者都快疯了,都是泪。这样严峻时刻我们生活必须品都是小区代购帮我们代买。这种情况,代购就是购买行为的执行者,而我们就是被代理对象。这就满足了代理模式应用场景的三个必要条件:
1.两个角色:执行者(采购),被代理对象(小伙伴)
2.执行者必须有被代理对象的引用(登记采购信息)
3.必须要做的事(废话,不买人就没了)
下面我们看看jdk与cglib是如何做到的
JDK动态代理
首先我们先定义一个接口Person
public interface Person {
void shop();
}
我们的小伙伴出场,他要shop
public class Friend implements Person {
private String name = "小伙伴";
private String adress = "火星";
private String material = "火腿肠";
@Override
public void shop() {
System.out.println("我叫"+this.name+",我住在"+this.adress+",我要买"+this.material);
}
}
但是我们的小伙伴不能出门,也就买不了火腿肠。这是采购人员来帮我们了
public class Caigou implements InvocationHandler {
private Person target;//引用
//获取引用 返回新的代理类实例
public Object getInstantce(Person target) throws Exception {
this.target = target;
Class<?> clazz = target.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
@Override
public Object invoke(Object arg0, Method method , Object[] args) throws Throwable {
System.out.println("我是采购");
//反射调用
method.invoke(this.target,args);
System.out.println("开始采购");
System.out.println("采购完成");
return null;
}
}
使用jdk的动态代理需要实现InvocationHandler。代理应用场景三个条件。我们有了两个对象,在newProxyInstance()中被代理类的引用也给了代理。newProxyInstance返回了指定接口新的代理类,第一个参数需要类加载器,第二个也就是我们指定的接口,第三个需要一个InvocationHandler也Caigou。我们一并将这个返回打印看看。
public class TestShop {
public static void main(String[] args) {
try {
Person person = (Person)new Caigou().getInstantce(new Friend());
System.out.println(person.getClass());
person.shop();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class com.sun.proxy.$Proxy0
我是采购
我叫小伙伴,我住在火星,我要买火腿肠
开始采购
采购完成
代理成功了,但是我们打印的Person的类对象为什么是class com.sun.proxy.$Proxy0
我们获取$Proxy0的字节码一探究竟。
public static void main(String[] args) {
try {
Person person = (Person)new Caigou().getInstantce(new Friend());
System.out.println(person.getClass());
person.shop();
//输出字节码文件
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
FileOutputStream fos = new FileOutputStream("输出$Proxy0.class文件地址");
fos.write(data);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
用反编译工具查看。
public final class $Proxy0 extends Proxy implements Person {
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 shop() throws {
try {
//super.h在调用newProxyInstance的第三个参数Caigou,这里实际上就是在执行Caigou的invoke方法。
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.*.proxy.jdk.Person").getMethod("shop");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
$Proxy0继承了Proxy,实现了Person。显然main方法中 person.shop()执行的是$Proxy0的shop()了,里面就只有一句代码,super.h.invoke(this, m3, (Object[])null); super.h就是在调用newProxyInstance的第三个参数给的this也就是Caigou,那这里实际上就是在执行Caigou的invoke方法了。
jdk的动态代理基本步骤:
- 获取被代理对象的引用与所实现的接口
- jdk动态生成类继承了Proxy 实现获取的接口
- 动态加载这个新的类
- 执行代理操作
总结jdk动态代理过程:字节码重组。