JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。

JDK动态代理的使用

编写一个类实现InvocationHandler接口,主要负责需要增强的方法的增强逻辑。

package com.morris.spring.demo.proxy.jdk;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class UserServiceInvocationHandler implements InvocationHandler {private Object target;public UserServiceInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("invoke before");Object result = method.invoke(target, args);System.out.println("invoke after");return result;}
}

代理类的生成:

package com.morris.spring.demo.proxy.jdk;import cn.morris.spring.service.UserService;
import cn.morris.spring.service.UserServiceImpl;import java.lang.reflect.Proxy;/*** JDK动态代理的使用* 目标对象必须实现一个接口才能使用JDK动态代理*/
public class JdkProxyDemo {public static void main(String[] args) {UserService userService = (UserService) Proxy.newProxyInstance(JdkProxyDemo.class.getClassLoader(), new Class<?>[]{UserService.class}, new UserServiceInvocationHandler(new UserServiceImpl()));userService.query("morris");}}

总结:

  • 要使用JDK动态代理,目标对象需要实现一个接口。
  • JDK动态代理为什么要使用实现接口呢,为什么不像cglib使用继承呢?因为jdk动态代理生成的动态代理类会继承Proxy类,而java是单继承,无法再继承目标类。
  • 自定义的InvocationHandler要接收一个目标接口的实现类,因为在代理类中要执行目标对象的方法,所以必须传入目标对象,否则无法执行。

代理类长什么样?

如何查看动态生成的代理类?

方法一:通过系统属性sun.misc.ProxyGenerator.saveGeneratedFiles=true,设置了这个属性后,生成的代理类会保存在项目下的com.sun.proxy下,当然如果你的类不是public的,那么包名是目标类的包名。

新版本的JDK中的属性可能叫jdk.proxy.ProxyGenerator.saveGeneratedFiles,具体是什么可以查看类ProxyGenerator的常量。

系统属性的设置方式:

  • 通过JVM启动参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
  • 通过环境变量
  • 通过代码:System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”,“true”);

方法二:使用JDK的API生成代理类

package com.morris.spring.demo.proxy.jdk;import com.morris.spring.service.UserService;
import sun.misc.ProxyGenerator;import java.io.FileOutputStream;
import java.io.IOException;/*** 使用JDK的API生成代理类*/
public class GenerateProxyClassDemo {public static void main(String[] args) throws IOException {byte[] bytes = ProxyGenerator.generateProxyClass(UserService.class.getSimpleName(), new Class[]{UserService.class});FileOutputStream fos = new FileOutputStream(UserService.class.getSimpleName() + ".class");fos.write(bytes);fos.flush();fos.close();}
}

方法三:使用第三方工具如arthas,这个工具可以导出内存中的所有类。

查看内存的代理类

使用arthas工具将内存中的com.sun.proxy包下的class文件dump下来,反编译后内容如下:

package com.sun.proxy;import com.morris.spring.service.UserService;
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 UserService {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 query(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.morris.spring.service.UserService").getMethod("query", 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动态代理生成的代理类的内容可以发现:

  • 代理类默认的包名为com.sun.proxy,后面看源码会发现这个会根据目标接口的访问修饰符来决定(如果是public,包名为com.sun.proxy,如果是protected,包名会跟目标接口包名相同,接口的访问修饰符不能为private)。
  • 代理类名为$Proxy开头,后面的数字每生成一个代理类会自增。
  • 代理类继承了Proxy类,实现了目标接口。
  • 代理类的目标方法会调用自己实现的InvocationHandler接口的invoke方法。

JDK动态代理源码解读

JDK动态代理的源码的入口为Proxy.newProxyInstance

Proxy的数据结构

private static final Class<?>[] constructorParams = { InvocationHandler.class }; // Proxy的构造方法的参数类型
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); // 代理类的缓存
protected InvocationHandler h; // 存放传入的自定义的InvocationHandler,供其子类$Proxy0调用

Proxy.newProxyInstance

java.lang.reflect.Proxy#newProxyInstance

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/// 重点在这,获得代理对象Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 获得构造参数是InvocationHandler的构造方法final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}// 调用构造方法$Proxy0(InvocationHandler h)生成代理对象实例 return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

源码中主要有两步:

  1. 创建代理类。
  2. 创建代理类的实例。

而getProxyClass0()的代码如下

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// 从静态属性proxyClassCache中获取,不存在则会调用ProxyClassFactory生成return proxyClassCache.get(loader, interfaces);
}

ProxyClassFactory

ProxyClassFactory是一个BiFunction,用于生成代理类

private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {// prefix for all proxy class namesprivate static final String proxyClassNamePrefix = "$Proxy"; // 生成的代理类的名称前缀// next number to use for generation of unique proxy class namesprivate static final AtomicLong nextUniqueNumber = new AtomicLong(); // 计数器@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {/** 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) {}// 判断传入的接口是不是传入的classloader加载的// 对象的比较在同一个classloader下才有意义if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}/** 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 inint 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.*/// 判断接口的访问修饰符,public的接口,生成的类的包名可以随意,但是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 packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** Generate the specified proxy class.*/// 生成代理类.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());}}
}

从源码可以看出:

  • jdk动态代理生成的代理类继承了Proxy类。
  • 底层是直接生成.class字节码数组,内存操作,速度快。
  • 底层使用了WeakCache做缓存。

【spring】JDK动态代理的使用与源码分析相关推荐

  1. JDK动态代理用例及源码解析

    动态代理分为JDK 动态代理和CGLIB 动态代理 参考链接:https://www.jianshu.com/p/269afd0a52e6 一. JDK 动态代理 实现JDK动态代理必须实现接口. 实 ...

  2. Spring JDK动态代理

    JDK 动态代理是通过 JDK 中的 java.lang.reflect.Proxy 类实现的.下面通过具体的案例演示 JDK 动态代理的使用. 1. 创建项目 在 MyEclipse 中创建一个名称 ...

  3. spring jdk动态代理、Cglib动态代理和LoadTimeWeaver(LTW)的应用选择

    在Java 语言中,从织入切面的方式上来看,存在三种织入方式:编译期织入.类加载期织入和运行期织入.编译期织入是指在Java编译期,采用特殊的编译器,将切面织入到Java类中:而类加载期织入则指通过特 ...

  4. 代理模式及JDK动态代理(InvocationHandler)的简单实现与分析

    在慕课网上学习了讲解代理模式的一个课程--<模式的秘密--代理模式>,感叹于David老师屌炸天的PPT,同时,老师一步一步模仿JDK源码去写code,教我们去简单实现JDK中的动态代理, ...

  5. Java的三种代理模式【附源码分析】

    Java的三种代理模式&完整源码分析 代理模式分为两种,静态代理和动态代理,动态代理包括JDK动态代理和Cglib动态代理. 静态代理 静态代理在使用时,需要定义接口或者父类,被代理对象与代理 ...

  6. 【spring】AOP引入的使用与源码分析

    通知是对目标对象方法的增强,而引入可以动态为目标对象实现新的接口,实现对类的增强. 引入的使用 目标类 public class DogService {public void hi() {Syste ...

  7. 高级JAVA - 动态代理的实现原理和源码分析

    在之前的一篇文章中 , 我们简单了解了一下代理模式(JAVA设计模式 - 代理模式) , 本篇我们来学习一下动态代理的实现原理 , 以及源码是怎样的 . JDK动态代理的主要实现步骤如下 : 1 . ...

  8. 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionServic ...

  9. 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)【享学Spring】

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 前言 数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容 ...

  10. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 > 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAcce ...

最新文章

  1. Redis 笔记系列(十一)——Redis的发布和订阅机制
  2. ASP.NETCore学习记录(一)
  3. Java 性能优化的 45 个细节
  4. Spring Cloud--Honghu Cloud分布式微服务云系统—System系统管理
  5. Windows Mobile下访问Sqlite的Native C++封装
  6. ipvs-dr模型及算法、keepalived基本应用、keepalive+ipvs实现高可用
  7. 数据绑定控件之Repeater
  8. Linux下qwt源码编译,QWT的编译与配置
  9. win10共享打印错误0x0000006_Win7打印机无法共享提示错误代码0x000006d9的解决方法...
  10. java的main方法中的String[]args
  11. 深入理解uwsgi和gunicorn网络模型
  12. Markdown编辑器对比分析
  13. 数据系统架构-3.数据仓库设计
  14. phpcms二次开发摘要
  15. android自动计步_Android计步模块实例代码(类似微信运动)
  16. 《老路用得上的商学课16—20》消费心理学(一)
  17. 20162320刘先润 2016-2017-2《程序设计与数据结构》课程总结
  18. Linux之CD驱动器读取命令
  19. python类takes no arguments_Python中的学习类出现的object() takes no parameters问题
  20. select函数到底该怎么用?

热门文章

  1. python 合并加速mp4文件(含对文件、目录的处理)
  2. python打印N*N乘法表
  3. 掌上飞车-艳云脚本云控系统
  4. 【华为机试真题 Python实现】勾股数元祖
  5. 中英文语音/录音转文字必备的6个软件
  6. ffmpeg视频添加meta信息
  7. openwrt修改默认网关地址_非常详细的锐捷网关路由配置教程,适合新手小白
  8. 用上周的作业:画一个太阳、地球、月亮的运动模型来剖析OpenGL中变换乃至整个绘制的秘密
  9. idea删除文件时出现选项 “Safe delete ( with usage search)“ 和 “Search in comments and strings“
  10. CentOS Install Passenger for ROR