2021-07-01

玩转 Java 动态编译,秀了秀了~!

来源:https://zhenbianshu.github.io

问题

之前的文章从Spring 的环境到 Spring Cloud 的配置中提到过,我们在使用 Spring Cloud 进行动态化配置,它的实现步骤是先将动态配置通过 @Value 注入到一个动态配置 Bean,并将这个 Bean 用注解标记为 @RefreshScope,在配置变更后,这些动态配置 Bean 会被统一销毁。

之后 Spring Cloud 的 ContextRefresher 会将变更后的配置作为一个新的 Spring Environment 加载进 ApplicationContext,由于 Scoped Bean 都是 Lazy Init 的,它们会在下一次使用时被使用新的 Environment 重新创建。

这套动态配置加载流程在使我们服务更加灵活的同时,也带来了很大的风险。首先从业务上,修改配置不像上线这么"重量级",不必要找 QA 进行回归测试,这就有可能引发一系列奇怪的 Bug,而且长时间发现不了,另外,Spring Cloud 本身没有 "fallback" 机制,一旦配置的数据类型出了问题,就会导致服务不可用。

为此,我给 Spring Cloud 提了个 issue,但作者认为变动太大,不好改也不必改。

其实我也明白这个问题的困境,每个人都得为自己要修改的配置负责,即使框架支持了 fallback,但将错误吞掉,配置修改后不生效也没什么变化可能也并不符合用户的期望。所以,尽量让用户要修改的配置正确成为了新的目标。

基于这种需求,我添加了一个动态配置的校验器,但实现里一部分代码来自 github,所以本文在总结思路的同时,也帮助我理解所有代码。

整体思路

由于框架层没法做太多事情,所以我的计划是将这些配置取出来,构造出一个独立的 Java 类,并在服务外新建一个 ApplicationContext 试图通过构造出来的 Java 类初始化一个 Spring Bean,如果这个 Spring Bean 初始化过程中报错了,说明配置是有问题的。

动态编译

通过配置构造 Java 类

首先要通过 .properties 文件构造出一个 Java 类,但问题是在配置里我们是不知道这些配置将要被怎么使用的,不知道它要被 Spring EL 如何处理,又将被转成什么类型。

这里我采用的策略是给配置添加注释,注释里使用一定的格式声明 EL 表达式和要生成的字段类型,当然这种实现有点 low,有人提议把这些信息放到配置项的 key 里,之后会再进行优化。

把各个字段解析完成后放到准备到的类模板中,就生成了一个 Config.java 类字符串,之后就要将这个字符串编译成字节码并由 Spring 加载成 Bean。

JavaCompiler

由于 Config.java 是在运行时生成的,所以编译也只能在运行时了,万幸 Java 有提供 javax.util.JavaCompiler 类进行 Java 类的动态编译,省去了"写入文件 —— 命令行编译 —— 类加载 —— 清理文件" 的复杂流程。

JavaCompiler 的典型应用示例如下:

JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();JavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);CompilationTask task = javaCompiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits);task.call();FileObject outputFile = fileManager.getFileForOutput(null, null, null, null);outputFile.getCharContent(true);

流程如下图:JavaCompiler 通过 JavaFileManager 管理输入和输出文件,使用时通过 getTask() 方法提交一个异步 CompilationTask 进行代码编译,代码编译时,JavaCompiler 通过 getCharContent() 从传入的 compilationUnits 获取到 .java 文件内容,把编译后的结果调用 CompiledByteCode 的 openOutputStream() 方法写到 CompiledByteCode 对象里。

委托模式

由于 JavaCompiler 的默认实现都是通过文件进行的,这不符合我的期望,我需要的是输入和输出都在内存进行,所以需要修改 JavaCompiler 的实现,JavaCompiler、JavaFileManager、JavaFileObject(Input/Output) 分别使用委托模式实现。 其中 JavaFileManager 已经有 ForwardingJavaFileManager 的实现,JavaFileObject 也有 SimpleJavaFileObject 的实现,我们继承其实现后重写部分方法即可。

我参考的源码:https://github.com/trung/InMemoryJavaCompiler

Spring Bean 实例化

要将 Config 类实例化成 Bean,我们可以在 FileSystem 实例化这个

类加载器

首先要让 Spring 能够加载到这些编译好的字节码,这就需要 ClassLoader 的配合。类加载器的默认实现不可能知道去加载我们内存里编译好的字节码,只好新加一个 ClassLoader,实现也很简单,继承 ClassLoader 抽象类,并实现 findClass 方法即可。

原文转载:http://www.shaoqun.com/a/839164.html

跨境电商:https://www.ikjzd.com/

锦桥纺织网:https://www.ikjzd.com/w/2469

淘粉吧首页:https://www.ikjzd.com/w/1725.html

刘小东:https://www.ikjzd.com/w/1853


来源:https://zhenbianshu.github.io问题之前的文章从Spring的环境到SpringCloud的配置中提到过,我们在使用SpringCloud进行动态化配置,它的实现步骤是先将动态配置通过@Value注入到一个动态配置Bean,并将这个Bean用注解标记为@RefreshScope,在配置变更后,这些动态配置Bean会被统一销毁。之后SpringCloud的Contex
c-tick认证:https://www.ikjzd.com/w/2074
修成正果:阿里巴巴以20亿美元全资收购网易考拉!:https://www.ikjzd.com/articles/106764
美国站卖家太难了,又一项费用将上涨!:https://www.ikjzd.com/articles/106765
作为亚马逊运营日常应该做什么呢?亚马逊卖家的日常运营操作!:https://www.ikjzd.com/articles/106766
亚马逊卖家平衡了:姐夫被判罚400万欧元!:https://www.ikjzd.com/articles/106771
被男同桌脱了内裤摸下面 用力挺进她的花苞:http://lady.shaoqun.com/m/a/248376.html
男生强迫女生把震动棒放裤子里 在教室捡到校花遥控器:http://lady.shaoqun.com/m/a/246925.html
强壮的公么征服我厨房 我在洗碗他在下面弄我:http://www.30bags.com/m/a/249935.html
毒恋班:大学恋爱班应该教什么?|观点:http://lady.shaoqun.com/a/394613.html
"最容易"泡"的四个女人":http://lady.shaoqun.com/a/394614.html
都说男多女少,教育部的权威数据告诉你高校的真实性别比例:http://lady.shaoqun.com/a/394615.html
Shopee开店必备:Shopee聊聊运营技巧:https://www.ikjzd.com/articles/146260

No comments:

Post a Comment