Java 6 (via JSR
223) added scripting language support to the standard
library. The JVM ships with the Mozilla
Rhino JavaScript engine included. Where an engine is available, it is
easy to add support for other JVM scripting languages. It is useful to
be able to load these engines dynamically.
This Java 6 code uses the URLClassLoader
class to load and invoke a selection of engines at runtime:
public class HelloScripts {
/**
* @param name
* the engine name
* @param script
* the script to be invoked
* @param directories
* the directories JAR files should be loaded from
* @throws ScriptException
*/
private static void invokeScript(String name,
String script, String... directories)
throws ScriptException {
ClassLoader loader = new URLClassLoader(
buildClassPath(directories));
ClassLoader oldLoader = Thread.currentThread()
.getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(loader);
ScriptEngineManager seManager = new ScriptEngineManager(
loader);
ScriptEngine engine = seManager.getEngineByName(name);
if (engine == null) {
throw new IllegalStateException("No engine for "
+ name);
}
engine.eval(script);
} finally {
Thread.currentThread().setContextClassLoader(
oldLoader);
}
}
private static URL[] buildClassPath(String... directories) {
try {
final List<URL> classPath = new ArrayList<URL>();
for (String directory : directories) {
for (File pathname : new File(directory)
.listFiles()) {
if (pathname.isFile()
&& pathname.toString().toLowerCase()
.endsWith(".jar")) {
URL url = pathname.toURI().toURL();
classPath.add(url);
}
}
}
return classPath.toArray(new URL[classPath.size()]);
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
public static void main(String[] args)
throws ScriptException {
invokeScript("ECMAScript",
"println('Hello, ECMAScript!');");
invokeScript("groovy", "println 'Hello, Groovy!';",
System.getenv("GROOVY_HOME") + "/lib");
invokeScript("jruby", "puts 'Hello, JRuby!'", System
.getenv("SCRIPT_ENGINES")
+ "/jruby/build", System.getenv("JRUBY_HOME")
+ "/lib");
invokeScript("jython", "print 'Hello, Jython!'", System
.getenv("SCRIPT_ENGINES")
+ "/jython/build", System.getenv("JYTHON_HOME"));
}
} |
Note that the ClassLoader
used to load the engine is
set as the context ClassLoader
around any calls to the
engine. Failure to do this may result in errors.
Expected output:
Hello, ECMAScript!
Hello, Groovy!
Hello, JRuby!
Hello, Jython!
Configuration
As well as the libraries for the scripting languages, some of the
implementations require a third-party scripting engine from scripting.dev.java.net.
Later versions may include support by default, in which case the
external library can be dropped from the classpath.
Language |
Version |
Engine Name |
Requires External Engine |
JavaScript (Mozilla Rhino) |
1.6 (built in) |
ECMAScript |
no |
Groovy |
Groovy Version: 1.6.0 |
groovy |
no |
JRuby |
jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev
9419) |
jruby |
yes |
Jython |
Jython 2.2.1 |
jython |
yes |
Run on a Windows PC, the code uses the following environment
variables set to the library installation directories:
GROOVY_HOME=C:\Java\groovy-1.6.0
JRUBY_HOME=C:\Java\jruby-1.2.0
JYTHON_HOME=C:\Java\jython2.2.1
SCRIPT_ENGINES=C:\Java\jsr223-engines
I am trying to use this approach to load the latest version of Rhino. My environment has an older version already implemented, and I cannot seem to load the new one, even when I use the URL class loader AND create a script engine manager with that class loader as well.
ReplyDeleteIs it possible to override something that's already built in?
ClassLoaders will try to load any given class from the parent ClassLoader first, so anything on the JRE classpath will be loaded before anything specific to a given URLClassLoader.
ReplyDeleteThat said, anything that goes into the JRE usually gets its own com.sun.foo namespace to avoid collisions, so there shouldn't be a problem here.
I was able to invoke Rhino (rhino1_7R2) by adding the following command to the above program:
invokeScript(
"ECMAScript",
"print('Hello, Rhino!');",
System.getenv("SCRIPT_ENGINES")
+ "/javascript/build",
System.getenv("ECMA_HOME"));
ECMA_HOME just contains js.jar. Note that I used an engine from the set described in the post.