静态代理、动态代理、Hook,它们之间到底是什么关系?

Proxy.getProxyClass(ClassLoader, interfaces)方法只需要接收一个类加载器和一组接口就可以返回一个代理Class对象,然后就可以通过反射创建代理实例;其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在我们的角度就是通过接口Class对象创建代理类Class对象

这里通过一个很骚的比喻来说明下:一个东厂太监(接口Class对象)有一家子财产,但是为了侍奉皇帝这一伟大事业,毅然决然的割了DD(没有构造方法),虽然实现了自己的理想,但是不能繁育下一代(不能构造器创建对象),也就没有后人继承自己的家业;但是好在华佗在世,江湖上有一个高人(Proxy),发明了一个克隆大法(getProxyClass),不仅克隆出了几乎和太监一样的下一代(新的Class),还拥有自己的小DD(构造方法),这样这个下一代就能继承太监的家产(类结构信息,其实是实现了该接口),同时还能娶妻生子,传给下一代(创建实例)

用一副图展示动态代理和静态代理实现思路:

那这样就很简单了

public static Object loadProxy(Object target) throws Exception {

//通过接口Class对象创建代理Class对象

Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());

//拿到代理Class对象的有参构造方法

Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class);

//反射创建代理实例

Object proxy = constructors.newInstance(new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println(“执行前日志…”+“\n”);

//执行目标类的方法

Object result = method.invoke(target, args);

System.out.println(“执行后日志…”+“\n”);

return result;

}

});

return proxy;

}

public static void main(String[] args) throws Exception {

ILogin proxy = (ILogin) loadProxy(new UserLogin());

proxy.userLogin();

}

看看打印结果

这样无论系统有多少目标类,通过传进来的目标类都可以获取到对应的代理对象,就达到我们在执行目标类前后加日志的效果了,同时还不需要编写代理类

Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,如下

public static Object loadProxy(Object object) {

return Proxy.newProxyInstance(

object.getClass().getClassLoader(), //和目标对象的类加载器保持一致

object.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成代理对象

new InvocationHandler() { //事件处理器,即对目标对象方法的执行

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println(“执行前日志…”);

Object result = method.invoke(object, args);

System.out.println(“执行后日志…”);

return result;

}

});

}

JDK动态代理总结:

  • 优点:相对于静态代理,极大的减少类的数量,降低工作量,减少对业务接口的依赖,降低耦合,便于后期维护;同时在某些情况下是最大的优势,即可以统一修改代理类的方法逻辑,而不需要像静态代理需要修改每个代理类

  • 缺点:因为使用的是反射,所以在运行时会消耗一定的性能;同时JDK代理只支持interface的动态代理,如果你再继续深究源码,会发现,所有动态生成的代理对象都有一个共同的父类,即都继承于Proxy;

Java的单继承机制决定了无法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,无法像静态代理中的代理类可以在内部扩展更多的功能

动态生成代理对象原理

在动态代理的过程中,我们不能清晰的看到代理类的实际样子,而且被代理对象和代理对象是通过InvocationHandler来完成的代理过程,其中代理对象是如何生成的,具体什么样,为什么代理对象执行的方法都会走到InvocationHandler中的invoke方法,要想了解这些就需要对Java是如何动态生成代理对象源码进行分析,继续往下看

你们有没有好奇getProxyClass这个方法是怎么通过【目标类实现的接口】生成代理Class对象的呢?

如果你在第一个方法输入如下代码

System.out.println(“执行日志…”+(proxy instanceof Proxy)+“\n”);

System.out.println(“执行日志…”+(proxyClass.getName())+“\n”);

System.out.println(“执行日志…”+(proxyClass.getSuperclass().getName())+“\n”);

System.out.println(“执行日志…”+(proxyClass.getSuperclass().getSimpleName())+“\n”);

System.out.println(“执行日志…”+(proxyClass.getInterfaces()[0].getSimpleName())+“\n”);

得到的结果是

  • 第一个结果为true,说明代理对象属于Proxy类型

  • 第二个结果是代理对象的全限定类名

  • 第三个结果是代理对象的父类的全限定名,第四个结果是父类名

  • 第四个结果是代理对象实现的接口名

从这四点是不是能知道点啥了:动态生成的代理对象的父类是Proxy,实现了ILogin接口;这也就是为什么能将代理对象强转成ILogin,从而调用其接口方法;

也说明了为什么只能支持动态生成接口代理,不能动态生成class代理,因为最终生成的代理对象肯定会继承Proxy类,如果我们提供的类已经继承了其它类,那就不能继承Proxy类了,动态代理也就无从谈起了

接下来再从源码分析下其原理,鉴于篇幅原因,这里只分析重点代码

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

/**

  • 如果存在实现给定接口的给定加载器定义的代理类,则只返回缓存副本; 否则,它将通过ProxyClassFactory创建代理类

*/

private static Class<?> getProxyClass0(ClassLoader loader,Class<?>… interfaces) {

if (interfaces.length > 65535) {

throw new IllegalArgumentException(“interface limit exceeded”);

}

return proxyClassCache.get(loader, interfaces);

}

private static final class ProxyClassFactory

implements BiFunction<ClassLoader, Class<?>[], Class<?>>

{

// 所有代理类名称的前缀

private static final String proxyClassNamePrefix = “$Proxy”;

// 用于生成唯一代理类名称的下一个数字

private static final AtomicLong nextUniqueNumber = new AtomicLong();

@Override

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);

for (Class<?> intf : interfaces) {//通过传入的接口clone后的接口

String proxyPkg = null; // 定义代理class的包名

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

for (Class<?> intf : interfaces) {

int flags = intf.getModifiers();//获取接口修饰符

if (!Modifier.isPublic(flags)) {//我们定义的接口修饰符都是public,所以这里不会进去

accessFlags = Modifier.FINAL;

String name = intf.getName();

int n = name.lastIndexOf(‘.’);

String pkg = ((n == -1) ? “” : name.substring(0, n + 1));

if (proxyPkg == null) {

proxyPkg = pkg;

} else if (!pkg.equals(proxyPkg)) {

throw new IllegalArgumentException(

“non-public interfaces from different packages”);

}

}

}

if (proxyPkg == null) {

// 如果没有非public代理接口,请使用com.sun.proxy包

proxyPkg = ReflectUtil.PROXY_PACKAGE + “.”;

}

/*

  • 生成代理class名称

  • 比如com.sun.proxy.$Proxy0

*/

long num = nextUniqueNumber.getAndIncrement();

String proxyName = proxyPkg + proxyClassNamePrefix + num;

/*

  • 通过接口和class名称生成代理class数据

  • 如果继续看generateProxyClass,会发现里面是生成class数据,包括写入Object的三个初始方法、写入实现的接口、写入继承Proxy类等到ByteArrayOutputStream中,然后转换成byte数组返回,至于是否会将Byte数据写到class文件保存在本地,视情况而定(默认不保存到硬盘)

*/

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

proxyName, interfaces, accessFlags);

try {

//将动态生成的class数据加载到内存中 生成一个代理class对象

return defineClass0(loader, proxyName,

proxyClassFile, 0, proxyClassFile.length);

} catch (ClassFormatError e) {

throw new IllegalArgumentException(e.toString());

}

}

}

生成class数据源码

ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析:

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {

ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);

final byte[] var4 = var3.generateClassFile();

//是否将生成的class数据保存到硬盘,默认不保存

if (saveGeneratedFiles) {

}

return var4;

}

private byte[] generateClassFile() {

//添加hashCode方法

this.addProxyMethod(hashCodeMethod, Object.class);

//添加equals方法

this.addProxyMethod(equalsMethod, Object.class);

//添加toString方法

this.addProxyMethod(toStringMethod, Object.class);

Class[] var1 = this.interfaces;

int var2 = var1.length;

int var3;

Class var4;

//遍历接口数组

for(var3 = 0; var3 < var2; ++var3) {

var4 = var1[var3];

Method[] var5 = var4.getMethods();

int var6 = var5.length;

//添加接口里的方法,此时方法体还为空

for(int var7 = 0; var7 < var6; ++var7) {

Method var8 = var5[var7];

this.addProxyMethod(var8, var4);

}

}

Iterator var11 = this.proxyMethods.values().iterator();

List var12;

while(var11.hasNext()) {

var12 = (List)var11.next();

checkReturnTypes(var12);

}

Iterator var15;

try {

//添加一个带有InvocationHandler的构造方法

this.methods.add(this.generateConstructor());

var11 = this.proxyMethods.values().iterator();

//生成方法体代码

while(var11.hasNext()) {

var12 = (List)var11.next();

var15 = var12.iterator();

while(var15.hasNext()) {

ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();

this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, “Ljava/lang/reflect/Method;”, 10));

//方法体里生成调用InvocationHandler的invoke方法代码

this.methods.add(var16.generateMethod());

}

}

this.methods.add(this.generateStaticInitializer());

} catch (IOException var10) {

throw new InternalError(“unexpected I/O Exception”, var10);

}

if (this.methods.size() > 65535) {

throw new IllegalArgumentException(“method limit exceeded”);

} else if (this.fields.size() > 65535) {

throw new IllegalArgumentException(“field limit exceeded”);

} else {

this.cp.getClass(dotToSlash(this.className));

this.cp.getClass(“java/lang/reflect/Proxy”);

var1 = this.interfaces;

var2 = var1.length;

//生成实现接口,继承Proxy类代码

for(var3 = 0; var3 < var2; ++var3) {

var4 = var1[var3];

this.cp.getClass(dotToSlash(var4.getName()));

}

this.cp.setReadOnly();

ByteArrayOutputStream var13 = new ByteArrayOutputStream();

DataOutputStream var14 = new DataOutputStream(var13);

try {

var14.writeInt(-889275714);

var14.writeShort(0);

var14.writeShort(49);

this.cp.write(var14);

var14.writeShort(this.accessFlags);

var14.writeShort(this.cp.getClass(dotToSlash(this.className)));

var14.writeShort(this.cp.getClass(“java/lang/reflect/Proxy”));

var14.writeShort(this.interfaces.length);

Class[] var17 = this.interfaces;

int var18 = var17.length;

for(int var19 = 0; var19 < var18; ++var19) {

Class var22 = var17[var19];

var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));

}

var14.writeShort(this.fields.size());

var15 = this.fields.iterator();

while(var15.hasNext()) {

ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();

var20.write(var14);

}

var14.writeShort(this.methods.size());

var15 = this.methods.iterator();

while(var15.hasNext()) {

ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();

var21.write(var14);

}

var14.writeShort(0);

return var13.toByteArray();

} catch (IOException var9) {

throw new InternalError(“unexpected I/O Exception”, var9);

}

}

}

从源码可以得出:

1. JDK帮我们生成了这样一个class数据,它继承了Proxy类,添加了一个带InvocationHandler参数的构造方法,这样也就明白了为什么使用构造方法反射创建代理对象的时候传入了一个InvocationHandler参数,因为默认会调用到Proxy类的构造方法,其参数正好是InvocationHandler,赋值给内部的成员变量h

protected Proxy(InvocationHandler h) {

Objects.requireNonNull(h);

this.h = h;

}

2. 实现了我们指定的接口,并实现了接口里的方法,同时接口中的方法调用了InvocationHandler的invoke方法

3. 当代理对象执行方法的时候,方法里面都会执行InvocationHandler的invoke方法,我们就可以在这里执行目标类方法

这样可以说动态生成代理class对象其实是动态生成代理class数据

动态代理类真身

使用如下代码将动态生成的class数据保存到硬盘

public static void write(){

byte[] classFile = ProxyGenerator.generateProxyClass(“$Proxy0”, UserLogin.class.getInterfaces());

String path = “D:/Workspaces/com/sun/proxy/LoginProxy.class”;

try(FileOutputStream fos = new FileOutputStream(path)) {

fos.write(classFile);

fos.flush();

System.out.println(“代理类class文件写入成功”);

} catch (Exception e) {

System.out.println(“写文件错误”);

}

}

接下来将这个class文件反编译成Java文件看看:如果你没有jad工具,可以通过JAD Java Decompiler(https://varaneckas.com/jad/)下载,使用如下命令

/**

  • -d :用户指定输出文件保存目录

  • d:\ :具体目录目录

  • -sjava :输出文件扩展名 这里保存成Java文件

  • D:\Workspaces\com\sun\proxy\LoginProxy.class :class文件

*/

jad -d d:\ -sjava D:\Workspaces\com\sun\proxy\LoginProxy.class

/**

  • 上面的命令没有指定保存的Java文件名,下面的命令可以指定保存文件名

  • -p 输出详细文件信息

*/

jad -p D:\Workspaces\com\sun\proxy\LoginProxy.class > D:\Workspaces\com\sun\proxy\LoginProxy.java

代理类:

public final class $Proxy0 extends Proxy implements ILogin {

public $Proxy0(InvocationHandler invocationhandler)

{

super(invocationhandler);

}

public final boolean equals(Object obj)

{

try

{

return ((Boolean)super.h.invoke(this, m1, new Object[] {

obj

})).booleanValue();

}

catch(Error _ex) { }

catch(Throwable throwable)

{

throw new UndeclaredThrowableException(throwable);

}

}

public final String toString()

{

try

{

return (String)super.h.invoke(this, m2, null);

}

catch(Error _ex) { }

catch(Throwable throwable)

{

throw new UndeclaredThrowableException(throwable);

}

}

public final void userLogin()

{

try

{

super.h.invoke(this, m3, null);

return;

}

catch(Error _ex) { }

catch(Throwable throwable)

{

throw new UndeclaredThrowableException(throwable);

}

}

public final int hashCode()

{

try

{

return ((Integer)super.h.invoke(this, m0, null)).intValue();

}

catch(Error _ex) { }

catch(Throwable throwable)

{

throw new UndeclaredThrowableException(throwable);

}

}

private static Method m1;

private static Method m2;

private static Method m3;

private static Method m0;

static

{

try

{

m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, new Class[] {

Class.forName(“java.lang.Object”)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值