Groovy 脚本在运行时重新加载
Groovy script reloading on runtime
我希望能够从我的 Java 应用程序执行 groovy 脚本。
如果需要,我想在运行时重新加载 groovy 脚本。根据他们的 tutorials,我可以做类似的事情:
long now = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
try {
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine("");
System.out.println(groovyScriptEngine.run("myScript.groovy", new Binding()););
} catch (Exception e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("time " + (end - now));//24 secs
myScript.groovy
"Hello-World"
这很好用,每次我在 myScript.groovy 中更改一行时都会重新加载脚本。
问题是这不是时间效率,它所做的是每次都从文件中解析脚本。
还有其他选择吗?例如,一些更智能的东西可以检查脚本是否已经被解析,以及自上次解析以来它是否没有改变,不要再次解析它。
<< 根据评论进行编辑 >>
如评论之一所述,如果您需要性能,则必须将解析(慢)与执行(快)分开。
对于脚本源的反应性重新加载,我们可以使用 java nio watch service:
import groovy.lang.*
import java.nio.file.*
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
script = shell.parse(source.text)
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
上面启动了一个单独的观察线程,它使用 java 监视服务来监视父目录的文件修改,并在检测到修改时重新加载脚本源。这假定 java 版本 7 或更高版本。睡眠只是为了让代码更容易玩,如果测量性能自然应该被删除。
将字符串 System.currentTimeMillis()
存储在 script.groovy
和 运行 上面的代码将使它每秒循环两次。在循环期间对 script.groovy
进行修改会导致:
~> groovy solution.groovy
script ran: 1557302307784
script ran: 1557302308316
script ran: 1557302308816
script ran: 1557302309317
script ran: 1557302309817
source reloaded...
script ran: 1557302310318
script ran: 1557302310819
script ran: 1557302310819
source reloaded...
每当对源文件进行更改时,都会打印 source reloaded...
行。
我不确定 windows 但我相信至少 linux java 在幕后使用 fsnotify 系统,这应该使文件监控部分性能良好。
需要注意的是,如果我们真的不走运,两行之间的观察线程会重置脚本变量:
script.setBinding(binding)
def result = script.run()
这会破坏代码,因为重新加载的脚本实例没有绑定集。为了解决这个问题,我们可以使用锁:
import groovy.lang.*
import java.nio.file.*
import java.util.concurrent.locks.ReentrantLock
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
lock = new ReentrantLock()
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
withLock {
script = shell.parse(source.text)
}
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
withLock {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
}
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
def withLock(Closure c) {
def result
lock.lock()
try {
result = c()
} finally {
lock.unlock()
}
result
}
这使代码有些复杂,但可以保护我们免受往往难以追踪的并发问题的影响。
我希望能够从我的 Java 应用程序执行 groovy 脚本。 如果需要,我想在运行时重新加载 groovy 脚本。根据他们的 tutorials,我可以做类似的事情:
long now = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
try {
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine("");
System.out.println(groovyScriptEngine.run("myScript.groovy", new Binding()););
} catch (Exception e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println("time " + (end - now));//24 secs
myScript.groovy
"Hello-World"
这很好用,每次我在 myScript.groovy 中更改一行时都会重新加载脚本。
问题是这不是时间效率,它所做的是每次都从文件中解析脚本。
还有其他选择吗?例如,一些更智能的东西可以检查脚本是否已经被解析,以及自上次解析以来它是否没有改变,不要再次解析它。
<< 根据评论进行编辑 >>
如评论之一所述,如果您需要性能,则必须将解析(慢)与执行(快)分开。
对于脚本源的反应性重新加载,我们可以使用 java nio watch service:
import groovy.lang.*
import java.nio.file.*
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
script = shell.parse(source.text)
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
上面启动了一个单独的观察线程,它使用 java 监视服务来监视父目录的文件修改,并在检测到修改时重新加载脚本源。这假定 java 版本 7 或更高版本。睡眠只是为了让代码更容易玩,如果测量性能自然应该被删除。
将字符串 System.currentTimeMillis()
存储在 script.groovy
和 运行 上面的代码将使它每秒循环两次。在循环期间对 script.groovy
进行修改会导致:
~> groovy solution.groovy
script ran: 1557302307784
script ran: 1557302308316
script ran: 1557302308816
script ran: 1557302309317
script ran: 1557302309817
source reloaded...
script ran: 1557302310318
script ran: 1557302310819
script ran: 1557302310819
source reloaded...
每当对源文件进行更改时,都会打印 source reloaded...
行。
我不确定 windows 但我相信至少 linux java 在幕后使用 fsnotify 系统,这应该使文件监控部分性能良好。
需要注意的是,如果我们真的不走运,两行之间的观察线程会重置脚本变量:
script.setBinding(binding)
def result = script.run()
这会破坏代码,因为重新加载的脚本实例没有绑定集。为了解决这个问题,我们可以使用锁:
import groovy.lang.*
import java.nio.file.*
import java.util.concurrent.locks.ReentrantLock
def source = new File('script.groovy')
def shell = new GroovyShell()
def script = shell.parse(source.text)
def watchService = FileSystems.default.newWatchService()
source.canonicalFile.parentFile.toPath().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
lock = new ReentrantLock()
boolean keepWatching = true
Thread.start {
while (keepWatching) {
def key = watchService.take()
if (key.pollEvents()?.any { it.context() == source.toPath() }) {
withLock {
script = shell.parse(source.text)
}
println "source reloaded..."
}
key.reset()
}
}
def binding = new Binding()
def start = System.currentTimeMillis()
for (i=0; i<100; i++) {
withLock {
script.setBinding(binding)
def result = script.run()
println "script ran: $result"
}
Thread.sleep(500)
}
def delta = System.currentTimeMillis() - start
println "took ${delta}ms"
keepWatching = false
def withLock(Closure c) {
def result
lock.lock()
try {
result = c()
} finally {
lock.unlock()
}
result
}
这使代码有些复杂,但可以保护我们免受往往难以追踪的并发问题的影响。