Groovyの代わりにJava

突然、プロジェクトでスクリプトが必要になり、問題が発生することがわかりました。進化と革命のどちらが優れているのでしょうか。

しかし、保守的なチームのあるレガシープロジェクトでは、グルーブを導入しようとしても失敗する可能性があります。 そして、リーダーシップは、プロジェクトの溝を逃さないために、さらに多くの理由を見つけることができます。 groovyは、同じscalaよりもはるかに単純で、Java対応のプログラマーに近いものです。







ただし、この場合でも、プロジェクトで動的にコンパイルされたスクリプトを使用できます。 Javaコードをメモリで動的にコンパイルしてjvmで実行し、動的に読み込まれたライブラリをmavenから使用する方法を学習します。 このためにできる限り少ないコードを記述し、使用プロセスを可能な限りシンプルにしたいと思います。 はい、私たちのプログラムでtools.jarが利用できることを望みません。



Groovyの専門家のinりを防ぐために、私自身この動的プログラミング言語を愛し 使用していることを認め、 Groovy Grapeにささやかな貢献をしました。 Groovyの利点を損なうことなく、動的なコンパイル、既存のJavaコードとの相互作用、依存関係の動的なインポート(Grapeが行っていること)で、jvmでのグルービングが競合しない分野でJavaを使用しようとします。



Javaでのコンパイルについて。 JSR 199



JSR 199標準はjavaコンパイラAPIであり、かなり以前から存在しています。 APIは、javaパッケージjavax.tools。*に含まれています。 ただし、Javaコードをメモリからメモリにコンパイルして実行するには、きれいなコードを作成してタンバリンに打ち込む必要があります。 コンパイラの実装はJREの一部として行われず、tools.jarはMavenリポジトリにありません。



JSR 199で書く量を減らす方法



毎回サイクルするのではなく、何か準備ができていて、同僚がジャニーノプロジェクトを提案した。 Janino自体にはjavaのサブセットの独自のコンパイラが含まれており、式の計算にのみ適しています。 JSR 199を使用するorg.codehaus.janino:commons-compiler-jdkがありますが、これはoracle / openjdk tools.jarのみに大きく依存しています。 夕方、ファイルを操作した後、 janino-commons-compiler-ecj (2.3 MB)が登場しました。これには、eclipse javaコンパイラーと、そのために変更されたcommons-compiler-jdkが含まれています。 自己完結型であり、JREでもコードをコンパイルおよびロードできます。 mvn-classloaderを追加すると、スクリプトで動的依存関係を使用してGroovy Grapeと同じ魔法を実行できます。



比較のために、動的言語mvel2 (989 KB)のライブラリは2、3倍少ないスペースしか必要としませんが、インターフェイスの実装、内部クラスの定義、匿名クラスのインスタンス化などの単純なことはできません。try / catch / finally構築およびスクリプトデバッグとの類似性はありませんそれは地獄のように見えるかもしれません。





Javaでスクリプトをコンパイルするには、依存関係com.github.igor-suhorukov:janino-commons-compiler-ecj:1.0と3行のコードのみが必要です。

SimpleClassPathCompiler simpleCompiler = new SimpleClassPathCompiler(dependenciesUrls); simpleCompiler.cook(SCRIPT_NAME+".java", scriptSourceText); Class<?> clazz = simpleCompiler.getClassLoader().loadClass(SCRIPT_NAME);
      
      







根拠がないようにするために、 githubで私が話しているすべての例があります。 サンプルスクリプトにはStream APIがあるため、実行するにはjava8をサポートするJVMが必要です。



それでは、始めましょう:

 git clone https://github.com/igor-suhorukov/janino-commons-compiler-ecj-example.git cd janino-commons-compiler-ecj-example mvn test
      
      







PhantomJsDowloaderクラスをmaven依存関係com.github.igor-suhorukov:phantomjs-runner:1.1からインポートするには、MavenClassLoaderを呼び出し、このMavenアーティファクトに基づいてコンパイラーのクラスパスを作成します。

 List<URL> urlsCollection = MavenClassLoader.usingCentralRepo().getArtifactUrlsCollection("com.github.igor-suhorukov:phantomjs-runner:1.1", null); new SimpleClassPathCompiler(urlsCollection);
      
      







次に、リポジトリから依存関係をダウンロードし、スクリプトをコンパイルして実行するメインJavaプログラムのテキストを指定します。

janino-commons-compiler-ecj-example / src / test / java / org.codehaus.commons.compiler.jdk / SimpleClassPathCompilerTest.java

 package org.codehaus.commons.compiler.jdk; import com.github.igorsuhorukov.codehaus.plexus.util.IOUtil; import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader; import org.junit.Test; import java.lang.reflect.Method; import java.net.URL; import java.util.List; public class SimpleClassPathCompilerTest { @Test public void testClassloader() throws Exception { final String SCRIPT_NAME = "MyScript"; List<URL> urlsCollection = MavenClassLoader.usingCentralRepo().getArtifactUrlsCollection("com.github.igor-suhorukov:phantomjs-runner:1.1", null); SimpleClassPathCompiler simpleCompiler = new SimpleClassPathCompiler(urlsCollection); simpleCompiler.setCompilerOptions("-8"); simpleCompiler.setDebuggingInformation(true,true,true); String src = IOUtil.toString(getClass().getResourceAsStream(String.format("/%s.java", SCRIPT_NAME))); simpleCompiler.cook(SCRIPT_NAME+".java", src); Class<?> clazz = simpleCompiler.getClassLoader().loadClass(SCRIPT_NAME); Method main = clazz.getMethod("main", String[].class); main.invoke(null, (Object) null); } public static void runIt(){ System.out.println("DONE!"); } }
      
      







そして、これはmavenの外部ライブラリを使用し、それをコンパイルしたクラスのrunItメソッドも呼び出すスクリプト自体です。

janino-commons-compiler-ecj-example / src / test / resources / MyScript.java

 import com.github.igorsuhorukov.phantomjs.PhantomJsDowloader; import com.github.igorsuhorukov.smreed.dropship.MavenClassLoader; import org.codehaus.commons.compiler.jdk.SimpleClassPathCompilerTest; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class MyScript{ public static void main(String[] args) throws Exception{ class Wrapper{ private String value; public Wrapper(String value) { this.value = value; } public String getValue() { return value; } } SimpleClassPathCompilerTest.runIt(); List<String> res = Arrays.asList(new Wrapper("do"), new Wrapper("something"), new Wrapper("wrong")).stream(). map(Wrapper::getValue).collect(Collectors.toList()); System.out.println(String.join(" ",res)); System.out.println("Classes from project classpath. For example "+MavenClassLoader.class.getName()); System.out.println(PhantomJsDowloader.getPhantomJsPath()); } }
      
      







この例を機能させるには、次の依存関係が必要です。



pom.xml

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.igor-suhorukov</groupId> <artifactId>janino-commons-compiler-ecj-example</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>janino-commons-compiler-ecj</artifactId> <version>1.0</version> <exclusions><exclusion><groupId>*</groupId><artifactId>*</artifactId></exclusion></exclusions> </dependency> <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>mvn-classloader</artifactId> <version>1.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
      
      







Javaのデバッグスクリプト



Javaプログラムの通常のデバッグと同様に、スクリプトをデバッグします。 ブレークポイントを設定し、スクリプトのコンパイル時にデバッグ情報を含めることを忘れないでください。

 simpleCompiler.setDebuggingInformation(true,true,true);
      
      









結論



数行追加するだけで、JavaプログラムからJavaコードをコンパイルする方法を学びました。 また、このスクリプトにMavenリポジトリからの依存関係を含め、IDEでコードをデバッグする方法も知っています。



この記事のアプローチは、要件がJava以外の使用を許可していない場合や、同僚がグルーブに対して敵対的であり、それについて何もできない場合、プロジェクトのスクリプトのgroovyを置き換えることができます。 AST /メタプログラミングについては、groovyが先にあり、あなたは正しいと主張することができますが、Javaではこれはそれほど単純ではありません。 ASTの操作については、記事「 Javaプログラムを使用したJavaプログラムの解析 」でJavaプログラムについて説明しました 次の出版物では、メトロプログラミングを使用して状況の解決を試みます。



この記事ではJavaの「純粋な」アプローチについて説明していますが、政治的な動機がプロジェクトでのこのアプローチの選択に影響を与える可能性がありますが、 JavaはGroovyよりもGroovyの優れている思います。



All Articles