一、JDK动态代理
实现方式
JDK动态代理通过反射的方式实现代理。
只能对实现接口的类进行代理。
1、创建接口
public interface B { void foo(String name); }
2、创建被代理类(需要实现该接口)
public class BImpl implements B { @Override public void foo(String name) { System.out.println("Hello, " + name); } }
3、创建代理对象并进行测试
public class JdkProxy { public static void main(String[] args) { //被代理的对象 BImpl b = new BImpl(); //被代理类BImpl的Class Class bClass = b.getClass(); // 使用JDK动态代理创建代理对象proxyB B proxyB = (B) Proxy.newProxyInstance(bClass.getClassLoader(), bClass.getInterfaces(), new InvocationHandler() { /** * 拦截代理对象执行的方法:当proxyB调用foo()方法的时候进行拦截 * @param proxy 代理对象:proxyB * @param method 代理对象执行的方法: foo(),实际上是接口的方法 * @param args 方法的参数:name * @return 实际的返还值, 如果没有返还值则为null * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object invoke = method.invoke(b, args); return invoke; } }); //代理对象执行方法 proxyB.foo("张三"); } }
4、invoke()方法调试查看
JDK动态代理过程解析
随着JDK版本的不断更新,JDK动态代理的速度也越来越快。
1、底层使用java反射来执行方法导致速度变慢
1.1、查看生成的代理类
因为代理类是存储来JVM内存中的,没有写入到本地磁盘。所以我们需要在main方法中加入下面的代码来让其创建的时候保存到本地磁盘,然后在当前项目的com/sun/proxy文件夹下查看。
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
生成的代理类的class文件:
使用super.h.invoke(this, m3, new Object[]{var1})来通过反射执行方法
package com.sun.proxy; import com.gypsophila.reflect.jdk.B; 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 B { 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 foo(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.gypsophila.reflect.jdk.B").getMethod("foo", 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()); } } }
1.2、探究反射慢的原因
运行时加入-XX:+TraceClassLoading参数查看类加载情况
1)、测试代码
public class TestClassLoad { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("com.gypsophila.reflect.cglib.B"); Object o = clz.newInstance(); Method m = clz.getMethod("foo", String.class); for (int i = 0; i < 16; i++) { m.invoke(o, Integer.toString(i)); } } }
2、源码
2.1、查看Method.invoke()方法的底层实现
可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。
2.2、MethodAccessor
两个实现类
1)、在第一次调用一个实际Java方法对应得Method对象的invoke()方法时使用newMethodAccessor()构造方法创建MethodAccessor。
public MethodAccessor newMethodAccessor(Method var1) { checkInitted(); if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) { return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers()); } else { //第一次实例化创建 NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1); DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2); var2.setParent(var3); //最后返回的MethodAccessor为DelegatingMethodAccessorImpl /* 实际的Java方法只有一个对应的Method对象作为root。这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时新创建Method对象把root包装起来再给用户。 */ return var3; } }
2)、DelegatingMethodAccessorImpl是一个间接层,方便在native与Java版的MethodAccessor之间实现切换。
class DelegatingMethodAccessorImpl extends MethodAccessorImpl { private MethodAccessorImpl delegate; DelegatingMethodAccessorImpl(MethodAccessorImpl var1) { this.setDelegate(var1); } //invoke()方法 public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { return this.delegate.invoke(var1, var2); } void setDelegate(MethodAccessorImpl var1) { this.delegate = var1; } }
3)、在超过阈值(默认值是15)前每次调用NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版,在下一次调用的时候会使用Java版的MethodAccessor。
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers()); this.parent.setDelegate(var3); } return invoke0(this.method, var1, var2); }
Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。
这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
4)、本文开头的例子的B.foo(),生成的Java版MethodAccessor大致如下:
1)、使用RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();获取进程ID。
2)、进入JAVA_HOME的lib目录下,在命令行执行java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB就可唤起HSDB的图形界面,点击File-》Attach to Hotspot Process,输入进程ID,点击OK。
3)、代码
package sun.reflect; import com.gypsophila.reflect.cglib.B; import java.lang.reflect.InvocationTargetException; public class GeneratedMethodAccessor1 extends MethodAccessorImpl { public Object invoke(Object var1, Object[] var2) throws InvocationTargetException { if (var1 == null) { throw new NullPointerException(); } else { B var10000; String var10001; try { //获得B的实现类对象 var10000 = (B)var1; //这里因为自动解析只有一个参数所以去掉了循环 if (var2.length != 1) { throw new IllegalArgumentException(); } //将参数拆箱 var10001 = (String)var2[0]; } catch (NullPointerException | ClassCastException var4) { throw new IllegalArgumentException(var4.toString()); } try { //调用目标方法 var10000.foo(var10001); return null; } catch (Throwable var3) { throw new InvocationTargetException(var3); } } } public GeneratedMethodAccessor1() { } }
JDK动态代理慢的原因
1、Method.invoke()本身要用Object数组包装参数
2、每次调用都必须检查方法的可见性(在Method.invoke()里)
3、,也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里)
4、编译器不会对反射代码进行调优
二、cglib动态代理
实现方式
采用继承代理类的方式进行代理。
类无论是实现接口还是没有实现接口都可以进行代理,但是不能对有final修饰的类或有final修饰的方法进行代理。
1、导入依赖
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
2、创建代理对象并进行测试
//用于创建代理对象 Enhancer enhancer = new Enhancer(); //设置被代理的父类 enhancer.setSuperclass(B.class); //设置如何代理 enhancer.setCallback(new MethodInterceptor() { /** * * @param o 增强对象:enhancedB * @param method 被拦截的方法 * @param objects 方法的参数 * @param methodProxy 方法签名的表示形式,包含方法名称,返回类型和参数类型。用于调用父类(非拦截方法);可以根据需要多次调用 * @return 最终返还值 * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o,objects); } }); //创建代理对象 B enhancedB = (B) enhancer.create(); enhancedB.foo("张良");
3、invoke()方法调试
MethodInterceptor会拦截增强对象执行的方法(在增强对象中进行了覆盖重写)。
为什么不用以下的方式?
method.invoke(o,objects); methodProxy.invoke(o,objects);
因为最终执行的还是增强对象的方法,会被再次拦截,一直递归,最终导致栈溢出。
cglib动态代理过程解析
1、查看生成的代理类
在main方法中使用以下代码将被代理类保存在本地磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:/");
代理方式:
public class B1ea38d09 extends B implements Factory {
public class B$$EnhancerByCGLIB$$1ea38d09 extends B implements Factory { private boolean CGLIB$BOUND; public static Object CGLIB$FACTORY_DATA; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static Object CGLIB$CALLBACK_FILTER; private static final Method CGLIB$foo$0$Method; private static final MethodProxy CGLIB$foo$0$Proxy; private static final Object[] CGLIB$emptyArgs; private static final Method CGLIB$equals$1$Method; private static final MethodProxy CGLIB$equals$1$Proxy; private static final Method CGLIB$toString$2$Method; private static final MethodProxy CGLIB$toString$2$Proxy; private static final Method CGLIB$hashCode$3$Method; private static final MethodProxy CGLIB$hashCode$3$Proxy; private static final Method CGLIB$clone$4$Method; private static final MethodProxy CGLIB$clone$4$Proxy; static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.gypsophila.reflect.cglib.B$$EnhancerByCGLIB$$1ea38d09"); Class var1; CGLIB$foo$0$Method = ReflectUtils.findMethods(new String[]{"foo", "(Ljava/lang/String;)V"}, (var1 = Class.forName("com.gypsophila.reflect.cglib.B")).getDeclaredMethods())[0]; CGLIB$foo$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "foo", "CGLIB$foo$0"); Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$equals$1$Method = var10000[0]; CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = var10000[1]; CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = var10000[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = var10000[3]; CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); } final void CGLIB$foo$0(String var1) { super.foo(var1); } public final void foo(String var1) { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$foo$0$Method, new Object[]{var1}, CGLIB$foo$0$Proxy); } else { //调用被代理对象的方法 super.foo(var1); } } final boolean CGLIB$equals$1(Object var1) { return super.equals(var1); } public Callback[] getCallbacks() { CGLIB$BIND_CALLBACKS(this); return new Callback[]{this.CGLIB$CALLBACK_0}; } public void setCallbacks(Callback[] var1) { this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0]; } static { CGLIB$STATICHOOK1(); } }