转载

JDK动态代理为什么不能代理类

JDK动态代理为什么不能代理类 戳蓝字「TopCoder」关注我们哦!

关于JDK动态代理,想必小伙伴都知道,它只能代理接口!但是有的小伙伴可能会有疑问,为什么它只能代理接口,不能代理类呢?这里借助某位技术大佬的话--”源码面前,了无秘密“,下面咱们就一起看下JDK动态代理源码实现,最后再探讨下JDK动态代理为什么不能代理类,OK,话不多说,Let's go~

本文分为两部分来分析JDK动态代理,首先看下JDK动态代理使用,然后分析动态代理源码实现及探讨下其为什么不能代理类。 (ps:已经熟悉JDK动态代理基础概念及使用的小伙伴可以跳过第一部分)

JDK动态代理使用

Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用JDK动态代理需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口(通过生成字节码机制)生成一个代理类,然后在这个代理类中织入代码(被代理的类是AOP里所说的目标,InvocationHandler是切面)。

动态代理是在运行期间通过接口生成代理类的,与静态代理相比更加灵活,但是也有一定的限制,第一是代理对象必须实现一个接口,否则会报异常,因为人家原理就是根据接口来生成代理对象的。第二是有性能问题,因为是通过反射来实现调用的,所以比正常的直接调用来得慢,并且通过生成类文件也会多消耗部分方法区空间,可能引起Full GC。

一个JDK动态代理的简单使用:

public static interface Hello {

void hi(String msg);

}

public static class HelloImpl implements Hello {

@Override

public void hi(String msg) {

System.out.println("hello " + msg);

}

}


/**

* 代理类

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

public static class HelloProxy implements InvocationHandler {

private Object proxied = null;


@Override

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

System.out.println("hello proxy");

return method.invoke(proxied, args);

}

}


public static void main(String[] args) {

Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class[]{Hello.class}, new HelloProxy(new HelloImpl()));


System.out.println(hello.getClass()); // class com.sun.proxy.$Proxy0

hello.hi("world");

}

通过调用Proxy静态方法 Proxy.newProxyInstance() 可以创建动态代理,这个方法需要得到一个类加载器,一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler的一个实现类。动态代理可以将所有调用重定向到调用处理器,因此通常会调用处理器的构造器传递一个”实际”对象的引用,从而将调用处理器在执行中介任务时,将请求转发。

JDK动态代理为什么不能代理类

上述示例代码我们看到了 method.invoke ,这不是反射么,动态代理和反射到底是个什么关系 ?动态代理底层是基于反射来实现的,这么说也不算错但稍微有些不全面。以上述代码为例,首先是写一个InvocationHandler实现类并调用Proxy.newProxyInstance生成代理类,然后在InvocationHandler实现类中通过反射调用对应的代理方法。最后看下上述代码的调用栈信息:在默认情况下,方法的反射调用为委派实现,委派给本地实现来进行方法调用。在调用超过 15 次之后,委派实现便会将委派对象切换至动态实现。这个动态的字节码是在Java运行过程中通过ASM自动生成的,它将直接使用 invoke 指令来调用目标方法。由于本文重点是讨论动态代理,所以针对反射就不再详细讨论了。

动态代理原理

以上述示例代码为例,默认情况下在运行时程序自动生成proxy class,如果我们想看下自动生成proxy clas到底长什么样该如何办呢?可以通过参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true (或者执行 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); )来保存成对应的class文件,比如示例代码中生成的class文件如下(在IDEA中查看源码):

JDK动态代理为什么不能代理类

//

// Source code recreated from a .class file by IntelliJ IDEA

// (powered by Fernflower decompiler)

//


package com.sun.proxy;


import com.youzan.owl.goingmerry.util.ProxyMain.Hello;

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 Hello {

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 void hi(String var1) throws {

try {

super.h.invoke(this, m3, 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);

}

}


static {

try {

m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));

m3 = Class.forName("com.youzan.owl.goingmerry.util.ProxyMain$Hello").getMethod("hi", 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());

}

}

}


下面开始分析JDK动态代理生成proxy class流程源码,以上述示例代码为例,从 Proxy.newProxyInstance() 方法开始,由于篇幅有限,以下代码分析只贴出主要流程代码。首先加载被代理类的接口的class,然后通过执行方法 java.lang.reflect.Proxy.ProxyClassFactory#apply 创建对应proxy class:

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


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

for (Class<?> intf : interfaces) {

Class<?> interfaceClass = null;

try {

interfaceClass = Class.forName(intf.getName(), false, loader);

} catch (ClassNotFoundException e) {

}

/*

* 判断不是接口则抛异常,说明了JDK动态代理只能对接口代理

* 不过到这里还不能说明为什么不能对类进行代理

*/

if (!interfaceClass.isInterface()) {

throw new IllegalArgumentException(

interfaceClass.getName() + " is not an interface");

}

//...

}


String proxyPkg = null; // package to define proxy class in

int accessFlags = Modifier.PUBLIC | Modifier.FINAL;


/*

* Record the package of a non-public proxy interface so that the

* proxy class will be defined in the same package. Verify that

* all non-public proxy interfaces are in the same package.

*/

for (Class<?> intf : interfaces) {

int flags = intf.getModifiers();

if (!Modifier.isPublic(flags)) {

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) {

// if no non-public proxy interfaces, use com.sun.proxy package

// public static final String PROXY_PACKAGE = "com.sun.proxy";

// 默认生成的proxy class 包路径

proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";

}


/*

* Choose a name for the proxy class to generate.

* 自动生成一个proxy class,序号+1

*/

long num = nextUniqueNumber.getAndIncrement();

String proxyName = proxyPkg + proxyClassNamePrefix + num; // proxyClassNamePrefix = "$Proxy";


/*

* Generate the specified proxy class.

* 开始生成动态proxy class,这里使用了ProxyGenerator类

*/

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

proxyName, interfaces, accessFlags);

try {

return defineClass0(loader, proxyName,

proxyClassFile, 0, proxyClassFile.length);

} catch (ClassFormatError e) {

/*

* A ClassFormatError here means that (barring bugs in the

* proxy class generation code) there was some other

* invalid aspect of the arguments supplied to the proxy

* class creation (such as virtual machine limitations

* exceeded).

*/

throw new IllegalArgumentException(e.toString());

}

}

关于 ProxyGenerator.generateProxyClass() ,感兴趣的同学可以自行阅读对应代码,其内部主要就是按照固定“ 模板 ”按照字节码格式生成对应class字节码,大致就是实现要代理接口的所有public方法及固定的方法(hashCode、equals和toString),然后调用InvocationHandler的invoke方法,也就是走到示例代码中的 HelloProxy 类( class HelloProxy implements InvocationHandler )。

其实我们还可以在程序中直接调用 ProxyGenerator.generateProxyClass() 来生成动态代理类,如下所示:

String name = "$Proxy00";

byte[] data = ProxyGenerator.generateProxyClass("com.sun.proxy." + name, new Class[] { Hello.class } );

FileOutputStream out = new FileOutputStream("/tmp/" + name + ".class" );

out.write(data);

out.close();

这里可以对比下$Proxy00.class和JDK动态代理生成的$Proxy0.class文件,二者除了类名之外,代码都是相同的。

最后来看下JDK动态代理为什么不能代理类,对照着生成的$Proxy0.class文件,如下所示一起来分析下:

public final class $Proxy0 extends Proxy implements Hello {

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 void hi(String var1) throws {

try {

super.h.invoke(this, m3, new Object[]{var1});

} catch (RuntimeException | Error var3) {

throw var3;

} catch (Throwable var4) {

throw new UndeclaredThrowableException(var4);

}

}

类Proxy的作用简单来说就是保存下用于自定义的InvocationHandler,便于在方法代理时执行自定义InvocationHandler的逻辑。由于$Proxy0已经继承了类Proxy,所以不能再extends一个类了,所以只能implements一个接口了。

参考资料:

  1. https://mp.weixin.qq.com/s/58CkiW1ZjqiwLWctJbBVFg

  2. https://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html

原文  http://mp.weixin.qq.com/s?__biz=MzIwNTI2ODY5OA==&mid=2649938597&idx=3&sn=db7f8f2fda6dfa702579eca437736692
正文到此结束
Loading...