如果我们要为target类创建一个【JDK动态代理对象】,那么我们必须要传入如下三个核心参数
为什么必须要这三个参数呢?之前使用动态代理的时候都是直接按接口要求传这三个参数,但从来没想过为什么?下面仔细去探究一下
【JDK动态代理】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class>[] interfaces,InvocationHandler h)
】方法,去创建的动态代理对象,我们这里也使用这个方法去创建一个简单的【动态代理对象】以便于理解他的核心原理。
public interface Subject {
/**
* 接口方法
*/
void doSomething();
/**
* sayHello
*
* @param name name
* @return string
*/
String sayHello(String name);
}
public class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject do something");
}
@Override
public String sayHello(String name) {
System.out.println("RealSubject sayHello");
return "hello-" + name;
}
}
Proxy.newProxyInstance
来为目标对象创建代理对象public class JdkDynamicProxyFactory {
/**
* 创建target类的代理对象
* 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
*
* @param 泛型
* @return 代理对象
*/
public static <T> T getProxy(Object target) {
// 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
Object proxyInstance = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行目标方法前");
// 执行目标方法
Object result = method.invoke(target, args);
System.out.println("执行目标方法后");
// 返回目标方法的执行结果
return result;
}
});
// 返回代理对象
return (T) proxyInstance;
}
}
public class Client {
public static void main(String[] args) {
// 保存生成的代理类的字节码文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 目标对象
RealSubject target = new RealSubject();
// 使用JDK动态代理为【target对象】创建代理对象
Subject proxy = JdkDynamicProxyFactory.getProxy(target);
// 调用代理对象的方法
proxy.doSomething();
System.out.println("=====================华丽的分割线=====================");
proxy.sayHello("wenpan");
}
}
在上面一步中我们使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样
doSomething方法
),实质上是调用的【Proxy类】的【h属性】的invoke方法
Proxy.h
】到底是个什么鬼?其实他就是创建代理对象是我们传入的【InvocationHandler
】// 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject
public final class $Proxy0 extends Proxy implements Subject {
// 2、可以看到,在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
// 以静态代码块的形式为属性赋值
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("doSomething");
m4 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
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 $Proxy0(InvocationHandler var1) throws {
super(var1);
}
// 3、object父类的equals方法
public final boolean equals(Object var1) throws {
try {
// 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
// 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
// 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 4、目标接口的方法
public final void doSomething() throws {
try {
// 调用了【Proxy.h属性】的invoke方法
// 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 5、目标接口的方法
public final String sayHello(String var1) throws {
try {
// 调用了【Proxy.h属性】的invoke方法
// 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
return (String)super.h.invoke(this, m4, 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 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);
}
}
}
下面的源代码我做了一些删减,只留下了最核心的部分
newProxyInstance
】方法创建代理对象时所做的几件事情Proxy
类的【Class对象】supper.h.invoke
调用)public class Proxy implements java.io.Serializable {
// h属性,保存我们传递进来的InvocationHandler
protected InvocationHandler h;
// 【有参构造器】注意这里的参数
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
// 生成代理对象的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException{
// 1、InvocationHandler强制不允许为空
Objects.requireNonNull(h);
// 获取到目标接口
final Class<?>[] intfs = interfaces.clone();
/*
* Look up or generate the designated proxy class.
* 2、获取到代理类的Class对象(也就是Proxy)
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* 通过反射执行 cl 的有参构造,也就是下面这个,可以看到通过反射执行Proxy有参构造,
* 将InvocationHandler赋值到了h属性上
*/
try {
// 3、获取到有参构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 4、通过构造器来创建一个代理对象并返回,这里传入的参数h 就是我们的【InvocationHandler】
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
// 省略....
}
}
}
现在再来看上面抛出的三个问题!为什么创建代理对象时需要传入如下三个参数:
因为动态代理类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而动态代理类则是在【运行过程中动态生成的类】。那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】
为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行
InvocationHandler
最终会被作为一个属性保存到Proxy对象的【h属性】上supper.h.invoke
的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler
InvocationHandler.invoke()方法