JDK动态代理详解(动态代理类源码解析)

2024-09-07 04:08

本文主要是介绍JDK动态代理详解(动态代理类源码解析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介
JDK动态代理就是在程序运行时,运用反射机制动态创建代理类,实现对目标类代码的增强。动态代理类的字节码在程序运行时由Java反射机制动态生成,不需要去手动开发。相比较静态代理,它更灵活。更容易扩展,代码
开发工作量更小,更容易维护。动态代理又分为JDK动态代理和CGLIB动态代理,一个通过反射生成代理类,一个通过asm开源包,修改字节码生成子类。区别在于JDK只能代理接口,所以有需要代理的类,必须实现了接口才行
,而CGLIB可以代理类,sping会自动切换两个动态代理。现在我们着重讲下JDK动态代理,它生成的匿名类是什么样的,为什么能实现代理?

JDK动态代理示例

/**
* 人员工作接口
*/
public interface WorkInterface {void goToWork();void goOffWork();
}
/**
* 工程师类
*/
public class Engineer implements WorkInterface {@Overridepublic void goToWork() {System.out.print("工程师某某开始上班了\n");}@Overridepublic void goOffWork() {System.out.print("工程师某某下班了\n");}
}
/**
* InvocationHandler
*/
public class WorkInvocationHandler implements InvocationHandler {private Object obj;public WorkInvocationHandler(Object obj) {this.obj = obj;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.print("进入了代理\n");Object result = method.invoke(obj, args);System.out.print("时间为:" + new Date() + "\n\n");return result;}
}
/**
* 动态代理Main方法
*
* @param args
*/
public static void main(String[] args) {//工程师类,Engineer是我们的代理对象WorkInterface engineer = new Engineer();//创建一个InvocationHandler,与代理对象关联InvocationHandler invocationHandler = new WorkInvocationHandler(engineer);Class<?> engineerClass = engineer.getClass();//新建一个代理类来代理engineer(这里就是动态生成代理类,通过实现相同接口,//达到在调用目标类之前先进入InvocationHandler,然后在通过method.invoke()方法调用原有的目标类中方法)/*** Proxy 代理类中 newProxyInstance方法参数说明* loader: 一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载* interfaces: 一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,*             那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。* h: 一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。*/WorkInterface engineerProxy = (WorkInterface) Proxy.newProxyInstance(engineerClass.getClassLoader(), engineerClass.getInterfaces(), invocationHandler);//调用工程师类中的方法engineerProxy.goToWork();engineerProxy.goOffWork();//将我们生成的代理类engineerProxy中相关信息打印出来String methodList = "";for (Method m : engineerProxy.getClass().getDeclaredMethods()) {methodList += m.getName() + "  ";}System.out.print("engineerProxy中的方法: " + methodList + "\n");System.out.print("engineerProxy的父类: " + engineerProxy.getClass().getSuperclass() + "\n");String interfaces = "";for (Class<?> i : engineerProxy.getClass().getInterfaces()) {interfaces += i.getName() + "  ";}System.out.print("engineerProxy中实现的接口: " + interfaces + "\n");
}

运行结果:
在这里插入图片描述

进入了代理
工程师某某开始上班了
时间为:Tue Jun 25 20:37:16 CST 2019进入了代理
工程师某某下班了
时间为:Tue Jun 25 20:37:16 CST 2019engineerProxy中的方法: equals  toString  hashCode  goToWork  goOffWork  
engineerProxy的父类: class java.lang.reflect.Proxy
engineerProxy中实现的接口: dynamicproxy.JDKProxy.WorkInterface  

JDK动态代理源码分析
JDK动态代理最重要的两个东西,一个Proxy,一个InvocationHandler,
通过

engineerProxy的父类: class java.lang.reflect.Proxy

我们就可以看出,生成的代理类是继承Proxy类的,InvocationHandler是与被代理类(目标类)相关联的,也是在InvocationHandler中通过实现invoke方法,调用目标类中的方法。Proxy与InvocationHandler又有啥关系那?
首先最最重要的还是生成,怎么生成的代理类的?可以看到是这段代码

//新建一个代理类来代理engineer(这里就是动态生成代理类,通过实现相同接口,
//达到在调用目标类之前先进入InvocationHandler,然后在通过method.invoke()方法调用原有的目标类中方法)
/**
* Proxy 代理类中 newProxyInstance方法参数说明
* loader: 一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
* interfaces: 一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,
*             那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
* h: 一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
*/
WorkInterface engineerProxy = (WorkInterface) Proxy.newProxyInstance(engineerClass.getClassLoader(), engineerClass.getInterfaces(), invocationHandler);

Proxy.newProxyInstance 是靠Proxy类的中的newProxyInstance方法,
这是newProxyInstance的源码

@CallerSensitive
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);}/** 查找或生成指定的代理类*/Class<?> cl = getProxyClass0(loader, intfs);/** 使用指定的调用处理程序调用其构造函数*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}//获取构造器final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;//判断代理类是否是Public的,不是public的则取消构造器验证权限限制if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {//设置在使用构造器的时候不执行权限检查cons.setAccessible(true);return null;}});}/** 创建代理对象,这里为什么用newInstance可以查下new和newInstance的区别,还有这里实例化的时候把h带进去了,* h是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);}
}

可以看到,这里是生成代理类的代码。

Class<?> cl = getProxyClass0(loader, intfs);

继续往下看 getProxyClass0的代码,如何生成代理类的。

/**
* 生成代理类
*/
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {//接口数量不能超过65535if (interfaces.length > 65535) {throw new IllegalArgumentException("interface limit exceeded");}//判断给定的代理类是否在缓存中存在,如果存在则直接返回副本,否则通过ProxyClassFactory创建return proxyClassCache.get(loader, interfaces);
}

proxyClassCache.get(loader, interfaces)源码就不继续往下看了,有兴趣的可以深入,我们看下当缓存中没有当前代理类的时候,是通过ProxyClassFactory来生成的,我们来看下ProxyClassFactory的源码

private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>>
{//代理类的前缀,所以为什么jdk动态代理生成的类都是$Proxy开头的private static final String proxyClassNamePrefix = "$Proxy";// 生成一个数字,保证唯一性,用于代理类的名称private 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) {/** 验证类加载器中是否有当前接口intf的对象*/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");}/** 验证是否是接口*/if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}/** 验证接口不是重复的*/if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // 定义代理类的包int accessFlags = Modifier.PUBLIC | Modifier.FINAL;    //访问标志/** 判断是否有非Public的接口,这样包名就使用非公共接口的包名,否则使用公共默认包名*/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");}}}//当不存在非公共接口时,使用默认包名 com.sun.proxyif (proxyPkg == null) {// if no non-public proxy interfaces, use com.sun.proxy packageproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** 选择要生成的代理类的名称.*/long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** 生成指定的代理类文件*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {//返回代理类class对象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动态代理代理类文件查看
在执行生成代理类代码之前,加上

//配置系统属性为true,代理类生成时将自动写入磁盘
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

在这里插入图片描述
这样代码运行后,就会在项目目录下,创建出代理类文件。
在这里插入图片描述
我们对$Proxy0.class 这个文件进行反编译,看看生成的文件源码是什么样的。

package com.sun.proxy;import dynamicproxy.JDKProxy.WorkInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/*
* 首先第一眼我们就看见了,我们生成的类是继承 Proxy的,所以为什么JDK动态代理不能代理类了,只能代理接口,不能多继承啊。
* 其次看到实现接口WorkInterface,所以在下面的代码里,我们看到了goToWork,goOffWork两个方法。
*/
public final class $Proxy0 extends Proxyimplements WorkInterface
{private static Method m1;private static Method m3;private static Method m2;private static Method m4;private static Method m0;/** 这里我们看到,$Proxy0的构造函数是有形参的,而且参数是InvocationHandler,这就解释了上面在实例化代理类时,为啥传入h这个参数了 return cons.newInstance(new Object[]{h});*/public $Proxy0(InvocationHandler paramInvocationHandler)throws{/** 这里是直接了父类Proxy的构造函数,所以在goToWork方法里我们看到,this.h.invoke(this, m3, null);这里就是调用了WorkInvocationHandler中的invoke的方法。* 然后invoke方法通过反射调用了我们的接口WorkInterface中的goToWork方法,最终调用了Engineer类中的实现方法goToWork。*/super(paramInvocationHandler);}public final boolean equals(Object paramObject)throws{try{return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();}catch (RuntimeException localRuntimeException){throw localRuntimeException;}catch (Throwable localThrowable){}throw new UndeclaredThrowableException(localThrowable);}public final void goToWork()throws{try{this.h.invoke(this, m3, null);return;}catch (RuntimeException localRuntimeException){throw localRuntimeException;}catch (Throwable localThrowable){}throw new UndeclaredThrowableException(localThrowable);}public final String toString()throws{try{return (String)this.h.invoke(this, m2, null);}catch (RuntimeException localRuntimeException){throw localRuntimeException;}catch (Throwable localThrowable){}throw new UndeclaredThrowableException(localThrowable);}public final void goOffWork()throws{try{this.h.invoke(this, m4, null);return;}catch (RuntimeException localRuntimeException){throw localRuntimeException;}catch (Throwable localThrowable){}throw new UndeclaredThrowableException(localThrowable);}public final int hashCode()throws{try{return ((Integer)this.h.invoke(this, m0, null)).intValue();}catch (RuntimeException localRuntimeException){throw localRuntimeException;}catch (Throwable localThrowable){}throw new UndeclaredThrowableException(localThrowable);}static{try{m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });m3 = Class.forName("dynamicproxy.JDKProxy.WorkInterface").getMethod("goToWork", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m4 = Class.forName("dynamicproxy.JDKProxy.WorkInterface").getMethod("goOffWork", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException localNoSuchMethodException){throw new NoSuchMethodError(localNoSuchMethodException.getMessage());}catch (ClassNotFoundException localClassNotFoundException){}throw new NoClassDefFoundError(localClassNotFoundException.getMessage());}
}

这篇关于JDK动态代理详解(动态代理类源码解析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1144016

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

高效+灵活,万博智云全球发布AWS无代理跨云容灾方案!

摘要 近日,万博智云推出了基于AWS的无代理跨云容灾解决方案,并与拉丁美洲,中东,亚洲的合作伙伴面向全球开展了联合发布。这一方案以AWS应用环境为基础,将HyperBDR平台的高效、灵活和成本效益优势与无代理功能相结合,为全球企业带来实现了更便捷、经济的数据保护。 一、全球联合发布 9月2日,万博智云CEO Michael Wong在线上平台发布AWS无代理跨云容灾解决方案的阐述视频,介绍了

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

常用的jdk下载地址

jdk下载地址 安装方式可以看之前的博客: mac安装jdk oracle 版本:https://www.oracle.com/java/technologies/downloads/ Eclipse Temurin版本:https://adoptium.net/zh-CN/temurin/releases/ 阿里版本: github:https://github.com/

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)