首页 文章 精选 留言 我的

精选列表

搜索[企业级市场],共10000篇文章
优秀的个人博客,低调大师

Jmix 2.0 发布 - 企业级 Web 快速开发框架

▲Jmix2.0 发布 Jmix 2.0是产品迭代中的一个非常重要的里程碑。这个版本引入了许多重要功能,并对Jmix的版图做了重大调整。在这篇文章中,我们主要了解有哪些更新以及这些更新对使用Jmix的应用程序有什么影响。 有关完整的详细信息和升级说明,请参阅文档中的最近更新页面。 技术栈更新 Jmix 2.0中影响最大的变化是技术栈的更新。现在运行时依赖基础框架的最新版本: Jmix core中Spring框架的升级:Spring Boot 3.1、Spring Framework 6.0、Spring Security 6.1 Flow UI子系统中的Vaadin 24.0 数据访问中的JPA实现采用了EclipseLink 4.0 BPM引擎使用了Flowable 7.0 基础框架的新版本提供了非常充足的支持期限,为Jmix框架和应用程序的稳步发展迎来了一个“恒纪元”。但另一方面,现在构建和运行应用程序至少需要JDK 17,如果使用WAR部署,则需要将Tomcat更新到10。 此外,由于经典UI中使用的Vaadin 8与新的Jakarta Servlet API和Spring 6不兼容,因此在Jmix 2.0中移除了经典UI。因此,要从Jmix 1.5经典UI升级到Jmix 2.0,需要替换UI层。有关Jmix版本迁移的更多信息,请参阅我们之前的博客文章:Jmix 延长免费支持Classic UI 新的Studio 2.0支持使用Flow UI的Jmix 1.5项目进行自动迁移。 支持 Flow UI 的扩展组件 我们已经为BPM、工作日历、Email和报表扩展组件实现了Flow UI版本,现在可以在Jmix 2.0中使用。 BPM扩展组件现在具有运行时全功能的BPMN建模器以及管理UI: ▲BPM 运行时建模器 工作日历扩展组件的新UI: ▲工作日历新UI Email 历史记录UI: ▲电子邮件历史记录UI 报表设计器在低分辨率的屏幕中更简洁易用: ▲低分辨率报表设计器 为了在大多数情况下提供简洁的UI,报表设计器中一个带区默认只有单一数据集。但是,如果勾选 多数据集 复选框,设计器将切换到更复杂的视图: ▲多数据集报表设计器 通用过滤器组件 genericFilter 组件的功能已经完善,支持在设计时和运行时创建配置和自定义条件。运行时的配置和自定义条件可以交由最终用户管理: ▲通用过滤器 代码编辑器组件 基于Ace编辑器的新 codeEditor 组件支持用户查看和编辑带有语法高亮显示的代码。代码编辑器还有一些高级功能,如不同的颜色主题: ▲代码编辑器 与许多其他Jmix UI组件一样,可以很容易地与数据模型进行绑定,以编辑存储在实体属性中的代码。 防止浏览器标签页关闭 Web应用中有一个常见的问题,当用户意外关闭浏览器标签页时,已经输入的数据会丢失。现在,当展示实体详细信息视图时,如果用户尝试关闭浏览器标签页,浏览器会显示有关离开页面的标准确认弹窗: ▲阻止弹窗关闭 可以使用 setPreventBrowserTabClosing() 方法在任何视图中启用或禁用此行为。 快速云部署 很多时候,开发者需要为用户、管理层或同事演示开发中的应用程序。如果可以直接在本机展示,或者有CI/CD工具将代码部署到演示或测试环境,那么这不是问题。但是,如果需要为异地相关人员展示开发中的应用,那么最好的选择是将其部署到云端。 Studio现在支持通过几个简单的步骤来完成云部署,这些步骤不需要任何有关部署或云的特定认知: 在AWS上注册一个帐户 在本机安装Docker 完成Studio中快速云部署向导的几个步骤。 在几分钟内,应用程序将在AWS EC2服务器上启动并运行,全球用户都可以访问。 背后的原理是,Studio首先为应用程序生成 docker-compose.yaml,通过AWS API创建EC2实例,在上面安装Docker,然后在服务器上构建应用程序镜像,并使用app和PostgreSQL数据库的容器运行 docker-compose。数据库和文件存储的数据会映射至服务器的文件系统,确保在修改代码重新部署时能保留所有数据。 该功能在Studio中隐藏了很长时间,因为我们认为还有一些需要完善的地方,在2.0中,终于和用户见面了。 Studio UI/UX改进 Jmix工具窗口现在显示一个包含项目中定义的所有Spring bean的节点: ▲Jmix工具窗口Bean节点 还可以使用下面这个选项对bean和其他元素按包名分组: ▲按包名分组 Beans部分仅显示带有 @Component 或 @Service 注解的bean,不显示在Java配置中使用 @Bean 注解创建的bean。 Inject对话框现在显示项目classpath中定义的所有bean,包括Java配置中使用 @Bean 注解创建的bean。Other Beans部分有智能分组: ▲注入智能分组 在Jmix论坛上有一个帖子,用户抱怨Studio生成的代码会收到IDEA的警告,类似“某些东西可以有 final 修饰符”。我们认为这是一个合理的要求,Studio应该遵循IDEA环境的代码检查。因此,所有生成的代码现在都包含推荐的 final 修饰符。如果你不需要,可以修改Jmix插件的设置禁用此选项。 对Studio生成代码的另一个改进是,使用 OffsetDateTime 类型生成实体审计相关的时间字段,包括创建、修改以及软删除的时间。这样一来,数据库将保存带有时区的时间戳。 Studio中使用BPM 如果你的项目中包含 BPM 扩展组件,则Jmix工具窗口现在能展示BPM节点。该节点包含BPMN流程(Processes)、流程草稿(Process Drafts)以及DMN表格: ▲Studio中的BPM设计器 流程可以在流程和流程草稿文件夹之间互相拷贝,通过建模器窗口顶部的按钮操作即可。这样可以组织一个简单的工作流程:在草稿文件夹中定义流程,流程定义完成后可以复制到流程文件夹。位于流程文件夹中的所有流程定义都将在下次应用程序启动时自动部署。如果部署出现问题,还可以将流程复制回草稿文件夹,然后从流程文件夹删除,以便应用程序能顺利启动。 通用REST中的身份验证 在1.4版本中,我们引入了Jmix授权服务作为预览功能。在2.0中,该功能升级成为通用REST中的主要身份验证方式。这一变化有两方面的原因:一方面,旧的Jmix OAuth2模块无法在Jmix 2.0中使用,因为依赖过时的Spring Security OAuth与Spring Boot 3不兼容;另一方面,我们需要遵循最佳实践,弃用过时的OAuth密码授权流程。 如果在Jmix 2.0中使用通用REST扩展组件,则必须选择授权类型,这决定了REST客户端将如何获得访问token。Jmix授权服务支持两种类型:客户端凭证和授权码。与之前的密码授权相比,这两种方式的优势在于,客户端应用程序不可以收集或保存用户密码,这在很多合规性要求中是必要条件。 与旧的密码授权最相似且最容易实现的是客户端凭证授权。这种方式应当用于服务器与服务器之间的通信,例如与第三方系统集成。此时,Jmix授权服务需要配置客户端的角色,例如 rest-minimal、order-management 等。要获取访问token,客户端只需在basic authentication header中传递其 client-id 和 client-secret。使用此token的后续API调用将以用户名为 client-id 的特殊用户进行,其角色为该客户端所配置的角色列表。换句话说,客户端使用提前配置好的静态权限集,而非真正的用户。 通过授权码进行授权稍微复杂一些,但支持客户端代表真正的用户进行操作。对于这种授权类型,当客户端请求身份验证时,Jmix授权服务会显示一个登录页面,供用户输入用户名和密码。认证成功后向客户端返回一个中间授权码。客户端使用中间授权码获取访问token,之后可以使用token进行API调用。此时,客户端作为输入用户名的用户运行,但是不知道用户的密码。 结论 随着新技术栈和生产就绪的Flow UI的引入,Jmix平台完成了现代化改造,后续将稳步发展。我们的团队将继续努力实现新的功能,并不断提高产品的整体质量。 在定于2023年10月发布的下一个功能版本中,我们计划了几个重要的更新。包括为更多的扩展组件实现Flow UI,在Flow UI中添加SuggestionField和SettingsFacet,以及提升对data repository的支持。我们未来版本的详细路线图发布在GitHub。 欢迎在我们的论坛上提供反馈,并感谢所有贡献想法、建议和提出bug的你们!

优秀的个人博客,低调大师

PrimiHub 1.6.8 发布,企业级开源隐私计算平台

PrimiHub 是一款由密码学专家团队打造的开源隐私计算平台,支持安全多方计算、联邦学习、隐私求交、隐私查询等。具有如下特性: 开源:完全开源、免费 安装简单:支持 Docker 一键部署 开箱即用:拥有Web界面、命令行和Python SDK多种使用方式 功能丰富:支持隐匿查询、隐私求交、联合统计、数据资源管理等功能 灵活配置:支持自定义扩展语法、语义、安全协议等 自主研发:基于安全多方计算、联邦学习、同态加密、可信计算等隐私计算技术 本次更新了什么? 新增特性: 联邦学习支持mysql数据库 纵向LR支持多分类 PIR支持数据库查询 优化: 修复页面发起联邦任务,未选择可信第三方运行报错,再次选择不运行问题 更新页面纵向LR及横向LR自定义参数配置 页面发起推理服务任务,增加任务类型,横向任务一方推理 修复PIR结果中文显示乱码 项目地址 GitHub:https://github.com/primihub/primihub Gitee:https://gitee.com/primihub/primihub 官网:https://primihub.com

优秀的个人博客,低调大师

小米开源企业级中后台设计系统 HiUI

HiUI 是一套适用于中后台系统的设计规范及前端组件化解决方案,可以帮助开发人员快速实现交互一致、界面美观的界面开发,由小米集团信息技术部开源。 特性 精于中后台产品的交互与视觉设计 全面的流程、数据展示模板,从业务中来,模板、组件覆盖大多数中后台系统需求 数据结构分离的单组件设计,无需学习,升级方便,开箱即用 丰富的组件库:根据业务场景提炼出5大类,50+基础组件,覆盖大部分使用场景,满足各种功能需求 更好的开发体验:提供功能强大、品类丰富的高质量组件,让开发更简单 ;更灵活的组件引入方式,支持分包与合包,让升级更平滑 ;提供全链路开发体系,开箱即用,让开发更高效 支持 TypeScript,提供优秀的类型提示和代码检查 拥抱 React hooks,提供全新的函数式编程体验 示例

优秀的个人博客,低调大师

Jmix 1.5.2 发布 - 企业级 Web 快速开发框架

Jmix Web 快速开发框架 1.5.2 发布,该补丁版本中主要包含了 Bug 修复,推荐升级: 🛠️ 主要 Bug 修复: - 保存三层实体关系时的错误。 - 标签选择器(TagPicker)打开界面没有隐藏已选标签的问题。 - 表单中的复选框显示重复图标的问题。 - 标准的RemoveAction无法删除自定义数据存储中的实体和DTO的问题。 - 如果删除了任何作业类,则Quartz作业界面无法显示作业的问题。 - DataGrid在收到null返回值时没有重置图标的问题。 - DataManager.getReference() 方法会导致底层序列自增的问题。 以及一些Flow UI中的问题。 详细修复的问题列表,请参考 Jmix GitHub: https://github.com/jmix-framework/jmix/issues?q=is%3Aclosed+milestone%3A1.5.2 🔑 Jmix 是一个覆盖应用程序全生命周期的 Java Web快速开发平台。以 Spring Boot 作为开源基础框架,提供过程中的 Studio 开发工具以及开箱即用的扩展组件。通过 Jmix 实现您的数字化愿景,无低代码平台限制,无供应商依赖,无需按用户付费。 资源:👉🏻 Jmix 适合我吗? 👉🏻 中文官网

优秀的个人博客,低调大师

Jmix 1.5.1 发布 - 企业级Web快速开发框架

Jmix Web 快速开发框架 1.5.1 发布,该补丁版本中主要包含了 Bug 修复,推荐升级: 💥 主要新功能: - 支持 Spring Boot 2.7.10 - 组件工具箱支持 PivotTable(透视表) - 在 Studio 中添加表格类型组件默认 100% 宽度 - Quartz 任务管理界面按钮支持本地化 🛠️ 主要 Bug 修复: - PresentationProvider 在某些情况下应用两次的问题 - 组件 enable 属性不支持的问题 - 通知组件为替代用户展示错误通知的问题 - 扩展组件中运行测试由于 Liquibase 脚本导致失败的问题 - URL 参数值中带有 “&” 符号导致解析失败的问题 - 一些 Flow UI 相关的修复 详细修复的问题列表,请参考 Jmix GitHub: https://github.com/jmix-framework/jmix/issues?q=is%3Aclosed+milestone%3A1.5.1 🔑 Jmix 是一个覆盖应用程序全生命周期的 Java 少代码快速开发平台。以 Spring Boot 作为开源基础框架,提供过程中的 Studio 开发工具以及开箱即用的扩展组件。通过 Jmix 实现您的数字化愿景,无低代码平台限制,无供应商依赖,无需按用户付费。 资源:👉🏻 Jmix 适合我吗? 👉🏻 中文官网

优秀的个人博客,低调大师

企业级java增量热部署解决方案

前言 在前说明:好久没有更新博客了,这一年在公司做了好多事情,包括代码分析和热部署替换等黑科技,一直没有时间来进行落地写出一些一文章来,甚是可惜,趁着中午睡觉的时间补一篇介绍性的文章吧。 首先热部署的场景是这样的,公司的项目非常多,真个BU事业部的项目加起来大约上几百个项目了,有一些项目本地无法正常启动,所以一些同学在修改完代码,或者是在普通的常规任务开发过程中都是盲改,然后去公司的代码平台进行发布,恶心的事情就在这里,有的一些项目从构建到发布运行大约30分钟,所以每次修改代码到代码见效需要30分钟的周期,这个极大的降低了公司的开发效率,一旦惰性成习惯,改变起来将十分的困难,所以我们极需要一个在本地修改完代码之后,可以秒级在服务端生效的神器,这样,我们的热部署插件就诞生了。 热部署在业界本身就是一个难啃的骨头,属于逆向编程的范畴,JVM有类加载,那么热部署就要去做卸载后重新加载,Spring有上下文注册,spring Bean执行初始化生命周期,热部署就要去做类的销毁,重新初始化,里面设计到的细节点非常之多,业界的几款热部署的处理方式也不尽相同,由于需要巨大的底层细节需要处理,所以目前上想找到一个完全覆盖所有功能的热部署插件是几乎不可能的,一般大家听到的热部署插件主要是国外的一些项目比如商业版本的jrebel,开源版的springloaded,以及比较粗暴的spring dev tools。当前这些项目都是现成的复杂开源项目或者是闭包的商业项目,想去自行修改匹配自己公司的项目,难度是非常之大。闲话少说,进入正文 前言一:什么是热部署 所谓热部署,就是在应用正在运行的时候升级软件,却不需要重新启动应用。对于Java应用程序来说,热部署就是在运行时更新Java类文件,同时触发spring的一些列重新加载过程。在这个过程中不需要重新启动,并且修改的代码实时生效 前言二:为什么我们需要热部署 程序员每天本地重启服务5-12次,单次大概3-8分钟,每天向Cargo部署3-5次,单次时长20-45分钟,部署频繁频次高、耗时长。插件提供的本地和远程热部署功能可让将代码变更秒级生效,RD日常工作主要分为开发自测和联调两个场景,下面分别介绍热部署在每个场景中发挥的作用: 前言三:热部署难在哪,为什么业界没有好用的开源工具 热部署不等同于热重启,像tomcat或者spring boot tool dev这种热重启相当于直接加载项目,性能较差,增量文件热部署难度很大,需要兼容各种中间件和用户写法,技术门槛高,需要对JPDA(Java Platform Debugger Architecture)、java agent、字节码增强、classloader、spring框架、Mybatis框架等集成解决方案等各种技术原理深入了解才能全面支持各种框架,另外需要IDEA插件开发能力,形成整体的产品解决方案。现在有了热部署,代码就是任人打扮的小姑娘! 前言四:为什么我们不用spring boot devtools 有一些朋友问我,为什么不直接使用spring boot devtools,有两方面原因吧,第一它仅仅只使用在spring boot项目中,对于普通的java项目以及spring xml项目是不支持的,最主要的第二点它的热加载方案实际上和tomcat热加载是一样的,只不过它的热加载通过嵌套classloader的方式来完成,这个classloader每次只加载class file变更的class二进制文件,这样就会来带一个问题,在非常庞大的项目面前(启动大约10min+)这种情况,它就显得很苍白。这归根结底的原因是在于他的reload范围实在是太大了,对于一些小项目还可以,但是一些比较庞大的项目实际使用效果还是非常感人的。 1、整体设计方案 2、走进agent instrument 规范:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html?is-external=true Class VirtualMachine:https://docs.oracle.com/javase/8/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html#loadAgent-java.lang.String- Interface ClassFileTransformer:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/ClassFileTransformer.html 2.1、JVM启动前静态Instrument Javaagent是java命令的一个参数。参数 javaagent 可以用于指定一个 jar 包,并且对该 java 包有2个要求: 这个 jar 包的 MANIFEST.MF 文件必须指定 Premain-Class 项。 Premain-Class 指定的那个类必须实现 premain() 方法。 premain 方法,从字面上理解,就是运行在 main 函数之前的的类。当Java 虚拟机启动时,在执行 main 函数之前,JVM 会先运行-javaagent所指定 jar 包内 Premain-Class 这个类的 premain 方法 。 在命令行输入java可以看到相应的参数,其中有 和 java agent相关的: -agentlib:<libname>[=<选项>] 加载本机代理库 <libname>, 例如 -agentlib:hprof 另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<选项>] 按完整路径名加载本机代理库 -javaagent:<jarpath>[=<选项>] 加载 Java 编程语言代理, 请参阅 java.lang.instrument 该包提供了一些工具帮助开发人员在 Java 程序运行时,动态修改系统中的 Class 类型。其中,使用该软件包的一个关键组件就是 Javaagent。从名字上看,似乎是个 Java 代理之类的,而实际上,他的功能更像是一个Class 类型的转换器,他可以在运行时接受重新外部请求,对Class类型进行修改。 agent加载时序图 从本质上讲,Java Agent 是一个遵循一组严格约定的常规 Java 类。 上面说到 javaagent命令要求指定的类中必须要有premain()方法,并且对premain方法的签名也有要求,签名必须满足以下两种格式: public static void premain(String agentArgs, Instrumentation inst) public static void premain(String agentArgs) JVM 会优先加载 带 Instrumentation 签名的方法,加载成功忽略第二种,如果第一种没有,则加载第二种方法。这个逻辑在sun.instrument.InstrumentationImpl 2.2、Instrumentation类常用API public interface Instrumentation { //增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。 void addTransformer(ClassFileTransformer transformer, boolean canRetransform); //在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义, 如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。 对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。 void addTransformer(ClassFileTransformer transformer); //删除一个类转换器 boolean removeTransformer(ClassFileTransformer transformer); //是否允许对class retransform boolean isRetransformClassesSupported(); //在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException; //是否允许对class重新定义 boolean isRedefineClassesSupported(); //此方法用于替换类的定义,而不引用现有的类文件字节,就像从源代码重新编译以进行修复和继续调试时所做的那样。 //在要转换现有类文件字节的地方(例如在字节码插装中),应该使用retransformClasses。 //该方法可以修改方法体、常量池和属性值,但不能新增、删除、重命名属性或方法,也不能修改方法的签名 void redefineClasses(ClassDefinition... definitions) throws ClassNotFoundException, UnmodifiableClassException; //获取已经被JVM加载的class,有className可能重复(可能存在多个classloader) @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); } 2.3、instrument原理: instrument的底层实现依赖于JVMTI(JVM Tool Interface),它是JVM暴露出来的一些供用户扩展的接口集合,JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。JVMTIAgent是一个利用JVMTI暴露出来的接口提供了代理启动时加载(agent on load)、代理通过attach形式加载(agent on attach)和代理卸载(agent on unload)功能的动态库。而instrument agent可以理解为一类JVMTIAgent动态库,别名是JPLISAgent(Java Programming Language Instrumentation Services Agent),也就是专门为java语言编写的插桩服务提供支持的代理。 2.3.1、启动时加载instrument agent过程: 创建并初始化 JPLISAgent; 监听VMInit事件,在 JVM 初始化完成之后做下面的事情: 创建 InstrumentationImpl 对象 ; 监听 ClassFileLoadHook 事件 ; 调用 InstrumentationImpl 的loadClassAndCallPremain方法,在这个方法里会去调用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 类的 premain 方法 ; 解析 javaagent 中 MANIFEST.MF 文件的参数,并根据这些参数来设置 JPLISAgent 里的一些内容。 2.3.2、运行时加载instrument agent过程: 通过 JVM 的attach机制来请求目标 JVM 加载对应的agent,过程大致如下: 创建并初始化JPLISAgent; 解析 javaagent 里 MANIFEST.MF 里的参数; 创建 InstrumentationImpl 对象; 监听 ClassFileLoadHook 事件; 调用 InstrumentationImpl 的loadClassAndCallAgentmain方法,在这个方法里会去调用javaagent里 MANIFEST.MF 里指定的Agent-Class类的agentmain方法。 2.3.3、Instrumentation的局限性 大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,或者笼统说就是类重定义(Class Redefine)的功能,但是有以下的局限性: premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。 类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses()方法,此方法有以下限制: 新类和老类的父类必须相同; 新类和老类实现的接口数也要相同,并且是相同的接口; 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致; 新类和老类新增或删除的方法必须是private static/final修饰的; 可以修改方法体。 除了上面的方式,如果想要重新定义一个类,可以考虑基于类加载器隔离的方式:创建一个新的自定义类加载器去通过新的字节码去定义一个全新的类,不过也存在只能通过反射调用该全新类的局限性。 2.4、那些年JVM和Hotswap之间的相爱相杀 围绕着method body的hotSwap JVM一直在进行改进 1.4开始JPDA引入了hotSwap机制(JPDA Enhancements),实现了debug时的method body的动态性 参照:https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/enhancements1.4.html 1.5开始通过JVMTI实现的java.lang.instrument (Java Platform SE 8 ) 的premain方式,实现了agent方式的动态性(JVM启动时指定agent) 参照:https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html 1.6又增加了agentmain方式,实现了运行时动态性(通过The Attach API 绑定到具体VM)。 参照:https://blogs.oracle.com/corejavatechtips/the-attach-api 其基本实现是通过JVMTI的retransformClass/redefineClass进行method body级的字节码更新,ASM、CGLib之类基本都是围绕这些在做动态性。 但是针对Class的hotSwap一直没有动作(比如Class添加method,添加field,修改继承关系等等),为什么?因为复杂度高并且没有太高的回报。 2.5、如何解决Instrumentation的局限性 由于JVM限制,JDK7和JDK8都不允许都改类结构,比如新增字段,新增方法和修改类的父类等,这对于spring项目来说是致命的,假设小龚同学想修改一个spring bean,新增了一个@Autowired字段,这种场景在实际应用时很多,所以我们对这种场景的支持必不可少。 那么我们是如何做到的呢,下面有请大名鼎鼎的dcevm,dcevm(DynamicCode Evolution Virtual Machine)是java hostspot的补丁(严格上来说是修改),允许(并非无限制)在运行环境下修改加载的类文件.当前虚拟机只允许修改方法体(method bodies),decvm,可以增加 删除类属性、方法,甚至改变一个类的父类、dcevm 是一个开源项目,遵从GPL 2.0、更多关于dcevm的介绍: https://www.cnblogs.com/redcreen/archive/2011/06/03/2071169.html https://www.slideshare.net/wangscu/hotspot-hotswap-who-and-who-are-best-freinds https://www.cnblogs.com/redcreen/archive/2011/06/14/2080718.html https://dl.acm.org/doi/10.1145/2076021.2048129 http://ssw.jku.at/Research/Papers/Wuerthinger11PhD/ http://ssw.jku.at/Research/Papers/Wuerthinger10a/ https://dl.acm.org/doi/10.1145/1868294.1868312 https://dl.acm.org/doi/10.1145/1890683.1890688 3、热部署技术解析 3.1、文件监听 热部署启动时首先会在本地和远程预定义两个目录,/var/tmp/xxx/extraClasspath和/var/tmp/xxx/classes,extraClasspath为我们自定义的拓展classpath url,classes为我们监听的目录,当有文件变更时,通过idea插件来部署到远程/本地,触发agent的监听目录,来继续下面的热加载逻辑,为什么我们不直接替换用户的classPath下面的资源文件呢,因为业务方考虑到war包的api项目,和spring boot项目,都是以jar包来启动的,这样我们是无法直接修改用户的class文件的,即使是用户项目我们可以修改,直接操作用户的class,也会带来一系列的安全问题,所以我们采用了拓展classPath url来实现文件的修改和新增,并且有这么一个场景,多个业务侧的项目引入了相同的jar包,在jar里面配置了mybatis的xml和注解,这种情况我们没有办法直接来修改jar包中源文件,通过拓展路径的方式可以不需要关注jar包来修改jar包中某一文件和xml,是不是很炫酷,同理这种方法可以进行整个jar包的热替换(方案设计中)。下面简单介绍一下核心监听器, 3.2、jvm class reload JVM的字节码批量重载逻辑,通过新的字节码二进制流和旧的class对象生成ClassDefinition定义,instrumentation.redefineClasses(definitions),来触发JVM重载,重载过后将触发初始化时spring插件注册的transfrom,下一章我们简单讲解一下spring是怎么重载的。 新增class我们如何保证可以加载到classloader上下文中?由于项目在远程执行,所以运行环境复杂,有可能是jar包方式启动(spring boot),也有可能是普通项目,也有可能是war web项目,针对这种情况我们做了一层classloader url拓展 User classLoader是框架自定义的classLoader统称,例如Jetty项目是WebAppclassLoader,其中Urlclasspath为当前项目的lib文件件下,例如spring boot项目也是从当前项目中BOOT-INF/lib/,等等,不同框架的自定义位置稍有不同。所以针对这种情况 我们必须拿到用户的自定义classloader,如果常规方式启动的,比如普通spring xml项目借助plus发布,这种没有自定义classloader,是默认AppClassLoader,所以我们在用户项目启动过程中借助agent字节码增强的方式来获取到真正的用户classloader。 我们做的事情:找到用户使用的子classloader之后通过反射的方式来获取classloader中的元素Classpath,其中classPath中的URL就是当前项目加载class时需要的所有运行时class环境,并且包括三方的jar包依赖等。 我们获取到URL数组,把我们自定义的拓展classpath目录加入到URL数组的首位,这样当有新增class时,我们只需要将class文件放到拓展classpath对应的包目录下面即可,当有其他bean依赖新增的class时,会从当前目录下面查找类文件。 为什么不直接对Appclassloader进行加强?而是对框架的自定义classloader进行加强 考虑这样一个场景,框架自定义类加载器中有ClassA,然后这个时候用户新增了一个Class B需要热加载,B class里面有A的引用关系,如果我们增强AppClassLoader时,初始化B实例时ClassLoader.loadclass首先从UserClassLoader开始找classB,依靠双亲委派原则,B是被Appclassloader加载的,因为B依赖了类A,所以当前AppClassLoader加载B一定是找不到的,这个时候汇报ClassNotFoundException。也就是说我们对类加载器拓展一定要拓展最上层的类加载器,这样才会达到我们想要的效果。 3.3、spring bean重载 spring bean reload过程中,bean的销毁和重启流程,其中细节点涉及的比较多。主要内容如下图展示: 首先当修改java class D时,通过spring classpathScan扫描校验当前修改的bean是否是spring bean(注解校验)然后触发销毁流程(BeanDefinitionRegistry.removeBeanDefinition)此方法会将当前spring 上下文中的 bean D 和依赖 spring bean D的 Bean C 一并销毁,但是作用范围仅仅在当前spring 上下文,若C被子上下文中的Bean B 依赖,是无法更新子上下文中的依赖关系的,此时,当有流量打进来,Bean B中关联的Bean C还是热部署之前的对象,所以热部署失败,所以我们在spring初始化过程中,需要维护一个父子上下文的对应关系,当子上下文变时若变更范围涉及到Bean B时,需要重新更新子上下文中的依赖关系,所以当有多上下文关联时需要维护多上下文环境,并且当前上下文环境入口需要reload。入口指:spring mvc controller,Mthrift和pigeon,对不同的流量入口,我们采用不同的reload策略。RPC框架入口主要操作为解绑注册中心,重新注册,重新加载启动流程等,对Spring mvc controller主要是解绑和注册url Mappping来实现流量入口类的变化切换 3.4、spring xml重载 当用户修改/新增spring xml时,需要对xml中所有bean进行重载 重新reload之后,将spring 销毁后重启。 注意:xml修改方式改动较大,可能涉及到全局的Aop的配置以及前置和后置处理器相关的内容,影响范围为全局,所以目前只放开普通的xml bean标签的新增/修改,其他能力酌情逐步放开。 3.5、mybatis xml 重载 4、远程反编译 在代码中通过插件右键-远程反编译即可查看当前classpath下面最新编译的最新class文件,这是如何办到的的呢,核心代码如下: agentString+= "try {\n" + "\t\t\tjava.lang.ClassLoader classLoader = org.springframework.beans.factory.support.DefaultListableBeanFactory.class.getClassLoader ();\n" + "\t\t\tjava.lang.Class clazz = classLoader.loadClass ( \"org.hotswap.agent.config.PluginManager\" );\n" + "\t\t\tjava.lang.reflect.Method method = clazz.getDeclaredMethod ( \"enhanceUserClassLoader\",new java.lang.Class[0]);\n" + "\t\t\tmethod.setAccessible ( true );\n" + "\t\t\tmethod.invoke ( null, new Object[0]);\n" + "\t\t} catch (java.lang.Exception e){\n" + "\t\t\te.printStackTrace ( );\n" + "\t\t}"; 上面代码是在用户侧启动DefaultListableBeanFactory时,初始化所有bean之后完成的,在方法preInstantiateSingletons之后会对当前用户侧classloader进行反向持有+ 路径增强。 public static void enhanceUserClassLoader(){ if(springbootClassLoader != null){ LOGGER.info ( "对用户classloader进行增强,springbootClassLoader:" + springbootClassLoader ); URLClassLoaderHelper.prependClassPath ( springbootClassLoader ); LOGGER.info ( "对用户classloader进行增强成功,springbootClassLoader:" + springbootClassLoader ); } } 通过使用代码启动时反射增强classloader,下面来看看核心方法prependClassPath public static void prependClassPath(ClassLoader classLoader){ LOGGER.info ( "用户classloader增强,classLoader:" + classLoader ); if(!(classLoader instanceof URLClassLoader)){ return; } URL[] extraClasspath = PropertiesUtil.getExtraClasspath (); prependClassPath( (URLClassLoader) classLoader,extraClasspath); } 其中URL[] extraClasspath = PropertiesUtil.getExtraClasspath ();这里获取的是用户自定义的classpath,每次新增修改class之后都会放进去最新的资源文件。 public static void prependClassPath(URLClassLoader classLoader, URL[] extraClassPath) { synchronized (classLoader) { try { Field ucpField = URLClassLoader.class.getDeclaredField("ucp"); ucpField.setAccessible(true); URL[] origClassPath = getOrigClassPath(classLoader, ucpField); URL[] modifiedClassPath = new URL[origClassPath.length + extraClassPath.length]; System.arraycopy(extraClassPath, 0, modifiedClassPath, 0, extraClassPath.length); System.arraycopy(origClassPath, 0, modifiedClassPath, extraClassPath.length, origClassPath.length); Object urlClassPath = createClassPathInstance(modifiedClassPath); ExtraURLClassPathMethodHandler methodHandler = new ExtraURLClassPathMethodHandler(modifiedClassPath); ((Proxy)urlClassPath).setHandler(methodHandler); ucpField.set(classLoader, urlClassPath); LOGGER.debug("Added extraClassPath URLs {} to classLoader {}", Arrays.toString(extraClassPath), classLoader); } catch (Exception e) { LOGGER.error("Unable to add extraClassPath URLs {} to classLoader {}", e, Arrays.toString(extraClassPath), classLoader); } } } 只需关注 URL[] origClassPath = getOrigClassPath(classLoader, ucpField); URL[] modifiedClassPath = new URL[origClassPath.length + extraClassPath.length]; System.arraycopy(extraClassPath, 0, modifiedClassPath, 0, extraClassPath.length); System.arraycopy(origClassPath, 0, modifiedClassPath, extraClassPath.length, origClassPath.length);这几行代码 首先获取到用户侧classloader中URLClassPath的URLS,然后在通过反射的方式将用户配置的extclasspath的路径设置到URLS数组中的首位,这样每次调用URLClassLoader的findResource方法都会获取到最新的资源文件了。 5、我们支持的功能 功能点 是否支持 修改方法体内容 ✅ 新增方法体 ✅ 新增非静态字段 ✅ 新增静态字段 ✅ spring bean中新增@autowired注解 ✅ 在spring 扫描包base package下,新增带@Service的bean,并且注入 ✅ 新增xml ✅ 增加修改静态块 ✅ 新增修改匿名内部类 ✅ 新增修改继承类 ✅ 新增修改接口方法 ✅ 新增泛型方法 ✅ 修改 annotation sql(Mybatis) ✅ 修改 xml sql(Mybatis) ✅ 增加修改静态块 ✅ 匿名内部类新增,修改 ✅ 内部类新增,修改 ✅ 新增,删除extend父类,implement 接口 ✅ 父类或接口新增方法,删除方法 ✅ 泛型方法,泛型类 ✅ 多文件热部署 ✅ spring boot项目 ✅ war包项目 ✅ 修改spring xml (只修改bean标签) ✅ 新增@Configuration @Bean ✅ pigeon服务框架 ✅ @Transactional 注解新增/修改,注解参数修改 ✅ 序列化 框架支持 ✅ dubbo alibaba ✅ dubbo apache ✅ dubbox ✅ motan ✅ 删除继承的class ❌ 枚举 字段修改 ❌ 修改static字段值 ❌ 其他功能迭代挖掘ing ☺ 6、强大到令人窒息的多文件热部署以及源码交流 由于篇幅原因和文采捉急,没有办法完整的写出热部署过程中遇到的各种各样稀奇古怪和无法解释的问题,和其中的坎坷经历。更多的功能需求迭代建议和agent源码技术交流可以加入QQ群来详细交流,QQ群号:825199617

优秀的个人博客,低调大师

iDempiere 企业级 ERP/CRM/SCM 8.2 Phong 发布

iDempiere r8.2 Phong于2020年12月20日(中国时间)发布。 官方的通知:https://groups.google.com/g/idempiere/c/ZRzwv_9rmBw/m/ioQEnWXHAQAJ 本次发布和以往有些不同,首先经历了1个多月的8.1 RC的测试后正式发布的8.2稳定版。 安装介质下载地址:https://www.idempiere.org/download/ 开发环境搭建方法等还没有更新至WIKI,后续请个别跟踪相关信息。 Hengsin更新了他的安装工具,可以尝试使用。 https://github.com/hengsin/idempiere-dev-setup 平台更新内容 Jetty = 9.4.33.v20201020 Eclipse 2020-12 Tycho 2.0 JUnit5(Support for JUnit5 Unit Testing) zk 9.5 功能更新内容 https://wiki.idempiere.org/en/Category:New_Features_v8.1 软件发布履历 2020年11月04日发布了8.1 RC 2019年11月22日发布了7.1 2019年01月05日发布了6.2 2018年11月04日发布了6.1 2017年10月31日发布了5.1 2016年10月31日发布了4.1 2015年10月31日发布了3.1 2014年10月31日发布了2.1 2013年10月31日发布了2.0 2013年06月27日发布了1.0c 2013年04月24日发布了1.0b 2012年10月31日发布了1.0a

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册