简体   繁体   English

Groovy脚本在运行时重新加载

[英]Groovy script reloading on runtime

I want to be a able to execute a groovy script from my Java application. 我希望能够从Java应用程序执行Groovy脚本。 I want to reload the groovy script on runtime if needed. 如果需要,我想在运行时重新加载groovy脚本。 According to their tutorials , I can do something like that : 根据他们的教程 ,我可以做这样的事情:

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 myScript.groovy

"Hello-World"

This works fine and the script is reloaded everytime i change a line in myScript.groovy. 这工作正常,并且每次我在myScript.groovy中更改一行时都会重新加载脚本。

The problem is that this is not time efficient, what it does is parsing the script from the file every time. 问题在于这不是省时的,它每次都是从文件中解析脚本。

Is there any other alternative ? 还有其他选择吗? Eg something smarter that checks if the script is already parsed and if it did not change since the last parse do not parse it again. 例如,一些更聪明的工具可以检查脚本是否已被解析以及自上次解析以来是否未更改,因此无需再次解析。

<< edited due to comments >> <<因评论而编辑>>

Like mentioned in one of the comments, separating parsing (which is slow) from execution (which is fast) is mandatory if you need performance. 就像其中一条注释中提到的那样,如果需要性能,则必须将解析(缓慢)与执行(快速)分开。

For reactive reloading of the script source we can for example use the java nio watch service : 对于脚本源的响应式重载,我们可以使用例如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

The above starts a separate watcher thread which uses the java watch service to monitor the parent directory for file modifications and reloads the script source when a modification is detected. 上面的代码启动了一个单独的观察程序线程,该线程使用java监视服务监视父目录中的文件修改,并在检测到修改时重新加载脚本源。 This assumes java version 7 or later. 假定使用Java版本7或更高版本。 The sleep is just there to make it easier to play around with the code and should naturally be removed if measuring performance. 睡眠就在那儿,以便更轻松地处理代码;如果要衡量性能,则应该自然删除。

Storing the string System.currentTimeMillis() in script.groovy and running the above code will leave it looping twice a second. 将字符串System.currentTimeMillis()存储在script.groovy并运行上面的代码将使它每秒循环两次。 Making modifications to script.groovy during the loop results in: 在循环期间对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...

where the source reloaded... lines are printed whenever a change was made to the source file. 每当对源文件进行更改时,都会打印source reloaded...行。

I'm not sure about windows but I believe at least on linux that java uses the fsnotify system under the covers which should make the file monitoring part performant. 我不确定Windows,但我相信至少在Linux上,java在后台使用fsnotify系统,这应该使文件监视部分发挥作用。

Should be noted that if we are really unlucky, the script variable will be reset by the watcher thread between the two lines: 应该注意的是,如果我们真的很不幸,观察者线程将在两行之间重置脚本变量:

  script.setBinding(binding)
  def result = script.run()

which would break the code as the reloaded script instance would not have the binding set. 因为重新加载的脚本实例将没有绑定集,所以这将破坏代码。 To fix this, we can for example use a lock: 为了解决这个问题,我们可以使用例如锁:

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
}

which convolutes the code somewhat but keeps us safe against concurrency issues which tend to be hard to track down. 这使代码有些复杂,但使我们能够避免可能难以跟踪的并发问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM