Java集成Groovy

4 篇文章 0 订阅

Java集成Groovy

1. 介绍

在这次教程里,我们将会探索一下如何将Groovy集成到一个Java应用中.

2. Groovy的简短介绍

Groovy是一个很有用的弱类型动态语言。开发支持主要来源于Apache基金会和超过200个开发者的Groovy社区。

它可以用来构建一个完整的工程,或者作为一个Module,第三方集成到Java代码中。甚至可以作为脚本在执行时动态编译。

更多的介绍,请阅读 Introduction to Groovy Language 或者 official documentation.

3. Maven依赖

在本教程时,最新的稳定版本是2.5.7, 2.63.0都还在Beta阶段。

类似于Spring Boot方便快捷,我们只需要引用groovy-all一个依赖就可以,不用关系它内部依赖的groovy其他module版本。

<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>${groovy.version}</version>
    <type>pom</type>
</dependency>

4. 联合编译(Joint compilation)

在开始具体如何编写Maven前,我们需要了解一下它集成的原理。

我们的代码包含了Java和Groovy文件. Groovy可以直接找到Java的class,但是Groovy应该如何找到Groovy的类和方法呢?

这就需要感谢联合编译了。

联合编译是一个Maven命令,负责将一个工程内的java和groovy进行编译。

在联合编译帮助下,Groovy编译器可以完成以下几件事情:

  • 解析groovy原文件
  • 根据编译实现方式的不同,创建stubs
  • 调用Javac编译这些stubs,从而使Java的类可以找到
  • 编译Groovy源文件,这样Groovy就可以找到依赖的Java类和方法

根据编译插件实现的不同,我们需要将Groovy文件放入到特定的文件夹或者通过配置告诉编译插件。

没有联合编译,Java原文件会被当成Groovy编译器当成Groovy脚本进行编译。有时候这是可行的,因为自从1.7开始,java的语法和groovy语法是兼容的,但是含义却可能并不一致。

5. Maven编译插件

支持联合编译的不止一个,但是都各有优缺点。目前使用最多的是Groovy-EclipseGMaven+.

5.1. Groovy-Eclipse插件

Groovy-Eclipse Maven plugin通过其他插件都会生成stubs来减少联合编译的复杂度,但是却显示比较奇怪。

为了可以使用最新的编译版本,我们需要添加Maven的二进制库:

<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>

然后,在插件部分,我们仍然需要配置Groovy编译器的版本.

实际上,我们使用的the Maven compiler plugin并不真正执行编译groovy,而是将这个编译工作交给 [the groovy-eclipse-batch artifact](https://search.maven.org/search?q=g:org.codehaus.groovy a: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>

groovy-all版本应该与编译器版本保持一致。

最后我们配置我们的source路径。编译器将自动会扫描src/main/javasrc/main/groovy,但是如果我们的java文件夹是空的,那么编译器就会停止不去寻找groovy文件

对于test也是同样的机制。

如果想要强制扫描文件,我们可以添加任意文件到src/main/javasrc/test/java,或者添加配置[groovy-eclipse-compiler plugin](https://search.maven.org/search?q=g:org.codehaus.groovy a:groovy-eclipse-compiler):

<plugin>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-eclipse-compiler</artifactId>
    <version>3.3.0-01</version>
    <extensions>true</extensions>
</plugin>

配置负责告诉编译器去额外进行编译。

5.2. GMavenPlus插件

GMavenPlus plugin 看起来有点类似GMaven plugin, 但在这个项目中,作者不仅仅是改动了一下功能,更是将编译器的版本与Groovy版本进行解耦

要想达到这样的目标,插件需要定义出编译插件的标准。

GMavenPlus compiler有一些其他编译插件没有的特性, 比如说invokedynamic, 交互式命令行, 和Android.

但它同时也会有一些劣势:

  • 如果 修改Maven’s source文件夹 将对Java和Groovy都进行重新编译。
  • 需要我们关注如何删除studs,如果maven的goal执行过程中不包括这个,那么就会更麻烦一些。

让我们来看一下配置[gmavenplus-plugin](https://search.maven.org/search?q=g:org.codehaus.gmavenplus a: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>

为了进行test,我们创建了一个gmavleplus-pom.xml.

5.3. 使用Eclipse-Maven插件编译

现在所有都配置好了,我们终于可以编译我们的class了。

在示例中,我们提供了一个简单的java应用,java代码在src/main/java, groovy代码在src/main/groovy下。

那么让我们来编译一下吧:

$ 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
...

这里可以看到Groovy编译的结果

5.4. 使用GMavenPlus编译

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]
...

可以注意到GMavenPlus使用了一下额外的几步:

  1. 对应每个Groovy生成stub
  2. 编译Java文件,包括stubs和java代码
  3. 编译Groovy文件

GMavenPlus继承了生成stubs,这可能会很开发者带来一些麻烦。

在理想的情形中,一切都应该很顺利。但是增加步骤就会增加失败的记录:比如,构建可能在清理stubs前失败.

如果这种情况发生,旧的stubs就会留下来,虽然明明一切都没问题,但是IDE会提示编译错误。

当然这种情况也可以通过clean的命令进行解决。

5.5. 在Jar包打包Groovy依赖

**如果需要执行run the program as a jar **, 我们需要添加 [the maven-assembly-plugin](https://search.maven.org/search?q=g:org.apache.maven.plugins a:maven-assembly-plugin), 把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>

当上述打包完成后,我们就可以执行下面的命令:

$ java -jar target/core-groovy-2-1.0-SNAPSHOT-jar-with-dependencies.jar com.baeldung.MyJointCompilationApp

6. 运行时加载Groovy代码

Maven编译可以让Groovy代码像java代码一样去编写,但是如果要实时改变代码逻辑,这种方式是不够的,我们仍然需要重启是使改过的代码生效.

通过使用Groovy的动态优势(有风险), 我们就可以解决上面的需要重启问题。

6.1. GroovyClassLoader

为了达到这个目标,我们需要 GroovyClassLoader, 它可以读取文本或者文件格式的源码,通过编译生成对应的class对象。

当动态语言来源是文件时,groovy会缓存编译的结果, 来避免重复编译带来的负担。

但来源是String对象的脚本时,groovy不会缓存,因此可能会造成内存的泄露问题。

GroovyClassLoader是Groovy集成的基础, 使用相对来说很简单:

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

7. 动态编译的缺点

使用以上的动态方法,我们可以做到应用不重启直接读取脚本或者jar包外的文件来生成代码。

这让我们很方便在系统执行时去添加新逻辑,达到类似热部署的开发模式。

但是任何事情都是双刃剑,我们也必须知道动态可能会在编译器和运行期产生的失败,从而破坏我们的代码安全性。

8. Java中使用Groovy的缺点

8.1. 性能

8.2. 找不到方法或属性

必须要设置安全和准确检查…

9. 总结

在这篇文章中,我们探索了一下groovy与java集成的方式,和可能会遇到的一些问题.

按照管理,文章中的代码可以访问GitHub.


原文链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值