
哈Ha! 我叫Dima,我是hh.ru“架构”团队的一名开发人员。 除其他外,我使同事的工作变得更轻松。 在生产中运行代码是典型的任务。 因此,当我听说有问题时,我决定修复它们。
并非总是可以通过简单的UPDATE / INSERT来进行数据更改-有时您需要使用验证,事件总线等。 在这种情况下,最佳解决方案是直接在应用程序中执行任意代码。 我们拥有Java,因此当
JEP-222出现时 ,我立即想到JShell也许可以再次使脚本编写变得方便。 奇迹没有发生,因此,在裁减下,您将发现jvm最著名的Java解释器(和“ near-Java”)与示例的比较不是很深入。 我邀请所有对猫感兴趣的人。
我们使用
BeanShell来运行脚本,而对于2019年来说,这是可怕的:自
2016年以来的最新版本,缺少对lambda甚至泛型的支持-所有这些迫使我们编写自Java
1.4以来没有人编写的代码。
标准
在开始比较之前,我们制定了内置脚本引擎的要求。 挠头,我组成了以下清单:
- 支持当前的Java语法;
- 将外部上下文传递给口译员的能力;
- 中断执行的能力;
- 重定向I / O的能力;
- 信息反馈。
我们编写脚本所用的语言越类似于我们正在开发的语言,错误就越少-双手记住。 但是,当我们犯了在编译阶段发现的错误时,它们应该允许开发人员修复它们-这些是缺少的变量,行,堆栈轨道等名称的指示。
接下来,这些脚本应该在特定的上下文中工作,并且可以访问Spring上下文,并访问将为这些脚本提供服务的记录器。 没有这样的机会来传递上下文,它的接收就变成了一个任务。
如果错误仍然泄漏到运行时中,那么重新启动整个实例以停止执行是一个坏主意,因此您只需要能够随时简单地中断脚本的执行即可。
最后-在脚本工作期间向系统输出的任何消息仅在此脚本的上下文中才有意义。 在系统日志中,这样的结论几乎没有用。 因此,我希望能够重定向这些消息以作为响应。
所以走吧
- 支持当前的Java语法- 是
- 传达上下文的能力- 否
- 中断执行的能力- 是
- 重定向I / O的能力- 否
- 信息反馈- 是
我
必须 马上说
JEP-222 并非旨在创建嵌入式解释器-其目标是
REPL ,即能够快速编写代码原型。 这充满了许多后果。
首先,生活并没有为Java编译器做好准备,因为您可以在类外部声明一个方法,并使用尚未在该方法主体中声明的变量。 因此,执行本身隐藏在令人印象深刻的抽象层后面。
其次,REPL可能不在本地执行,而是在远程计算机上的某处执行,因此在编写API时要牢记这些功能。 我认为这是API无法将外部上下文传递给解释器并重定向I / O的主要原因。
此外,还有不同的启动模式-
远程 (当外壳通过JDI连接到计算机时)和
本地 。 由于不可能以编程方式传达上下文,但是我们仍然很希望,希望只停留在本地模式下,并且我们可以使用
代码生成但是,不幸的是,本地模式显然没有被认为是主要模式-
这种脚本调用了编译器的
死锁 。 尽管在JDI模式下相同的代码可以正常工作。
因此,我不得不放弃使用JShell,尽管通常来说API很奇怪,但是可以理解-我们将脚本提供给输入,获得事件流,对于每个事件我们都可以检查状态,获取错误和计费信息。 错误使我们能够识别允许使用的表达式:
更新: 从JShell收集了ScriptEngine 。 但是为此,我必须编写我的
启动模式- 支持当前的Java语法- 否
- 传达上下文的能力- 是的
- 中断执行的能力- 是
- 重定向I / O的能力- 是(但需要使用特殊方法)
- 信息反馈- 是
失败使我们注意到了我们现在正在使用的东西。 经过漫长的休息后,该项目似乎已生效,并且根据
路线图判断,该项目正在自信地朝着将解决我们所有问题的版本迈进-现在应该可以使用许多功能。
在撰写本文时,beanshell确实支持泛型,但是lambda仍然不起作用。 也许随着局势的释放,情况将会改变。
但是在集成方面,该引擎非常友好-支持标准javax.scripting,运行时错误非常冗长:

但是,使用无lambda的流是令人生厌的。 用另一种语言编写甚至可能更容易。 因此,我决定仔细研究“ near-java”部分。 当然,这里是脚本解释器角色的第一个候选人
- 支持当前的Java语法- 否
- 传达上下文的能力- 否
- 中断执行的能力- 是
- 重定向I / O的能力- 否
- 信息反馈- 是
很幸运的Java代码将是有效的kotlin代码。 但是我没有成功在kotlin上启动至少在Java上足够合适的任何东西,但是我们还是尝试一下。
Kotlin已经
宣布对javax.scripting的支持已经有两年了。
我们必须处理的第一个问题是依赖关系。
Kotlin-compiler包含了org.jdom类,这些类开始在应用程序中与org.jdom进行对抗并将其包装起来。因此,我们有kotlin-compiler-embeddable,其中所有这些类均放入自定义包中。
但是,在配置之后,事实证明外部上下文的传输不起作用。 现在这是一个严重的问题,直到无法解决为止,再深入研究是没有意义的。 如果您知道问题出在哪里以及如何解决,请在注释中写。
错误也很冗长:

- 支持当前的Java语法- 不,但是有类似物
- 传达上下文的能力- 是的
- 中断执行的能力- 是
- 重定向I / O的能力- 是
- 信息反馈- 是
Grooves除了支持javax.scripting外,还提供了自己的,更高级的API来进行解释器集成。 例如,可以传递AST转换,该转换允许您
在每个表达式之后添加
条件中断 。 这个东西是如此强大,以至于已经
很恐怖了 。
而且,Java(尤其是beanshell)代码可以是非常有效的凹槽代码。
集成和测试操作成功完成,除了工作表初始化和lambda语法(必须用大括号括起来)外,现有的binshell脚本都可以正常工作。 错误不仅仅是冗长的:

也许今天,它是唯一允许您在2019年为示例编写代码的解释器,另一方面,它满足合理的呈现给解释器的所有要求。
我们可以得出什么结论?
首先,REPL不是脚本引擎。 从命令行到集成到应用程序似乎只是一步,但是仔细研究发现,这些工具具有不同的任务,有时彼此矛盾。
第二个-今天还没有一个用于执行Java脚本的工具,因此,如果您需要这样的工具,请准备学习一种新的语法。