1. Introduction
1.绪论
In this tutorial, we’ll explore the latest techniques to integrate Groovy into a Java Application.
在本教程中,我们将探讨将Groovy集成到Java应用中的最新技术。
2. A Few Words About Groovy
2.关于Groovy的几句话
The Groovy programming language is a powerful, optionally-typed and dynamic language. It’s supported by the Apache Software Foundation and the Groovy community, with contributions from more than 200 developers.
Groovy编程语言是一种功能强大、可选择类型的动态语言。它得到了Apache软件基金会和Groovy社区的支持,有200多名开发者为之做出贡献。
It can be used to build an entire application, to create a module or an additional library interacting with our Java code, or to run scripts evaluated and compiled on the fly.
它可以用来构建整个应用程序,创建一个模块或一个与我们的Java代码交互的附加库,或运行在运行中评估和编译的脚本。
For more information, please read Introduction to Groovy Language or go to the official documentation.
欲了解更多信息,请阅读Groovy语言简介或进入官方文档。
3. Maven Dependencies
3.Maven的依赖性
At the time of writing, the latest stable release is 2.5.7, while Groovy 2.6 and 3.0 (both started in fall ’17) are still in alpha stage.
在撰写本文时,最新的稳定版本是2.5.7,而Groovy 2.6和3.0(都在17年秋季开始)仍处于alpha阶段。
Similar to Spring Boot, we just need to include the groovy-all pom to add all the dependencies we may need, without worrying about their versions:
与Spring Boot类似,我们只需要包含groovy-all pom来添加我们可能需要的所有依赖项,而不必担心它们的版本。
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<type>pom</type>
</dependency>
4. Joint Compilation
4.联合编撰
Before going into the details of how to configure Maven, we need to understand what we are dealing with.
在讨论如何配置Maven的细节之前,我们需要了解我们正在处理的问题。
Our code will contain both Java and Groovy files. Groovy won’t have any problem at all finding the Java classes, but what if we want Java to find Groovy classes and methods?
我们的代码将同时包含Java和Groovy文件。Groovy在寻找Java类时不会有任何问题,但如果我们想让Java找到Groovy的类和方法呢?
Here comes joint compilation to the rescue!
这里是联合汇编的救援!
Joint compilation is a process designed to compile both Java and Groovy files in the same project, in a single Maven command.
联合编译是一个流程,旨在通过一条Maven命令,在同一个项目中同时编译Java和Groovy文件。
With joint compilation, the Groovy compiler will:
通过联合编译,Groovy编译器将。
- parse the source files
- depending on the implementation, create stubs that are compatible with the Java compiler
- invoke the Java compiler to compile the stubs along with Java sources – this way Java classes can find Groovy dependencies
- compile the Groovy sources – now our Groovy sources can find their Java dependencies
Depending on the plugin implementing it, we may be required to separate the files into specific folders or to tell the compiler where to find them.
根据实现它的插件,我们可能需要将文件分离到特定的文件夹中,或者告诉编译器在哪里找到它们。
Without joint compilation, the Java source files would be compiled as if they were Groovy sources. Sometimes this might work since most of the Java 1.7 syntax is compatible with Groovy, but the semantics would be different.
如果不进行联合编译,Java源文件将被编译为Groovy源文件。有时这可能会起作用,因为大部分Java 1.7的语法与Groovy兼容,但语义会有所不同。
5. Maven Compiler Plugins
5.Maven编译器插件
There are a few compiler plugins available that support joint compilation, each with its strengths and weaknesses.
目前有几个支持联合编译的编译器插件,每个插件都有其优势和劣势。
The two most commonly used with Maven are Groovy-Eclipse Maven and GMaven+.
最常用的两种Maven是Groovy-Eclipse Maven和GMaven+。
5.1. The Groovy-Eclipse Maven Plugin
5.1.Groovy-Eclipse的Maven插件
The Groovy-Eclipse Maven plugin simplifies the joint compilation by avoiding stubs generation, still a mandatory step for other compilers like GMaven+, but it presents some configuration quirks.
Groovy-Eclipse Maven插件 通过避免生成存根简化了联合编译,对于GMaven+等其他编译器来说,这仍然是一个强制性的步骤,但它也带来了一些配置上的怪癖。
To enable retrieval of the newest compiler artifacts, we have to add the Maven Bintray repository:
为了能够检索到最新的编译器工件,我们必须添加Maven Bintray资源库。
<pluginRepositories>
<pluginRepository>
<id>bintray</id>
<name>Groovy Bintray</name>
<url>https://dl.bintray.com/groovy/maven</url>
<releases>
<!-- avoid automatic updates -->
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
Then, in the plugin section, we tell the Maven compiler which Groovy compiler version it has to use.
然后,在插件部分,我们告诉Maven编译器必须使用哪个Groovy编译器版本。
In fact, the plugin we’ll use – the Maven compiler plugin – doesn’t actually compile, but instead delegates the job to the groovy-eclipse-batch artifact:
事实上,我们要使用的插件–Maven编译器插件–实际上并没有进行编译,而是将这项工作委托给 groovy-eclipse-batch工件。
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>3.3.0-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>${groovy.version}-01</version>
</dependency>
</dependencies>
</plugin>
The groovy-all dependency version should match the compiler version.
groovy-all依赖的版本应该与编译器版本相匹配。
Finally, we need to configure our source autodiscovery: by default, the compiler would look into folders such as src/main/java and src/main/groovy, but if our java folder is empty, the compiler won’t look for our groovy sources.
最后,我们需要配置我们的源码自动发现:默认情况下,编译器会查找诸如src/main/java和src/main/groovy的文件夹,但如果我们的java文件夹是空的,编译器将不会查找我们的groovy源码。
The same mechanism is valid for our tests.
同样的机制对我们的测试也是有效的。
To force the file discovery, we could add any file in src/main/java and src/test/java, or simply add the groovy-eclipse-compiler plugin:
为了强制发现文件,我们可以在src/main/java和src/test/java中添加任何文件,或者干脆添加groovy-eclipse-compiler插件。
<plugin>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>3.3.0-01</version>
<extensions>true</extensions>
</plugin>
The <extension> section is mandatory to let the plugin add the extra build phase and goals, containing the two Groovy source folders.
<extension>部分是强制性的,让插件添加额外的构建阶段和目标,包含两个Groovy源文件夹。
5.2. The GMavenPlus Plugin
5.2.GMavenPlus插件
The GMavenPlus plugin may have a name similar to the old GMaven plugin, but instead of creating a mere patch, the author made an effort to simplify and decouple the compiler from a specific Groovy version.
GMavenPlus插件可能有一个类似于旧版GMaven插件的名字,但作者没有创建一个单纯的补丁,而是努力简化编译器并使其与特定的Groovy版本脱钩。
To do so, the plugin separates itself from the standard guidelines for compiler plugins.
为此,该插件将自己与编译器插件的标准准则分开。
The GMavenPlus compiler adds support for features that were still not present in other compilers at the time, such as invokedynamic, the interactive shell console, and Android.
GMavenPlus编译器增加了对当时其他编译器尚不具备的功能的支持,例如invokedynamic、交互式外壳控制台和Android。
On the other side, it presents some complications:
另一方面,它也带来了一些复杂的问题。
- it modifies Maven’s source directories to contain both the Java and the Groovy sources, but not the Java stubs
- it requires us to manage stubs if we don’t delete them with the proper goals
To configure our project, we need to add the gmavenplus-plugin:
为了配置我们的项目,我们需要添加gmavenplus-plugin。
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<goals>
<goal>execute</goal>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<!-- any version of Groovy \>= 1.5.0 should work here -->
<version>2.5.6</version>
<scope>runtime</scope>
<type>pom</type>
</dependency>
</dependencies>
</plugin>
To allow testing of this plugin, we created a second pom file called gmavenplus-pom.xml in the sample.
为了测试这个插件,我们在样本中创建了第二个pom文件,名为gmavenplus-pom.xml。
5.3. Compiling With the Eclipse-Maven Plugin
5.3.用Eclipse-Maven插件进行编译
Now that everything is configured, we can finally build our classes.
现在一切都配置好了,我们终于可以建立我们的类。
In the example we provided, we created a simple Java application in the source folder src/main/java and some Groovy scripts in src/main/groovy, where we can create Groovy classes and scripts.
在我们提供的例子中,我们在源文件夹src/main/java中创建了一个简单的Java应用程序,在src/main/groovy中创建了一些Groovy脚本,在这里我们可以创建Groovy类和脚本。
Let’s build everything with the Eclipse-Maven plugin:
让我们用Eclipse-Maven插件来构建一切。
$ mvn clean compile
...
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ core-groovy-2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files
...
Here we see that Groovy is compiling everything.
这里我们看到,Groovy正在编译所有的东西。
5.4. Compiling With GMavenPlus
5.4.用GMavenPlus编译
GMavenPlus shows some differences:
GMavenPlus显示了一些差异。
$ mvn -f gmavenplus-pom.xml clean compile
...
[INFO] --- gmavenplus-plugin:1.7.0:generateStubs (default) @ core-groovy-2 ---
[INFO] Using Groovy 2.5.7 to perform generateStubs.
[INFO] Generated 2 stubs.
[INFO]
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ core-groovy-2 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to XXX\Baeldung\TutorialsRepo\core-groovy-2\target\classes
[INFO]
...
[INFO] --- gmavenplus-plugin:1.7.0:compile (default) @ core-groovy-2 ---
[INFO] Using Groovy 2.5.7 to perform compile.
[INFO] Compiled 2 files.
[INFO]
...
[INFO] --- gmavenplus-plugin:1.7.0:removeStubs (default) @ core-groovy-2 ---
[INFO]
...
We notice right away that GMavenPlus goes through the additional steps of:
我们马上就注意到,GMavenPlus经历了额外的步骤。
- Generating stubs, one for each groovy file
- Compiling the Java files – stubs and Java code alike
- Compiling the Groovy files
By generating stubs, GMavenPlus inherits a weakness that caused many headaches to developers in the past years, when working with joint compilation.
通过生成存根,GMavenPlus继承了一个弱点,这个弱点在过去几年中给开发者带来了许多令人头痛的问题,在使用联合编译时。
In the ideal scenario, everything would work just fine, but introducing more steps we have also more points of failure: for example, the build may fail before being able to clean up the stubs.
在理想的情况下,一切都会顺利进行,但引入更多的步骤,我们也有更多的故障点:例如,在能够清理存根之前,构建可能会失败。
If this happens, old stubs left around may confuse our IDE, which would then show compilation errors where we know everything should be correct.
如果发生这种情况,留在周围的旧存根可能会混淆我们的IDE,然后在我们知道一切都应该是正确的地方显示出编译错误。
Only a clean build would then avoid a painful and long witch hunt.
只有一个干净的建设,才能避免痛苦和漫长的猎奇。
5.5. Packaging Dependencies in the Jar File
5.5.在Jar文件中打包依赖关系
To run the program as a jar from the command line, we added the maven-assembly-plugin, which will include all the Groovy dependencies in a “fat jar” named with the postfix defined in the property descriptorRef:
为了从命令行以jar形式运行程序,我们添加了maven-assembly-plugin,它将在一个 “胖jar “中包含所有Groovy依赖项,并以属性descriptorRef:中定义的后缀命名。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<!-- MainClass in mainfest make a executable jar -->
<archive>
<manifest>
<mainClass>com.baeldung.MyJointCompilationApp</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- bind to the packaging phase -->
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
Once the compilation is complete we can run our code with this command:
一旦编译完成,我们可以用这个命令运行我们的代码。
$ java -jar target/core-groovy-2-1.0-SNAPSHOT-jar-with-dependencies.jar com.baeldung.MyJointCompilationApp
6. Loading Groovy Code on the Fly
6.飞快地加载Groovy代码
The Maven compilation let us include Groovy files in our project and reference their classes and methods from Java.
通过Maven编译,我们可以在项目中加入Groovy文件,并从Java中引用其类和方法。
Although, this is not enough if we want to change the logic at runtime: the compilation runs outside the runtime stage, so we still have to restart our application in order to see our changes.
不过,如果我们想在运行时改变逻辑,这还不够:编译是在运行时阶段之外运行的,所以我们仍然必须重新启动我们的应用程序,以便看到我们的变化。
To take advantage of the dynamic power (and risks) of Groovy, we need to explore the techniques available to load our files when our application is already running.
为了利用Groovy的动态能力(和风险),我们需要探索可用的技术,在我们的应用程序已经运行时加载我们的文件。
6.1. GroovyClassLoader
6.1.GroovyClassLoader
To achieve this, we need the GroovyClassLoader, which can parse source code in text or file format and generate the resulting class objects.
为了实现这一目标,我们需要GroovyClassLoader,它可以解析文本或文件格式的源代码,并生成所产生的类对象。
When the source is a file, the compilation result is also cached, to avoid overhead when we ask the loader multiple instances of the same class.
当源文件是一个文件时,编译结果也被缓存起来,以避免我们向加载器询问同一类的多个实例时的开销。
Script coming directly from a String object, instead, won’t be cached, hence calling the same script multiple times could still cause memory leaks.
直接来自String对象的脚本不会被缓存,因此多次调用同一脚本仍可能导致内存泄漏。
GroovyClassLoader is the foundation other integration systems are built on.
GroovyClassLoader是其他集成系统建立的基础。
The implementation is relatively simple:
实施起来相对简单。
private final GroovyClassLoader loader;
private Double addWithGroovyClassLoader(int x, int y)
throws IllegalAccessException, InstantiationException, IOException {
Class calcClass = loader.parseClass(
new File("src/main/groovy/com/baeldung/", "CalcMath.groovy"));
GroovyObject calc = (GroovyObject) calcClass.newInstance();
return (Double) calc.invokeMethod("calcSum", new Object[] { x, y });
}
public MyJointCompilationApp() {
loader = new GroovyClassLoader(this.getClass().getClassLoader());
// ...
}
6.2. GroovyShell
6.2.GroovyShell
The Shell Script Loader parse() method accepts sources in text or file format and generates an instance of the Script class.
Shell脚本加载器parse()方法接受文本或文件格式的源,并生成Script类的实例。
This instance inherits the run() method from Script, which executes the entire file top to bottom and returns the result given by the last line executed.
这个实例继承了来自Script的run()方法,它从上到下执行整个文件,并返回最后一行执行的结果。
If we want to, we can also extend Script in our code, and override the default implementation to call directly our internal logic.
如果我们愿意,我们也可以在我们的代码中扩展Script,并覆盖默认实现,直接调用我们的内部逻辑。
The implementation to call Script.run() looks like this:
调用Script.run()的实现是这样的。
private Double addWithGroovyShellRun(int x, int y) throws IOException {
Script script = shell.parse(new File("src/main/groovy/com/baeldung/", "CalcScript.groovy"));
return (Double) script.run();
}
public MyJointCompilationApp() {
// ...
shell = new GroovyShell(loader, new Binding());
// ...
}
Please note that the run() doesn’t accept parameters, so we would need to add to our file some global variables initialize them through the Binding object.
请注意,run()不接受参数,所以我们需要在我们的文件中添加一些全局变量,通过Binding对象初始化它们。
As this object is passed in the GroovyShell initialization, the variables are shared with all the Script instances.
由于这个对象是在GroovyShell初始化中传递的,所以这些变量与所有Script实例共享。
If we prefer a more granular control, we can use invokeMethod(), which can access our own methods through reflection and pass arguments directly.
如果我们喜欢更精细的控制,我们可以使用invokeMethod(),它可以通过反射访问我们自己的方法并直接传递参数。
Let’s look at this implementation:
让我们来看看这个实现。
private final GroovyShell shell;
private Double addWithGroovyShell(int x, int y) throws IOException {
Script script = shell.parse(new File("src/main/groovy/com/baeldung/", "CalcScript.groovy"));
return (Double) script.invokeMethod("calcSum", new Object[] { x, y });
}
public MyJointCompilationApp() {
// ...
shell = new GroovyShell(loader, new Binding());
// ...
}
Under the covers, GroovyShell relies on the GroovyClassLoader for compiling and caching the resulting classes, so the same rules explained earlier apply in the same way.
在封面下,GroovyShell依靠GroovyClassLoader来编译和缓存产生的类,所以前面解释的规则也以同样方式适用。
6.3. GroovyScriptEngine
6.3.GroovyScriptEngine
The GroovyScriptEngine class is particularly for those applications which rely on the reloading of a script and its dependencies.
GroovyScriptEngine类特别适用于那些依赖于重新加载脚本及其依赖关系的应用程序。
Although we have these additional features, the implementation has only a few small differences:
虽然我们有这些额外的功能,但实现起来只有一些小的差别。
private final GroovyScriptEngine engine;
private void addWithGroovyScriptEngine(int x, int y) throws IllegalAccessException,
InstantiationException, ResourceException, ScriptException {
Class<GroovyObject> calcClass = engine.loadScriptByName("CalcMath.groovy");
GroovyObject calc = calcClass.newInstance();
Object result = calc.invokeMethod("calcSum", new Object[] { x, y });
LOG.info("Result of CalcMath.calcSum() method is {}", result);
}
public MyJointCompilationApp() {
...
URL url = null;
try {
url = new File("src/main/groovy/com/baeldung/").toURI().toURL();
} catch (MalformedURLException e) {
LOG.error("Exception while creating url", e);
}
engine = new GroovyScriptEngine(new URL[] {url}, this.getClass().getClassLoader());
engineFromFactory = new GroovyScriptEngineFactory().getScriptEngine();
}
This time we have to configure source roots, and we refer to the script with just its name, which is a bit cleaner.
这一次,我们必须配置源根,而且我们在引用脚本时只用它的名字,这样就比较干净了。
Looking inside the loadScriptByName method, we can see right away the check isSourceNewer where the engine checks if the source currently in cache is still valid.
在loadScriptByName方法中,我们可以立即看到检查isSourceNewer,引擎检查当前缓存中的源是否仍然有效。
Every time our file changes, GroovyScriptEngine will automatically reload that particular file and all the classes depending on it.
每当我们的文件发生变化时,GroovyScriptEngine将自动重新加载该特定文件和所有依赖于它的类。
Although this is a handy and powerful feature, it could cause a very dangerous side effect: reloading many times a huge number of files will result in CPU overhead without warning.
虽然这是一个方便而强大的功能,但它可能会引起一个非常危险的副作用。多次重新加载大量的文件将导致CPU开销,而没有警告。
If that happens, we may need to implement our own caching mechanism to deal with this issue.
如果发生这种情况,我们可能需要实现我们自己的缓存机制来处理这个问题。
6.4. GroovyScriptEngineFactory (JSR-223)
6.4.GroovyScriptEngineFactory(JSR-223)
JSR-223 provides a standard API for calling scripting frameworks since Java 6.
JSR-223从Java 6开始提供了一个用于调用脚本框架的标准API。
The implementation looks similar, although we go back to loading via full file paths:
虽然我们回到了通过完整的文件路径加载的状态,但实现起来却很相似。
private final ScriptEngine engineFromFactory;
private void addWithEngineFactory(int x, int y) throws IllegalAccessException,
InstantiationException, javax.script.ScriptException, FileNotFoundException {
Class calcClas = (Class) engineFromFactory.eval(
new FileReader(new File("src/main/groovy/com/baeldung/", "CalcMath.groovy")));
GroovyObject calc = (GroovyObject) calcClas.newInstance();
Object result = calc.invokeMethod("calcSum", new Object[] { x, y });
LOG.info("Result of CalcMath.calcSum() method is {}", result);
}
public MyJointCompilationApp() {
// ...
engineFromFactory = new GroovyScriptEngineFactory().getScriptEngine();
}
It’s great if we are integrating our app with several scripting languages, but its feature set is more restricted. For example, it doesn’t support class reloading. As such, if we are only integrating with Groovy, then it may be better to stick with earlier approaches.
如果我们要将我们的应用程序与几种脚本语言集成,那么它是很好的,但是它的功能集受到了更多限制。例如,它不支持类重载。因此,如果我们只与Groovy集成,那么坚持使用早期的方法可能会更好。
7. Pitfalls of Dynamic Compilation
7.动态编译的误区
Using any of the methods above, we could create an application that reads scripts or classes from a specific folder outside our jar file.
使用上述任何一种方法,我们可以创建一个应用程序,从我们的jar文件之外的特定文件夹中读取脚本或类。
This would give us the flexibility to add new features while the system is running (unless we require new code in the Java part), thus achieving some sort of Continuous Delivery development.
这将为我们提供灵活性,在系统运行时增加新的功能(除非我们在Java部分需要新的代码),从而实现某种持续交付开发。
But beware this double-edged sword: we now need to protect ourselves very carefully from failures that could happen both at compile time and runtime, de facto ensuring that our code fails safely.
但要注意这把双刃剑:我们现在需要非常小心地保护自己,防止在编译时和运行时都可能发生的故障,事实上确保我们的代码安全失败。
8. Pitfalls of Running Groovy in a Java Project
8.在Java项目中运行Groovy的误区
8.1. Performance
8.1.业绩
We all know that when a system needs to be very performant, there are some golden rules to follow.
我们都知道,当一个系统需要有很好的性能时,有一些黄金规则需要遵循。
Two that may weigh more on our project are:
两个可能对我们的项目更有分量的是。
- avoid reflection
- minimize the number of bytecode instructions
Reflection, in particular, is a costly operation due to the process of checking the class, the fields, the methods, the method parameters, and so on.
特别是反思,由于检查类、字段、方法、方法参数等的过程,是一个昂贵的操作。
If we analyze the method calls from Java to Groovy, for example, when running the example addWithCompiledClasses, the stack of operation between .calcSum and the first line of the actual Groovy method looks like:
如果我们分析从Java到Groovy的方法调用,例如,当运行例子addWithCompiledClasses时,.calcSum和实际Groovy方法的第一行之间的操作栈看起来是这样的。
calcSum:4, CalcScript (com.baeldung)
addWithCompiledClasses:43, MyJointCompilationApp (com.baeldung)
addWithStaticCompiledClasses:95, MyJointCompilationApp (com.baeldung)
main:117, App (com.baeldung)
Which is consistent with Java. The same happens when we cast the object returned by the loader and call its method.
这与Java是一致的。当我们对加载器返回的对象进行铸造并调用其方法时,也会发生同样的情况。
However, this is what the invokeMethod call does:
然而,这就是invokeMethod调用的作用。
calcSum:4, CalcScript (com.baeldung)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:101, CachedMethod (org.codehaus.groovy.reflection)
doMethodInvoke:323, MetaMethod (groovy.lang)
invokeMethod:1217, MetaClassImpl (groovy.lang)
invokeMethod:1041, MetaClassImpl (groovy.lang)
invokeMethod:821, MetaClassImpl (groovy.lang)
invokeMethod:44, GroovyObjectSupport (groovy.lang)
invokeMethod:77, Script (groovy.lang)
addWithGroovyShell:52, MyJointCompilationApp (com.baeldung)
addWithDynamicCompiledClasses:99, MyJointCompilationApp (com.baeldung)
main:118, MyJointCompilationApp (com.baeldung)
In this case, we can appreciate what’s really behind Groovy’s power: the MetaClass.
在这种情况下,我们可以体会到Groovy的力量背后真正的东西:MetaClass。
A MetaClass defines the behavior of any given Groovy or Java class, so Groovy looks into it whenever there’s a dynamic operation to execute in order to find the target method or field. Once found, the standard reflection flow executes it.
MetaClass定义了任何给定的Groovy或Java类的行为,因此Groovy在有动态操作需要执行时就会查看它,以找到目标方法或字段。一旦找到,标准的反射流程就会执行它。
Two golden rules broken with one invoke method!
一个调用方法就能打破两条黄金规则
If we need to work with hundreds of dynamic Groovy files, how we call our methods will then make a huge performance difference in our system.
如果我们需要处理数以百计的动态Groovy文件,那么我们如何调用我们的方法就会在我们的系统中产生巨大的性能差异。
8.2. Method or Property Not Found
8.2.未找到方法或属性
As mentioned earlier, if we want to deploy new versions of Groovy files in a CD life cycle, we need to treat them like they were an API separate from our core system.
如前所述,如果我们想在CD生命周期中部署新版本的Groovy文件,我们需要把它们当作独立于核心系统的API。
This means putting in place multiple fail-safe checks and code design restrictions so our newly joined developer doesn’t blow up the production system with a wrong push.
这意味着要设置多种故障安全检查和代码设计限制,这样我们新加入的开发人员就不会因为错误的推送而炸毁生产系统。
Examples of each are: having a CI pipeline and using method deprecation instead of deletion.
这方面的例子有:有一个CI管道和使用方法废除而不是删除。
What happens if we don’t? We get dreadful exceptions due to missing methods and wrong argument counts and types.
如果我们不这样做会发生什么?我们会因为缺少方法和错误的参数数量和类型而出现可怕的异常。
And if we think that compilation would save us, let’s look at the method calcSum2() of our Groovy scripts:
如果我们认为编译可以拯救我们,让我们看看我们的Groovy脚本的方法calcSum2()。
// this method will fail in runtime
def calcSum2(x, y) {
// DANGER! The variable "log" may be undefined
log.info "Executing $x + $y"
// DANGER! This method doesn't exist!
calcSum3()
// DANGER! The logged variable "z" is undefined!
log.info("Logging an undefined variable: $z")
}
By looking through the entire file, we immediately see two problems: the method calcSum3() and the variable z are not defined anywhere.
通过查看整个文件,我们立即看到两个问题:方法calcSum3()和变量z没有在任何地方定义。
Even so, the script is compiled successfully, without even a single warning, both statically in Maven and dynamically in the GroovyClassLoader.
即便如此,该脚本还是编译成功了,甚至没有任何警告,在Maven中是静态的,在GroovyClassLoader中是动态的。
It’ll fail only when we try to invoke it.
只有当我们试图调用它时,它才会失败。
Maven’s static compilation will show an error only if our Java code refers directly to calcSum3(), after casting the GroovyObject like we do in the addWithCompiledClasses() method, but it’s still ineffective if we use reflection instead.
只有当我们的Java代码直接引用calcSum3(),像我们在addWithCompiledClasses()方法中那样铸造GroovyObject后,Maven的静态编译才会显示错误,但如果我们用反射代替,还是无效的。
9. Conclusion
9.结语
In this article, we explored how we can integrate Groovy in our Java application, looking at different integration methods and some of the problems we may encounter with mixed languages.
在这篇文章中,我们探讨了如何将Groovy集成到我们的Java应用中,研究了不同的集成方法以及在混合语言中可能遇到的一些问题。
As usual, the source code used in the examples can be found on GitHub.
像往常一样,例子中使用的源代码可以在GitHub上找到。