We do not need to compile dynamic languages into Java bytecode to use them in Java applications. Use the script packages added in Java platform and Standard Edition 6 (Java SE) (and are backward compatible with Java SE 5 ), java code can call multiple dynamic languages in a simple and unified way at runtime. This series of articles is divided into two parts. Part 1 describes the features of Java Script APIs. This article uses a simple hello World application to show how Java code executes the script code and how the script executes the Java code in turn. Part 2 will thoroughly study the powerful functions of the Java Script API.
Java developers know that Java is not the best language in any situation. This year, the release of jruby 1.0 and groovy led a boom, prompting people to add dynamic languages to their Java applications. Groovy, jruby, Rhino, Jython, and some other open-source projects make it possible to write code in so-called scripting languages and run it in JVM (see references ). Generally, integrating these languages in Java code requires an understanding of the APIs and features specific to various interpreters.
The javax. Script package added in Java SE 6 makes it easier to integrate dynamic languages. By using a group of interfaces and specific classes, this package allows us to simply call Multiple scripting languages. However, the Java Script API function is not only used to write scripts in applications. This script package enables us to read and call external scripts at runtime, this means that we can dynamically modify these scripts to change the behavior of running applications.
Java Script API
Comparison between script and dynamic
The term scripts usually indicate the languages that run in the interpreter shell. They often do not have separate compilation steps. The term dynamics usually indicate the language that determines the type of a variable or the behavior of an object at run time. It often has the closure and Continuity Characteristics. Some common programming languages have these two features at the same time. The scripting language is preferred because the focus of this article is the Java Script API, rather than the lack of dynamic features of the mentioned language.
In October 2006, the Java language added a script package, which provided a unified way to integrate the script language into Java applications. For language developers, they can use this package to write glue code so that people can call their language in Java applications. For Java developers, the script package provides a set of classes and interfaces, allowing a public API to call scripts written in multiple languages. Therefore, the script package is similar to the Java database connectivity (JDBC) package in different languages (such as different databases), and can be integrated into the Java platform using consistent interfaces.
In the past, in Java code, dynamic calling of the scripting language involved the use of unique classes provided by various language releases or the use of Apache's Jakarta bean Scripting framework (BSF ). BSF unifies a set of scripting languages within an API (see references ). Using the Java SE 6 script API, more than 20 scripting languages (applescript, groovy, JavaScript, jelly, PHP, Python, Ruby, and velocity) can all be integrated into Java code, this depends on BSF in a large program.
The script API provides two-way visibility between Java applications and external scripts. Java code can not only call external scripts, but also allow those scripts to access selected Java objects. For example, an external Ruby script can call a method on a Java object and access the properties of the object, this allows the script to add the behavior to the running application (if the behavior of the application cannot be predicted during development ).
An external script can be called to enhance, configure, monitor, or perform other runtime operations on the application at run time. For example, you can modify business rules without stopping the application. Script Packages may play the following roles:
· Write business rules in a language that is simpler than the Java language, without using a mature rule engine.
· Create a plug-in architecture that allows you to dynamically customize applications.
· Integrate existing scripts into Java applications, such as scripts for processing or converting file articles.
· Use sophisticated programming languages (rather than attribute files) to configure runtime behavior of applications from outside.
· Add a domain-specific language to a Java application ).
· Use the scripting language during the development of Java application prototypes.
· Compile the application test code in the script language.
Hello, script world
The helloscriptingworld class (the code in this article can be obtained from the download section) demonstrates some key features of the Java Script package. It uses hardcoded JavaScript as the sample scripting language. The main () method of this class (as shown in Listing 1) will create a Javascript script engine and call five methods respectively (as shown in the following list) used to highlight the features of the script package.
Listing 1. helloscriptingworld main method
| Public static void main (string [] ARGs) throws scriptexception, nosuchmethodexception { Scriptenginemanager scriptenginemgr = new scriptenginemanager (); Scriptengine jsengine = scriptenginemgr. getenginebyname ("JavaScript "); If (jsengine = NULL ){ System. Err. println ("No script engine found for JavaScript "); System. Exit (1 ); } System. Out. println ("calling invokehelloscript ..."); Invokehelloscript (jsengine ); System. Out. println ("/ncalling definescriptfunction ..."); Definescriptfunction (jsengine ); System. Out. println ("/ncalling invokescriptfunctionfromengine ..."); Invokescriptfunctionfromengine (jsengine ); System. Out. println ("/ncalling invokescriptfunctionfromjava ..."); Invokescriptfunctionfromjava (jsengine ); System. Out. println ("/ncalling invokejavafromscriptfunction ..."); Invokejavafromscriptfunction (jsengine ); } |
The main function of the main () method is to obtain a javax. Script. scriptengine instance (the first two lines of code in Listing 1 ). The script engine can load and execute scripts in a specific language. It is the most frequently used and important class in Java Script packages. We get a script engine (the first line of code) from javax. Script. scriptenginemanager ). Generally, a program only needs to obtain a script engine instance, unless it uses many scripting languages.
Scriptenginemanager class
Scriptenginemanager may be the only frequently used class in the script package; most of the others are interfaces. It may be the only class in the script package that needs to be instantiated directly or indirectly (through dependency injection mechanisms such as spring framework. Scriptenginemanager can return the script engine in the following three ways:
· Request the JavaScript engine through the engine or language name, such as listing 1.
· File extensions used by scripts in this language, such as. RB of Ruby scripts.
· MIME types declared by the script engine that know how to process.
Why is JavaScript used in this example?
The Hello world example in this article uses some JavaScript scripts because JavaScript code is easy to understand, however, it is mainly because Sun Microsystems and BEA Systems Provide a Java 6 runtime environment with a javascript interpreter based on Mozilla rhino open source JavaScript. When using JavaScript, you do not need to add the script language JAR file in the class path.
Scriptenginemanager indirectly searches for and creates a script engine. That is to say, when the script engine management program is instantiated, scriptenginemanager will use the new service discovery mechanism in Java 6 to find all registered javax. Script. scriptenginefactory implementations in the class path. These factory classes are encapsulated in Java Script API implementation; you may never need to directly process these factory classes.
After scriptenginemanager finds all the Script Engine Factory classes, It queries each class and determines whether the requested script engine can be created-in List 1, it is the JavaScript engine. If the factory says it can create a script engine for the required language, the hypervisor will require the factory to create an engine and return it to the caller. If the factory of the requested language is not found, the hypervisor returns NULL, and the code in Listing 1 checks the return value of null and prevents it.
Scriptengine Interface
As mentioned above, the code will use the scriptengine instance to execute the script. The script engine acts as an intermediate program between the script code and the underlying language interpreter or compiler that finally executes the code. In this way, we do not need to know which classes are used by each interpreter to execute the script. For example, the jruby script engine can pass the code to an instance of the org. jruby. Ruby class of jruby. Compile the script into an intermediate form, and then call it to calculate the script and process the returned value. The implementation of the script engine hides some details, including how the interpreter shares class definitions, application objects, and input/output streams with Java code.
Figure 1 shows the overall relationship between the application, Java Script API, scriptengine implementation, and script language interpreter. We can see that the application only depends on the script API, which provides the scriptenginemanager class and scriptengine interface. The scriptengine implementation component handles the details using the specific script language interpreter.
Figure 1: script API component relationship |
You may ask: How can I obtain the jar files required by the script engine implementation and language interpreter? The best way is to find the Script Engine implementation in the open-source scripting Project hosted on java.net (see references ). You can find the scripting engine implementation in many languages and links to other websites on java.net. The scripting project also provides various links through which you can download the interpreters of supported scripting languages.
In listing 1, the main () method passes scriptengine to each method for calculating the JavaScript code of this method. The first method is shown in Listing 2. The invokehelloscript () method calls the eval method of the script engine to calculate and execute specific strings in JavaScript code. The scriptengine interface defines six overloaded eval () methods to treat the received script as a string or Java. io. reader Object Computing, Java. io. reader objects are generally used to read scripts from external sources (such as files.
Listing 2. invokehelloscript Method
Private Static void invokehelloscript (scriptengine jsengine) throws scriptexception { Jsengine. eval ("println ('Hello from JavaScript ')"); } |
Script Execution Context
The sample script in the helloscriptingworld application uses the Javascript println () function to output results to the console, but we have full control over the input and output streams. The script engine provides an option to modify the context of the script execution, which means that we can modify the standard input stream, standard output stream, and standard error stream, you can also define which global variables and Java objects are available for the script being executed.
In the invokehelloscript () method, JavaScript outputs hello from JavaScript to the standard output stream. In this example, it is the console window. (Listing 6 contains the complete output when running helloscriptingworldapplication .)
Note that javax. Script. scriptexception is thrown by both this and other methods in the class. The selected exception (the only exception defined in the script package) indicates that the engine cannot parse or execute the given code. All Script Engine eval () Methods declare to throw a scriptexception, so our code needs to handle these exceptions as appropriate.
Listing 3 shows two related methods: definescriptfunction () and invokescriptfunctionfromengine (). The definescriptfunction () method also uses a hard-coded JavaScript code to call the eval () method of the script engine. Note that all the work of this method is to define a JavaScript function sayhello (). No code is executed. The sayhello () function has only one parameter. It uses the println () statement to output this parameter to the console. The javascript interpreter of the script engine adds this function to the global environment for subsequent eval calls (this call occurs in the invokescriptfunctionfromengine () method, which is not surprising ).
Listing 3. definescriptfunction and invokescriptfunctionfromengine Methods
Private Static void definescriptfunction (scriptengine engine) throws scriptexception { // Define a function in the Script Engine Engine. eval ( "Function sayhello (name) {" + "Println ('hello, '+ name)" + "}" ); }Private Static void invokescriptfunctionfromengine (scriptengine engine) Throws scriptexception { Engine. eval ("sayhello ('World! ')"); } |
These two methods demonstrate that the script engine can maintain the state of application components and can use the state in subsequent eval () method calls. The invokescriptfunctionfromengine () method can use the maintained State by calling the sayhello () JavaScript function defined in the eval () call.
Many script engines maintain the state of global variables and functions between eval () calls. However, it is worth noting that the Java Script API does not require the script engine to provide this feature. The JavaScript, groovy, and jruby script engines used in this article do maintain these statuses between eval () calls.
The code in Listing 4 was modified based on the previous example. The original invokescriptfunctionfromjava () method does not use the eval () method or JavaScript code of scriptengine when calling the sayhello () JavaScript function. In contrast, the methods in Listing 4 Use the javax. Script. invocable interface of the Java Script API to call functions maintained by the script engine. The invokescriptfunctionfromjava () method passes the script engine object to the invocable interface, then calls the invokefunction () method on the interface, and finally calls the sayhello () JavaScript function using the given parameter. If the called function requires a return value, the invokefunction () method encapsulates the value as a Java object type and returns it.
Listing 4. invokescriptfunctionfromjava Method
Private Static void invokescriptfunctionfromjava (scriptengine engine) Throws scriptexception, nosuchmethodexception { Invocable invocableengine = (invocable) engine; Invocableengine. invokefunction ("sayhello", "from Java "); } |
Use proxy for advanced script calls
When a script function or method implements a Java interface, you can use advanced invocable. The invocable interface defines a getinterface () method. This method uses the interface as a parameter and returns a Java code object that implements this interface. After obtaining the proxy object from the script engine, you can treat it as a normal Java object. The method called by the proxy will be delegated to the script engine for execution in the script language.
Note that there is no JavaScript code in Listing 4. The invocable interface allows Java code to call a script function without knowing its implementation language. If the script engine cannot find a function with a given name or parameter type, the invokefunction () method throws a java. Lang. nosuchmethodexception.
The Java Script API does not require the script engine to implement the invocable interface. In fact, the code in Listing 4 should use the instanceof operator to ensure that the script engine implements the invocable interface before cast.
Use the script code to call the Java method
The examples in listing 3 and 4 demonstrate how Java code calls functions or methods defined in scripting. You may ask: Can the code written in the scripting language call methods on Java objects in turn? The answer is yes. The invokejavafromscriptfunction () method in listing 5 shows how to enable the script engine to access Java objects and how script code can call methods for these Java objects. Specifically, the invokejavafromscriptfunction () method uses the put () method of the script engine to provide the helloscriptingworld class instance to the engine. When the engine has access to a Java object (the name provided by the put () call), the script code in the eval () method script uses this object.
Listing 5. invokejavafromscriptfunction and gethelloreply Methods
Private Static void invokejavafromscriptfunction (scriptengine engine) Throws scriptexception { Engine. Put ("helloscriptingworld", new helloscriptingworld ()); Engine. eval ( "Println ('invoking gethelloreply method from JavaScript... ');" + "Var MSG = helloscriptingworld. gethelloreply (vjavascript ');" + "Println ('java returned: '+ MSG )" ); }/** Method invoked from the above script to return a string .*/ Public String gethelloreply (string name ){ Return "Java method gethelloreply says, 'Hello," + name + "'"; } |
The javascript code contained in the eval () method call in listing 5 uses the put () method of the script engine to call the provided variable name helloscriptingworld to access and use the helloscriptingworld Java object. The second line of JavaScript code in listing 5 calls the gethelloreply () Public Java method. The gethelloreply () method returns the Java method gethelloreply says, 'Hello, <parameter> 'string. The javascript code in the eval () method assigns the Java return value to the MSG variable, and then prints it to the console.
Java object Conversion
When the Script Engine enables scripts running in the engine environment to use Java objects, the engine needs to encapsulate them into the object types applicable to the script language. Encapsulation may involve some appropriate object-value conversion. For example, Java integer objects can be used directly in mathematical expressions of the script language. The study on how to convert a Java object to a script object is particularly relevant to the engines of various script languages and is not covered in this article. However, you should be aware of the conversion, because you can test to ensure that the conversion method in the script language meets your expectations.
Scriptengine. Put and Its Related get () methods are the main way to share objects and data between Java code and scripts running in the script engine. (For details about this aspect, see the script-execution scope section later in this article .) When we call the put () method of the engine, the script engine associates the second parameter (any Java object) with a specific string keyword. Most script engines allow scripts to use specific variable names to access Java objects. The script engine can handle the name passed to the put () method at will. For example, the jruby Script Engine allows Ruby code to access helloscriptingworld using the global $ helloscriptingworld object to conform to the syntax of Ruby global variables.
The get () method of the script engine retrieves available values in the script environment. Generally, Java code uses the get () method to access all global variables and functions in the script environment. However, only Java objects that explicitly share put () with scripts can be accessed by scripts.
This feature allows external scripts to access and operate Java objects in running applications is a powerful technique for extending Java program functions. (Part 1 will study this technique through examples ).
Run the helloscriptingworld Application
You can run the helloscriptingworld application by downloading and building source code. The. ZIP file contains an ant script and a Maven build script to help you compile and run the sample application. Perform the following steps:
· Download this. ZIP file.
· Create a new directory, such as Java-scripting, and decompress the files downloaded in step 1 to this directory.
· Open the shell command line and go to the directory.
· Run the ant run-Hello command.
You can see the ant console output similar to listing 6. Note that the definescriptfunction () function does not produce any output, because although it defines the output, it does not call the JavaScript function.
Listing 6. output when helloscriptingworld is run
Calling invokehelloscript... Hello from JavascriptCalling definescriptfunction... Calling invokescriptfunctionfromengine... Hello, world! Calling invokescriptfunctionfromjava... Hello, from Java Calling invokejavafromscriptfunction... Invoking gethelloreply method from JavaScript... Java returned: Java method gethelloreply says, 'Hello, JavaScript' |
Java 5 compatibility
Java SE 6 introduces the Java Script API, but you can also use Java SE 5 to run this API. You only need to provide an implementation of the missing javax. Script package class. Fortunately, this implementation is included in the Java specification request 223 reference implementation (see references for download links .) JSR 223 defines Java Script APIs.
If you have downloaded the JSR 223 reference implementation, unzip the download file and copy the script-api.jar, script-js.jar, and JS. Jar files to your class path. These files provide the script API, the Javascript script engine interface, and the Javascript script engine attached to Java SE 6.
Script Execution Scope
Compared with simply calling the get () and put () Methods of the engine, it is better configurable to expose Java objects to scripts running in the script engine. When we call the get () or put () method on the script engine, the engine will retrieve or save the requested keyword in the default instance of the javax. Script. bindings interface. (The bindings interface is only a map interface, which is used to force the keyword to be a string .)
When the code calls the eval () method of the script engine, the keyword and value bound by the engine by default are used. However, you can provide your own bindings object for eval () calls to restrict which variables and objects are visible to the specific script. This call looks similar to eval (string, bindings) or eval (reader, bindings ). To help you create custom bindings, the script engine provides a createbindings () method, which is a bindings object with empty content. Using the bindings object to temporarily call Eval will hide the Java object previously saved in the default binding of the engine.
To add a function, the script engine has two default bindings: one is the "engine scope" binding used by get () and put () calls, and the other is the "global scope" binding, when an object cannot be found in the "engine scope", the engine uses the second binding method for search. The script engine does not need to allow the script to access global binding. Most scripts can access it.
Global scope binding is designed to share objects between different script engines. All the script engines returned by the scriptenginemanager instance are "global scope" bound objects. You can use the getbindings (scriptcontext. global_scope) method to retrieve the global binding of an engine, and use the setbindings (bindings, scriptcontext. global_scope) method to set global binding for the engine.
Scriptcontext is an interface that defines and controls the context of the script engine runtime. The scriptcontext of the script engine contains "engine" and "Global" scope binding, as well as the input and output streams used for standard input and output operations. You can use the getcontext () method of the engine to obtain and operate the context of the script engine.
Some script API concepts, such as scope, binding, and context, seem confusing at first, because their meanings are cross-colored. The source code download file in this article contains a JUnit test file named scriptapirhinotest, which is located in the src/test/Java directory. This file can help explain these concepts through Java code.
Future Plans
Now, you have a basic understanding of the Java Script API. Part 1 of this series of articles will be expanded on this basis to demonstrate a more practical example application. This application uses an external script file written together with groovy, Ruby, and JavaScript to define the business logic that can be modified at runtime. As you can see, defining business rules in a scripting language can make it easier to write rules and make it easier for people outside programmers to read them, such as business analysts or rule writers.