在运行时更新代码(已Spring解密)

当从编译到部署再到测试的开发周期花费太长时间时,人们希望能够及时替换正在运行的代码,而无需重新启动应用程序服务器并等待部署完成。 在这种情况下,像JRebel这样的商业解决方案或像Grails这样的开源框架就可以提供帮助。

JVM不支持在运行时替换代码,例如您可以使用Class.forName()动态加载类。 基本上,您有以下选择:

  • HotSwap:Java 1.4引入的技术,可让您在调试器会话中重新定义类。 这种方法非常有限,因为它仅允许您更改方法的主体,而不能添加新的方法或类。
  • OSGi:这项技术允许您定义包。 在运行时,可用此软件包的较新版本替换该软件包。
  • 一次性类加载器:通过在模块的所有类上包装单独的类加载器,可以在模块的新版本可用时丢弃类加载器并进行替换。
  • 使用Java代理检测类:Java代理可以在定义类之前对它们进行检测。 这样,它可以将代码注入到已加载的类中,该类将此类与类文件的一个版本连接起来。 一旦有新版本可用,将执行新代码。

Grails所采用的技术称为“ 弹簧加载” ,它使用“ Java代理”方法来处理从文件系统而不是从jar文件加载的类。 但是,这在后台如何工作?

为了了解弹簧负载,我们建立了一个小样本项目,使我们可以更详细地研究该技术。 该项目仅包含两个类: Main类调用ToBeChanged类的print()方法,并休眠一会儿:

public static void main(String[] args) throws InterruptedException {while (true) {ToBeChanged toBeChanged = new ToBeChanged();toBeChanged.print();Thread.sleep(500);}
}

print()方法仅打印出一个版本,以便我们可以看到它已更改。 此外,我们还打印出堆栈跟踪,以查看其随时间的变化:

public void print() {System.out.println("V1");StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();for (StackTraceElement element : stackTrace) {System.out.println("\t" + element.getClassName() + "." + element.getMethodName() + ":" + element.getLineNumber());}
}

启动应用程序时,我们必须使用选项javaagent提供包含Java Agent的jar文件。 当弹簧加载以验证者不喜欢的方式修改字节码时,我们必须通过将选项noverify传递给JVM来禁用字节码的验证。 最后,我们使用cp传递包含类文件的文件夹,并告诉JVM包含main()方法的类:

java -javaagent:springloaded-1.2.4.BUILD-SNAPSHOT.jar -noverify -cp target/classes com.martinsdeveloperworld.springloaded.Main

ToBeChanged类的版本从V1更新到V2并使用mvn package重建项目后,我们看到以下输出:

...
V1java.lang.Thread.getStackTrace:-1com.martinsdeveloperworld.springloaded.ToBeChanged.print:7com.martinsdeveloperworld.springloaded.Main.main:8
V2java.lang.Thread.getStackTrace:-1com.martinsdeveloperworld.springloaded.ToBeChanged$$EPBF0gVl.print:7com.martinsdeveloperworld.springloaded.ToBeChanged$$DPBF0gVl.print:-1com.martinsdeveloperworld.springloaded.ToBeChanged.print:-1com.martinsdeveloperworld.springloaded.Main.main:8
...

版本V1的stacktrace看起来像我们期望的那样。 从Main.main()方法ToBeChanged.print()被调用。 对于版本V2这是不同的。 在这里,方法ToBeChanged.print现在调用方法ToBeChanged$$DPBF0gVl.print() 。 还请注意,调用ToBeChanged.print()的行号已从8更改为-1,表示该行未知。

新的行号-1强烈表明Java代理已经以一种允许它调用新方法而不是执行旧代码的方式来检测方法ToBeChanged.print() 。 为了证明这一假设,我在spring-loaded的代码中添加了一些日志记录语句,并添加了一个功能,可将每个插入的文件转储到本地硬盘驱动器中。 这样,我们可以检查ToBeChanged.print()后方法ToBeChanged.print()外观:

0 getstatic #16 <com/martinsdeveloperworld/springloaded/ToBeChanged.r$type>3 ldc #72 <0>5 invokevirtual #85 <org/springsource/loaded/ReloadableType.changed>8 dup9 ifeq 42 (+33)12 iconst_113 if_icmpeq 26 (+13)16 new #87 <java/lang/NoSuchMethodError>19 dup20 ldc #89 <com.martinsdeveloperworld.springloaded.ToBeChanged.print()V>22 invokespecial #92 <java/lang/NoSuchMethodError.<init>>25 athrow26 getstatic #16 <com/martinsdeveloperworld/springloaded/ToBeChanged.r$type>29 invokevirtual #56 <org/springsource/loaded/ReloadableType.fetchLatest>32 checkcast #58 <com/martinsdeveloperworld/springloaded/ToBeChanged__I>35 aload_036 invokeinterface #94 <com/martinsdeveloperworld/springloaded/ToBeChanged__I.print> count 241 return42 pop43 getstatic #100 <java/lang/System.out>46 ldc #102 <V1>48 invokevirtual #107 <java/io/PrintStream.println>51 invokestatic #113 <java/lang/Thread.currentThread>54 invokevirtual #117 <java/lang/Thread.getStackTrace>57 astore_1
...
152 return

getstatic操作码检索新字段r$type的值并将其压入堆栈(操作码ldc )。 然后,调用方法ReloadableType.changed()来调用之前已推入堆栈的对象引用。 顾名思义,方法ReloadableType.changed()检查此类型的新版本是否存在。 如果方法未更改,则返回0;如果方法已更改,则返回1。 如果返回值为零,即方法未更改,则以下操作码ifeq跳至第42行。 从第42行开始,我们看到了原来的实现,我在这里将其缩短了一点。

如果值为1,则if_icmpeq指令跳至第26行,在此再次读取静态字段r$type 。 此引用用于在其上调用方法ReloadableType.fetchLatest() 。 下面的checkcast指令验证返回的引用的类型为ToBeChanged__I 。 在这里,我们第一次偶然发现了这种弹簧接口为每种类型生成的人工接口。 它反映了原始类在进行检测时所具有的方法。 两行之后,此接口用于在ReloadableType.fetchLatest()返回的引用上调用方法print() ReloadableType.fetchLatest()

该引用不是对该类的新版本的引用,而是对所谓的调度程序的引用。 调度程序实现ToBeChanged__I接口,并通过以下指令实现方法print()

0 aload_1
1 invokestatic #21 <com/martinsdeveloperworld/springloaded/ToBeChanged$$EPBF0gVl.print>
4 return

动态生成的类ToBeChanged$$EPBF0gVl是所谓的执行程序,体现了该类型的新版本。 对于每个新版本,都会创建一个新的调度程序和执行程序,只有接口保持不变。 一旦有新版本可用,则在新调度程序上调用接口方法,并且在最简单的情况下,该接口方法将转发至执行程序中包含的代码的新版本。 不能直接在执行器上调用接口方法的原因是,弹簧加载还可以处理在新版本的类中添加方法的情况。 由于此方法在旧版本中不存在,因此将通用方法__execute()添加到接口和调度程序。 然后,此动态方法可以将调用调度到新方法,如从生成的调度程序中获取的以下指令集中所示:

0 aload_31 ldc #25 <newMethod()V>3 invokevirtual #31 <java/lang/String.equals>6 ifeq 18 (+12)9 aload_2
10 checkcast #33 <com/martinsdeveloperworld/springloaded/ToBeChanged>
13 invokestatic #36 <com/martinsdeveloperworld/springloaded/ToBeChanged$$EPBFaboY.newMethod>
16 aconst_null
17 areturn
18 aload_3
...
68 areturn

在这种情况下,我向类ToBeChanged添加了一个名为newMethod()的新方法。 __execute()方法的开头比较调用的描述符是否与新方法匹配。 在这种情况下,它将调用转发给新的执行者。 为了使此工作正常进行,必须将新方法的所有调用都重写为__execute()方法。 这也可以通过对原始类的检测来完成,并且也可以进行反射。

结论

Spring展示了在运行时可以用较新版本“替换”一个类的可能性。 为了实现这一点,利用了一系列Java技术,例如Java Agent和字节码检测。 通过仔细研究实现,可以大致了解有关JVM和Java的许多知识。

翻译自: https://www.javacodegeeks.com/2015/05/updating-code-at-runtime-spring-loaded-demystified.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

魅族android n内测报名,不再万年Android 5.0! Flyme安卓N内测招募开启

科客点评&#xff1a;恰逢Flyme五周年庆&#xff0c;这算的是给煤油们最大的礼物。近日&#xff0c;魅族Flyme系统非常活跃&#xff0c;为国内友商操碎了心&#xff0c;为此适配了一众友商热门机型&#xff0c;刷了不少存在感&#xff0c;但这显然不是魅族要搞的“大事情”。6月…

Android AudioTrack/AudioRecord -wav文件读取3

下面是一个网上一个大神写的,在公司测过了,还不错. 还可以写一个构造函数: initReader(InputStream is){ fis new FileInputStream(is); bis new BufferedInputStream(fis); }eg:call it as following : InputStream isActivity.getResource().openRawResource(); InitRe…

db2数据库连接数 linux_介绍一款数据库管理工具DBeaver

之前连接MySQL一直使用的是navicate&#xff0c;挺好用的&#xff0c;不过是个付费软件&#xff0c;一直想找一款免费开源的软件来替代。今天偶然间发现DBeaver&#xff0c;这是一款基于java开发的数据库工具&#xff0c;而且可以支持Windows、Linux、MacOS多个平台&#xff0c…

jqgrid mvc_jqGrid,REST,AJAX和Spring MVC集成

jqgrid mvc两年多以前&#xff0c;我写了一篇关于如何在Struts2中实现优雅的CRUD的文章。 实际上&#xff0c;我必须就该主题写两篇文章&#xff0c;因为该主题如此广泛。 今天&#xff0c;我采用了一套更为流行的&#xff0c;完善的框架和库&#xff0c;采用了更为轻量级的现代…

android view getwidth 0,Android中View.getWidth()和View.getMeasuredWidth()的区别

一。也許很多童鞋對getWidth()和getMeasuredWidth()的用法有很多的不解&#xff0c;這兩者之間有什麼樣的不同呢&#xff0c;網上也有各種不同的版本&#xff0c;但大多數都大同小異&#xff0c;從這個地方CtrlC,到另一個地方CtrlV,沒有把問題說透&#xff0c;也有一部分文章誤…

微信利用PHP创建自定义菜单的方法

在使用通用接口前&#xff0c;你需要做以下两步工作&#xff1a;1.拥有一个微信公众账号&#xff0c;并获取到appid和appsecret&#xff08;在公众平台申请内测资格&#xff0c;审核通过后可获得&#xff09;2.通过获取凭证接口获取到access_token注意&#xff1a;access_token…

ChronicleMap –具有堆外内存的Java体系结构

我的上一篇文章是在几周前写的&#xff0c;在收到一些有效的反馈后&#xff0c;我想澄清几点&#xff0c;作为本文的序言。 “ 使用零垃圾创建数百万个对象 ”的主要收获应该是&#xff0c;使用Chronicle&#xff0c;在编写Java程序时&#xff0c;您不会“局限于”使用jvm分配…

HTML滚动条S默认最小值,css修改滚动条默认样式

html代码:这是内容111这里是内容222这里是内容333css代码:.inner{width: 265px;height: 400px;position: absolute;top: 33px;left: 13px;/*cursor: pointer;*/overflow:hidden;}.innerbox{overflow-x: hidden;overflow-y: auto;color: #000;font-size: .7rem;font-family: &qu…

下列不属于html5语义元素,HTML5 新的语义元素

HTML5 提供了新的语义元素来明确一个Web页面的不同部分:HTML5中新的语义元素HTML5 元素标签定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分。根据W3C HTML5文档: section 包含了一组内容及其标题。WWFThe World Wide Fund for Nature (WWF) is....HTM…

【Android Developers Training】 81. 解析XML数据

注&#xff1a;本文翻译自Google官方的Android Developers Training文档&#xff0c;译者技术一般&#xff0c;由于喜爱安卓而产生了翻译的念头&#xff0c;纯属个人兴趣爱好。 原文链接&#xff1a;http://developer.android.com/training/basics/network-ops/xml.html 可扩展…

java核心面试_不正确的核心Java面试答案

java核心面试总览 在Internet上&#xff0c;Java面试问题和答案从一个网站复制到另一个网站。 这可能意味着错误或过时的答案可能永远不会得到纠正。 这是一些不太正确或已经过时的问题和答案。 即是Java 5.0之前的版本。 每个提供的问题后都有两个部分。 斜体的第一部分指示…

outlook自动保存html,当创建一个新的HTML电子邮件时保持默认的Outlook格式

我想创建一个简单的脚本来创建一个HTML消息&#xff0c;并且我想保留尽可能多的默认值。当创建一个新的HTML电子邮件时保持默认的Outlook格式在我的情况下&#xff0c;当我使用Home创建一个新邮件->新邮件时&#xff0c;它总是会创建一个默认字体[Calibri 11]&#xff0c;格…

干加个偏旁可以变成什么字_面试官:“干”字加一笔,变成什么字?回答王和午字不对...

随着大学生的增多&#xff0c;如今的求职者进入职场&#xff0c;想到一份心仪的工作&#xff0c;最让人头疼的就是面试&#xff0c;越来越多的企业都需要全能型的人才&#xff0c;从而在面试的时候不仅要考核专业知识&#xff0c;面试官还要费尽心思出各种各样的题来考验求职者…

Oracle研学-查询

学自B站黑马程序员 1.单表查询 //查询水表编号为 30408 的业主记录 select * from T_OWNERS where watermeter30408 //查询业主名称包含“刘”的业主记录 select * from t_owners where name like %刘% //查询业主名称包含“刘”的并且门牌号包含 5 的业主记录 select * from…

国际站html代码,国际站必须看得懂的HTML代码

国际站必须看得懂的HTML代码國産〇〇柒大家每天都忙着找关键词&#xff0c;忙着写标题&#xff0c;忙着做各种的优化。目的就是想把自己的产品排名到前面&#xff0c;获得更多的曝光&#xff0c;带来更多的询盘。在这个过程中我们是客服同事也是一名搜索优化人员&#xff0c;但…

phoengap–node+websocket在线聊天室

该实验项目基于&#xff1a; phonegapnodewebsocket可以应用于android 和 ios平台。 已经测试通过。以下是测试的图&#xff1a; 首先是用node 架设服务器。 基本上都node 基于websocket的。 主要是对message做处理和判断来进行输出和逻辑处理 而客户都&#xff0c;由于android…

中音萨克斯指法表图_萨克斯的几个特殊指法记忆和几个概念

大家在平常的练习和吹奏的时候&#xff0c;经常会出现找不到相应的指法的情况&#xff0c;有经验的萨友们通过长时间的摸索&#xff0c;会找到其中的一些规律。实际上&#xff0c;能看懂“一图在手&#xff0c;不用再担心找不到指法了”里面的表格&#xff0c;可以起到同样的作…

计算机网络结构示意图,计算机网络原理-计算机网络体系结构.pdf

绪论  计算机 网络概述一、计算机网络的发展过程截止 目前为止 &#xff0c;计算机网络 已发展到第 四代 &#xff0c; 即出现了第四代计算机网&#xff0c;它们是&#xff1a;第一代 &#xff1a;面 向终端 的计算机 网络第二代 &#xff1a;分组交换计算机 网络 (包括国际标…

git配置和使用

1、注册bitbucket用户登录bitbucket站点https://bitbucket.org/注册一个用户&#xff0c;注册后用户名为linjiqin&#xff0c;邮箱为linjiqindkhs.com。 2、Create repository(仓库)登录bitbucket&#xff0c;点击“Create”按钮会出现一个Create a new repository页面&#xf…

操作系统饥饿现象_操作系统心得体会

一、操作系统1.基本概念操作系统简称OS&#xff0c;是配置在计算机硬件上的第一层软件&#xff0c;它能够有效的组织和管理计算机系统中的硬件和软件资源&#xff0c;合理的组织计算机工作流程&#xff0c;控制程序的执行&#xff0c;并向用户提供各种服务功能。OS是现代计算机…