老生常谈系列之Aop--JDK动态代理的底层实现原理
老生常谈系列之Aop--JDK动态代理的底层实现原理
前言
在Aop系列里面有两篇文章,分别是老生常谈系列之Aop--Spring Aop原理浅析和老生常谈系列之Aop--Spring Aop源码解析(二)都有涉及JDK动态代理的使用,但是没有详细分析JDK动态代理的实现原理,只是贴出了使用方法。本着知其然更要知其所以然的目标,这一篇文章,我们就来深扒一下JDK动态代理的实现原理。
原理分析
这里的代码分析是基于JDK1.8
Proxy.newProxyInstance()
说到
Proxy.newProxyInstance()方法,首先我们来回忆一下老生常谈系列之Aop--Spring Aop源码解析(二)文章中调用JDK动态代理的例子,可以看到如下代码。
public Object getProxy(){ return Proxy.newProxyInstance(calculateService.getClass().getClassLoader(), calculateService.getClass().getInterfaces(), new MyInvocationHandler (newCalculateServiceImpl())); }
我们是通过
Proxy.newProxyInstance()方法来获取一个代理类的,那么
Proxy.newProxyInstance()帮我们做了什么呢?
直接跟进源码,这里保留了方法上的英文注释,去除了抛出异常部分的注释,各位看官用心体会。同时这里的代码精简了一部分JDK的权限校验,只留下了核心代码并且添加了注释,逻辑还是比较简单的。
/** * Returns an instance of a proxy class for the specified interfaces * that dispatches method invocations to the specified invocation * handler. * * <p>{@code Proxy.newProxyInstance} throws * {@code IllegalArgumentException} for the same reasons that * {@code Proxy.getProxyClass} does. * * @param loader the class loader to define the proxy class * @param interfaces the list of interfaces for the proxy class * to implement * @param h the invocation handler to dispatch method invocations to * @return a proxy instance with the specified invocation handler of a * proxy class that is defined by the specified class loader * and that implements the specified interfaces */ @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); final Class<?>[] intfs = interfaces.clone(); // 去除一些权限校验 /* * 查找或生成指定的代理类 * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, intfs); /* * 获取指定的构造函数,这里指定的构造函数为入参为InvocationHandler的构造函数 * Invoke its constructor with the designated invocation handler. */ try { // 去除一些权限校验 final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h; // 去除一些权限校验 // 调用构造函数生产代理类实例 return cons.newInstance(new Object[]{h});} catch (... e) { // 省略异常处理 } }
翻译一下:返回将方法调用分派到指定调用处理器的指定接口的代理类的实例。
显而易见,这里通过了一层转发,实现了在调用方法之前先回调到自定义的
InvocationHandler的
invoke()方法。这一实现原理,在前面的文章是有说到的。但是JDK动态代理是怎么生成了一个可以回调到自定义
InvocationHandler的代理类的呢?它是怎么将具体实现类的方法和生成的代理类的方法进行关联的呢?下面让我们来逐一解释。
getProxyClass0()
很显然,
getProxyClass0()是需要重点关注的方法。从这个方法的注释就可以看出来,这个方法是查找或生成指定的代理类,很显然,我们要获取的类对象的结构和内容是在这个方法里完成的。也就是说,这个方法会通过我们传入的
ClassLoader loader和
Class<?>[] interfaces来构造完成一个
Class<?>对象。
跟进代码
/** * Generate a proxy class. Must call the checkProxyAccess method * to perform permission checks before calling this. */ private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
好家伙,一进来发现这个方法异常简单,可以说就一行代码,只是通过
proxyClassCache去获取,如果存在缓存直接返回,否则通过
ProxyClassFactory来生成代理类,那我们来看一下
proxyClassCache是个什么东东。
/** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache是一个
WeakCache类型的缓存集合,该对象维护了两个
BiFunction<T, U, R>属性
private final BiFunction<K, P, ?> subKeyFactory; private final BiFunction<K, P, V> valueFactory;
对应到这里也就是上面代码的
KeyFactory和
ProxyClassFactory,分别对应一个Key生成工厂和value生成工厂,这两个属性会在
new WeakCache的时候赋值。我们可以重点关注
ProxyClassFactory里面的
apply(ClassLoader loader, Class<?>[] interfaces)方法,该方法就是实现了生成代理类Class对象的方法。
ProxyClassFactory
跟进
ProxyClassFactory代码,JDK的源码很多校验和控制,看起来没那么清晰,但是不要慌,把这些边边角角和异常控制去掉,逻辑还是很清晰的。
/** * A factory function that generates, defines and returns the proxy class given * the ClassLoader and array of interfaces. */ private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names // 代理类的名称 private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names // 生成代理类的序号 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) { /* * 验证类加载器是否将此接口的名称解析为相同的 Class 对象。 * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * 验证 Class 对象实际上是不是代表一个接口。 * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * 验证此接口不是重复的。 * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } 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 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; // 前面的都是校验和生成accessFlags,proxyName等信息 /* * 生成指定的代理类,可以看到委托给了ProxyGenerator.generateProxyClass()实现 * Generate the specified proxy class. */ 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()); } } }
上面的代码很长?
无所谓的,看到这一句就好了
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags),这里生产了代理类的
byte[]对象,然后再调用
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)生成一个
Class对象。
ProxyGenerator.generateProxyClass()
跟进
ProxyGenerator.generateProxyClass()方法。
/** * Generate a proxy class given a name and a list of proxy interfaces. * * @param name the class name of the proxy class * @param interfaces proxy interfaces * @param accessFlags access flags of the proxy class */ public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) { ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags); // 生成代理类的byte[]数组 final byte[] classFile = gen.generateClassFile(); // 是否保存动态代理生成的代码,sun.misc.ProxyGenerator.saveGeneratedFiles if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { try { int i = name.lastIndexOf('.'); Path path; if (i > 0) { Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar)); Files.createDirectories(dir); path = dir.resolve(name.substring(i+1, name.length()) + ".class"); } else { path = Paths.get(name + ".class"); } Files.write(path, classFile); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } return classFile; }
这个属性看着是不是很眼熟,我们通过设置这个属性来保存动态代理生成的字节码,这里也可以看到生效的原理,以及之前用的时候为啥说只对JDK动态代理生效。
/** debugging flag for saving generated class files */ private final static boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( new GetBooleanAction( "sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
到这里这个
ProxyGenerator才是真正的生产代理类的文件的
byte[]数组
/** * Construct a ProxyGenerator to generate a proxy class with the * specified name and for the given interfaces. * * A ProxyGenerator object contains the state for the ongoing * generation of a particular proxy class. */ private ProxyGenerator(String className, Class<?>[] interfaces, int accessFlags) { this.className = className; this.interfaces = interfaces; this.accessFlags = accessFlags; }
这个就是生成的具体逻辑了。
- 为所有方法组装 ProxyMethod 对象以生成代理调度代码。
- 为我们正在生成的类中的所有字段和方法组装 FieldInfo 和 MethodInfo 结构。
- 编写最终的类文件。
其中前面第一第二点的逻辑都较好理解,第三点就是在写class文件了,这个按照JVM的字节码结构去拼接一份class字节码。例如
dout.writeInt(0xCAFEBABE)在文件开头写入魔数,
dout.writeShort(CLASSFILE_MINOR_VERSION)和
dout.writeShort(CLASSFILE_MAJOR_VERSION)分别为写小版本号和大版本号。这些顺序都是JVM字节码规定的顺序,需要严格按照此顺序拼接。关于字节码更多的知识,可以看JVM字节码结构。这个方法的代码很长,但是我不打算删减,这里的步骤缺一不可。
/** * Generate a class file for the proxy class. This method drives the * class file generation process. */ private byte[] generateClassFile() { /* ============================================================ * Step 1: Assemble ProxyMethod objects for all methods to * generate proxy dispatching code for. */ /* * Record that proxy methods are needed for the hashCode, equals, * and toString methods of java.lang.Object. This is done before * the methods from the proxy interfaces so that the methods from * java.lang.Object take precedence over duplicate methods in the * proxy interfaces. */ addProxyMethod(hashCodeMethod, Object.class); addProxyMethod(equalsMethod, Object.class); addProxyMethod(toStringMethod, Object.class); /* * Now record all of the methods from the proxy interfaces, giving * earlier interfaces precedence over later ones with duplicate * methods. */ for (Class<?> intf : interfaces) { for (Method m : intf.getMethods()) { addProxyMethod(m, intf); } } /* * For each set of proxy methods with the same signature, * verify that the methods' return types are compatible. */ for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* ============================================================ * Step 2: Assemble FieldInfo and MethodInfo structs for all of * fields and methods in the class we are generating. */ try { methods.add(generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // add static field for method's Method object fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); // generate code for proxy method and add it methods.add(pm.generateMethod()); } } methods.add(generateStaticInitializer()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception", e); } if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } /* ============================================================ * Step 3: Write the final class file. */ /* * Make sure that constant pool indexes are reserved for the * following items before starting to write the final class file. */ cp.getClass(dotToSlash(className)); cp.getClass(superclassName); for (Class<?> intf: interfaces) { cp.getClass(dotToSlash(intf.getName())); } /* * Disallow new constant pool additions beyond this point, since * we are about to write the final constant pool table. */ cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { /* * Write all the items of the "ClassFile" structure. * See JVMS section 4.1. */ // u4 magic; dout.writeInt(0xCAFEBABE); // u2 minor_version; dout.writeShort(CLASSFILE_MINOR_VERSION); // u2 major_version; dout.writeShort(CLASSFILE_MAJOR_VERSION); cp.write(dout); // (write constant pool) // u2 access_flags; dout.writeShort(accessFlags); // u2 this_class; dout.writeShort(cp.getClass(dotToSlash(className))); // u2 super_class; dout.writeShort(cp.getClass(superclassName)); // u2 interfaces_count; dout.writeShort(interfaces.length); // u2 interfaces[interfaces_count]; for (Class<?> intf : interfaces) { dout.writeShort(cp.getClass( dotToSlash(intf.getName()))); } // u2 fields_count; dout.writeShort(fields.size()); // field_info fields[fields_count]; for (FieldInfo f : fields) { f.write(dout); } // u2 methods_count; dout.writeShort(methods.size()); // method_info methods[methods_count]; for (MethodInfo m : methods) { m.write(dout); } // u2 attributes_count; dout.writeShort(0); // (no ClassFile attributes for proxy classes) } catch (IOException e) { throw new InternalError("unexpected I/O Exception", e); } return bout.toByteArray(); }
这个方法最终生成了byte[]数组,也就是说,我们已经拥有了一份类的二进制文件了,可以说到这一步,几乎已经完成了所有工作,只需要把这一份二进制文件加载进JVM,就可以得到一个可运行的
Class<?>对象了。
最后这一步由
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)实现,这是一个native方法,由JVM实现,具体代码在
ClassLoader.c。
// The existence or signature of this method is not guaranteed since it // supports a private method. This method will be changed in 1.7. JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_defineClass0(JNIEnv *env, jobject loader, jstring name, jbyteArray data, jint offset, jint length, jobject pd) { return Java_java_lang_ClassLoader_defineClass1(env, loader, name, data, offset, length, pd, NULL); }
到这里已经获取了
Class<?>,让我们回到
Proxy.newProxyInstance()方法,生成代理类已经完成了。接下来是调用
Constructor<?> cons = cl.getConstructor(constructorParams);
获取入参为
InvocationHandler的构造函数,最后调用构造函数生产代理对象实例。
cons.newInstance(new Object[]{h});
到这,生成动态代理的逻辑已经全部完成。脑子里有没有一定收获,记不记得发生了什么?没有?那不怪你,我也是乱写的。
手动实现一个JDK动态代理
既然原理都已经清晰了,那我们能不能自己实现一个动态代理。为了简单起见,我这里不会直接去写二进制的byte[]数组,因为我不会字节码的结构。这个例子的实现是拼接字符串,然后调用编译器去编译成class文件,再调用
defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)去构造
Class<?>对象,然后照葫芦画瓢获取它的构造函数,生成一个代理对象。
还是复用这篇文章老生常谈系列之Aop--Spring Aop原理浅析中JDK动态代理里面的代码。
生成动态代理是这行代码
return Proxy.newProxyInstance(calculateService.getClass().getClassLoader(), calculateService.getClass().getInterfaces(), new MyInvocationHandler(new CalculateServiceImpl()));
实现MyProxy类
那么我们自己实现一个
MyProxy类。
首先那就搞个
MyProxy类,实现
newProxyInstance()方法。这里的实现很简单,分为以下几步:
- 根据传入接口,拼接源码,保存为
.java
文件。注意:这里我为了简单,只支持传入一个接口(我太懒了)。 - 调用
JavaCompiler
编译源码 loader.findClass("$Proxy0")
加载编译好的class文件- 获取构造器生成
Class
对象
package io.codegitz.proxy; /** * @author Codegitz * @date 2022/1/14 16:41 **/ public class MyProxy { /** * 生成代理对象 * @param loader * @param interfaces * @param h * @return * @throws IllegalArgumentException */ public static Object newProxyInstance(MyClassLoader loader, Class<?> interfaces, InvocationHandler h) throws IllegalArgumentException, IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { // 根据传入接口获取源代码 String sourceCode = getSourceCode(interfaces); String path = MyProxy.class.getResource("").getPath(); File file = new File(path+"$Proxy0.java"); FileWriter fw = new FileWriter(file); fw.write(sourceCode); fw.close(); // 获取java编译器 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); // 标注java文件管理器,用来获取java字节码文件 StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null); Iterable iterable = manager.getJavaFileObjects(file); // 创建task,通过java字节码文件将类信息加载到JVM中 JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable); // 开始执行task Boolean call = task.call(); // 关闭管理器 manager.close(); Class proxyClass = loader.findClass("$Proxy0"); // 返回被代理后的代理对象 Constructor c = proxyClass.getConstructor(InvocationHandler.class); return c.newInstance(h); } private static String getSourceCode(Class<?> interfaces){ StringBuilder src = new StringBuilder(); src.append("package io.codegitz.proxy;").append("\n") .append("import java.lang.reflect.Method;").append("\n") .append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append("\n") .append("private java.lang.reflect.InvocationHandler h;").append("\n") .append("public $Proxy0(java.lang.reflect.InvocationHandler h){").append("\n") .append("this.h=h;").append("\n") .append("}").append("\n"); for(Method method:interfaces.getMethods()){ src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append("\n") .append("try {").append("\n") .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append("\n") .append("this.h.invoke(this, m, new Object[]{});").append("\n") .append("}catch (Throwable e){").append("\n") .append("e.printStackTrace();").append("\n") .append("}").append("\n") .append("}").append("\n"); } src.append("}"); return src.toString(); } }
实现MyClassLoader类
定义
MyClassLoader对象,实现
findClass()方法,这里主要是为了定制获取自己编译好的class文件,否则直接使用原有的
ClassLoader是没问题的。
/** * @author Codegitz * @date 2022/1/14 17:24 **/ public class MyClassLoader extends ClassLoader { private String baseDir; public MyClassLoader() { this.baseDir = MyClassLoader.class.getResource("").getPath(); } /** * 通过类名称加载类字节码文件到JVM中 * * @param name 类名 * @return 类的Class独享 * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 获取类名 String className = MyClassLoader.class.getPackage().getName() + "." + name; if (null == baseDir) { throw new ClassNotFoundException(); } // 获取类文件 File file = new File(baseDir, name + ".class"); if (!file.exists()) { throw new ClassNotFoundException(); } // 将类文件转换为字节数组 try ( FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream(); ) { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } // 调用父类方法生成class实例 return defineClass(className, out.toByteArray(), 0, out.size()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } }
测试运行
最后测试方法改成使用自己的方法
public Object getMyProxy() throws IOException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IllegalAccessException { return MyProxy.newProxyInstance(new MyClassLoader(),calculateService.getClass().getInterfaces()[0],new MyInvocationHandler(new CalculateServiceImpl())); }
万事俱备只欠东风,搞个方法测试一把,代码如下:
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); CalculateService proxy = (CalculateService) new CalculateServiceProxy(new CalculateServiceImpl()).getMyProxy(); proxy.calculate(); }
如图,这里已经实现了自定义返回动态代理。
运行结果如下,跟我们使用原生的JDK动态代理实现了一样的效果。虽然实现得非常简陋,但是原理就是这么个原理。
总结
好了,到这里文章已经结束。我们首先介绍了JDK动态代理的实现逻辑,随后自己动手实现了一个简陋版的JDK动态代理。
这里再回顾一下思路:
- 根据传入接口拼接源代码,这一步跟我们平时在IDEA写代码是一样的
- 调用编译器编译代码,这一步跟我们点一下IDEA的build是一样的
- 加载编译好的class文件,这一步跟我们在IDEA点击运行是一样的
- 执行逻辑,输出结果
是不是还是很简单的,跟我们平时的操作是一样的,只不过是手动搞了一遍。又水一篇,简简单单。
如果有人看到这里,那在这里老话重提。与君共勉,路漫漫其修远兮,吾将上下而求索。
- 老生常谈系列之Aop--CGLIB动态代理的底层实现原理
- JDK动态代理原理和mybatis的Mapper底层实现
- Spring AOP的底层实现技术---JDK动态代理
- Java-JDK动态代理(AOP)使用及实现原理分析
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- Java JDK 动态代理(AOP)使用及实现原理分析
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- 分析动态代理Java JDK 动态代理(AOP)使用及实现原理分析
- Spring AOP源码分析(三):基于JDK动态代理和CGLIB创建代理对象的实现原理
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- SpringAOP的CGLIB动态代理的底层原理实现
- Spring AOP的底层实现技术---JDK动态代理
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- Java JDK 动态代理(AOP)使用及实现原理分析
- AOP的底层实现-CGLIB动态代理和JDK动态代理
- Java JDK动态代理(AOP)的实现原理与使用详析
- Java JDK 动态代理(AOP)使用及实现原理分析
- Spring面向切面编程(AOP)原理一之使用JDK实现动态代理
- Java JDK 动态代理(AOP)使用及实现原理分析