2017年に1つのライブラリの複数のバージョンでJavaアプリケーションを実行する方法
私が直面しなければならなかった1つの問題の解決策と、Java 9のコンテキストでのこの問題の調査を共有したいと思います。
単純な状況を想像してください。Elasticsearchクラスターをデプロイし、そこにデータをロードします。 このクラスターを検索するアプリケーションを作成しています。 Elasticsearchの新しいバージョンが絶えずリリースされているため、新しいバージョンをクラスターに導入します 問題 ローリングアップグレードを使用する機能。 しかし、ここに問題があります-ある時点で、保存されたデータの形式を変更し(たとえば、新しい機能の1つを最大限に活用するため)、インデックスの再作成を不適切にしました。 このオプションは私たちに適しています:同じマシンに新しいクラスターをインストールします-古いデータスキームを持つ最初のクラスターは検索のためだけに残り、新しいデータスキームを持つ受信データは2番目にロードされます。 次に、検索コンポーネントは2つのクラスターに接続する必要があります。
このアプリケーションはJava APIを使用してクラスターと通信します。つまり、Elasticsearch自体を依存関係にプルします。 5番目のバージョンとともにRest Clientがリリースされたことで、このような問題(Elasticsearchの便利なAPIからも)を節約できたことは注目に値しますが、2番目のバージョンのリリース時に対応する予定です。
Elasticsearch 1.7および2.4の2つのクラスターでドキュメントを検索するという単純なアプリケーションの例を使用して、可能な解決策を考えてみましょう。 コードはgithubで入手でき、この記事の構造を繰り返します(OSGiのみが欠落しています)。
ビジネスに取り掛かろう。 次の構造を持つMavenプロジェクトを作成します。
+---pom.xml +---core/ | +---pom.xml | +---src/ | +---main/ | | +---java/ | | | +---elasticsearch/ | | | +---client/ | | | +---SearchClient.java | | | +---Searcher.java | | +---resources/ | +---test/ | +---java/ +---es-v1/ | +---pom.xml | +---src/ | +---main/ | | +---java/ | | | +---elasticsearch/ | | | +---client/ | | | +---v1/ | | | +---SearchClientImpl.java | | +---resources/ | +---test/ | +---java/ +---es-v2/ +---pom.xml +---src/ +---main/ | +---java/ | | +---elasticsearch/ | | +---client/ | | +---v2/ | | +---SearchClientImpl.java | +---resources/ +---test/ +---java/
明らかに、1つのモジュールで同じライブラリの複数のバージョンを接続しても機能しないため、プロジェクトはマルチモジュールである必要があります。
- コア -これがアプリケーションのロジック全体です。
- Elasticsearchとの対話用のインターフェイスを別のモジュールに転送することができます(そして必要です)。
- es-v1 -Elasticsearch 1.7.5のインターフェース実装。
- es-v2 -Elasticsearch 2.4.5のインターフェース実装。
コアモジュールには、 es-v1およびes-v2モジュールの「テスト」であるSearcher
クラスが含まれています。
public class Searcher { public static void main(String[] args) throws Exception { List<SearchClient> clients = Arrays.asList( getClient("1"), getClient("2") ); for (SearchClient client : clients) { System.out.printf("Client for version: %s%n", client.getVersion()); Map doc = client.search("test"); System.out.println("Found doc:"); System.out.println(doc); System.out.println(); } clients.forEach(SearchClient::close); } private static SearchClient getClient(String desiredVersion) throws Exception { return null; // . } }
超自然的なことは何もありません。モジュールで使用されるElasticsearchのバージョンが表示され、それを介したテスト検索が実行されます-これで十分です。
実装の1つを見てみましょう。2つ目はほとんど同じです。
public class SearchClientImpl implements SearchClient { private final Settings settings = ImmutableSettings.builder() .put("cluster.name", "es1") .put("node.name", "es1") .build(); private final Client searchClient = new TransportClient(settings) .addTransportAddress(getAddress()); private InetSocketTransportAddress getAddress() { return new InetSocketTransportAddress("127.0.0.1", 9301); } @Override public String getVersion() { return Version.CURRENT.number(); } @Override public Map search(String term) { SearchResponse response = searchClient.prepareSearch("*") .setQuery(QueryBuilders.termQuery("field", term)) .execute() .actionGet(); if (response.getHits().getTotalHits() > 0) { return response.getHits().getAt(0).getSource(); } else { return null; } } @Override public void close() { searchClient.close(); } }
すべてが簡単です。現在のバージョンは、Elasticsearchで配線され、すべてのインデックス( *
)のフィールドfield
で検索され、最初に見つかったドキュメントがあればそれを返します。
ここでの問題は、 Searcher#getClient
でSearchClient
インターフェイスのSearchClient
を呼び出して、目的の結果を取得するSearcher#getClient
です。
たぶんClass.forName?
あなたがJavaの専門家でなくても、 ClassLoaderのルールを聞いたことがあるはずです。 デフォルトのままにすると、計画を完了できなくなります。そのため、このようなソリューションは機能しません。
private static SearchClient getClient(String desiredVersion) throws Exception { String className = String.format("elasticsearch.client.v%s.SearchClientImpl", desiredVersion); return (SearchClient) Class.forName(className).newInstance(); }
収集し、実行して結果を確認します...たとえば、次のように、かなり不確かです。
Exception in thread "main" java.lang.IncompatibleClassChangeError: Implementing class at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:348) at elasticsearch.client.Searcher.getClient(Searcher.java:28) at elasticsearch.client.Searcher.main(Searcher.java:10)
ClassNotFoundException
...または何か他のものをスローする可能性がありますが...
URLClassLoader
は、指定されたjarファイルとディレクトリのセットから指定された名前を持つ最初のクラスを見つけてロードするため、これは必須のクラスではありません。 この場合、 elasticsearch-2.4.5.jar
がクラスパスリスト内のelasticsearch-1.7.5.jar
elasticsearch-2.4.5.jar
するため、このエラーが発生し、すべてのクラス(名前で一致する)が2.4.5でロードされます。 。 Searcherは最初にElasticsearch 1.7.5( getClient("1")
)のモジュールをロードしようとするため、URLClassLoaderは間違ったクラスをロードします...
クラスローダーのクラスが名前(およびファイル名)で交差している場合、この状態はjar hell(またはclass-path hell)と呼ばれます。
カスタムClassLoader
モジュールとその依存関係を異なるクラスローダーに分散する必要があることが明らかになります。 es-v *モジュールごとにURLClassLoaderを作成し、jarファイルを使用して各ディレクトリに指定します。
private static SearchClient getClient(String desiredVersion) throws Exception { String className = String.format("elasticsearch.client.v%s.SearchClientImpl", desiredVersion); Path moduleDependencies = Paths.get("modules", "es-v" + desiredVersion); URL[] jars = Files.list(moduleDependencies) .map(Path::toUri) .map(Searcher::toURL) .toArray(URL[]::new); ClassLoader classLoader = new URLClassLoader(jars); // parent = app's class loader return (SearchClient) classLoader.loadClass(className).newInstance(); }
すべてのモジュールを収集して適切なmodules/es-v*/
ディレクトリにコピーする必要があります。これには、 es-v1およびes-v2モジュールのmaven-dependency-plugin
を使用します。
プロジェクトを組み立てましょう:
mvn package
そして実行:
. 29, 2017 10:37:08 org.elasticsearch.plugins.PluginsService <init> INFO: [es1] loaded [], sites [] . 29, 2017 10:37:12 org.elasticsearch.plugins.PluginsService <init> INFO: [es2] modules [], plugins [], sites [] Client for version: 1.7.5 Found doc: {field=test 1} Client for version: 2.4.5 Found doc: {field=test 2}
ビンゴ!
Elasticsearch 1.7の再構築で後述するように、JvmInfoにパッチを適用しない場合。
非常に筋金入りのケースでは、 コアモジュールもElasticsearchライブラリのユーティリティメソッドを使用することを想定しています。 クラスのロード順序が原因で、現在のソリューションは機能しなくなります。
- findLoadedClass(String)を呼び出して、クラスがすでにロードされているかどうかを確認します。
- 親クラスローダーでloadClassメソッドを呼び出します。 親がnullの場合、代わりに仮想マシンに組み込まれているクラスローダーが使用されます。
- findClass(String)メソッドを呼び出して、クラスを見つけます。
つまり、この場合、 es-v *ではなく、 コアのElasticsearchクラスがロードされます。 ブート順序を詳しく見ると、回避策があります。手順2と3を逆にして、この順序に違反するクラスローダーを作成します。このようなローダーは、 es-v *モジュールだけでなく、 coreのクラスも読み込むことができます。
URLClassLoaderを作成して、ParentLastURLClassLoaderなどの名前を付けましょう。
public class ParentLastURLClassLoader extends URLClassLoader { ... }
そしてloadClass(String,boolean)
を再定義し、ClassLoaderからコードをコピーし、不要なものをすべて削除します。
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null) { try { if (getParent() != null) { c = getParent().loadClass(name); } } catch (ClassNotFoundException e) { } if (c == null) { c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }
getParent().loadClass(String)
、 findClass(String)
の呼び出しを入れ替えて、次を取得します。
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null) { try { c = findClass(name); } catch (ClassNotFoundException ignored) { } if (c == null) { c = getParent().loadClass(name); if(c == null) { throw new ClassNotFoundException(name); } } } if (resolve) { resolveClass(c); } return c; } }
アプリケーションはモジュールクラスを手動でロードするため、クラスがどこにも見つからない場合、ローダーはClassNotFoundException
をスローする必要があります。
ローダーが作成されたので、これを使用して、 getClient(String)
メソッドのURLClassLoader
を置き換えます。
ClassLoader classLoader = new URLClassLoader(jars);
ParentLastURLClassLoader
:
ClassLoader classLoader = new ParentLastClassLoader(jars);
アプリケーションを再度起動すると、同じ結果が表示されます。
. 29, 2017 10:42:41 org.elasticsearch.plugins.PluginsService <init> INFO: [es1] loaded [], sites [] . 29, 2017 10:42:44 org.elasticsearch.plugins.PluginsService <init> INFO: [es2] modules [], plugins [], sites [] Client for version: 1.7.5 Found doc: {field=test 1} Client for version: 2.4.5 Found doc: {field=test 2}
ServiceLoader API
Java 6では、 java.util.ServiceLoaderクラスが追加されました。これは、インターフェイス/抽象クラスによって実装をロードするメカニズムを提供します。 このクラスは問題を解決します:
private static SearchClient getClient(String desiredVersion) throws Exception { Path moduleDependencies = Paths.get("modules", "es-v" + desiredVersion); URL[] jars = Files.list(moduleDependencies) .map(Path::toUri) .map(Searcher::toURL) .toArray(URL[]::new); ServiceLoader<SearchClient> serviceLoader = ServiceLoader.load(SearchClient.class, new URLClassLoader(jars)); return serviceLoader.iterator().next(); }
すべてが非常に簡単です:
- モジュールディレクトリですべてのjarファイルを探します。
- それらをブートローダーに入れ、
- 作成されたローダーを使用して、インターフェースの少なくとも1つの実装をロードしようとしています。
ServiceLoaderがインターフェイスの実装を確認するには、 META-INF/services
ディレクトリにインターフェイスのフルネームでファイルを作成する必要があります。
+---es-v1/ | +---src/ | +---main/ | +---resources/ | +---META-INF/ | +---services/ | +---elasticsearch.client.spi.SearchClient +---es-v2/ +---src/ +---main/ +---resources/ +---META-INF/ +---services/ +---elasticsearch.client.spi.SearchClient
そして、各行にこのインターフェイスを実装するクラスの完全な名前を書きます。この場合、モジュールごとに1つのSearchClientImpl
です。
es-v1の場合 、ファイルに次の行があります。
elasticsearch.client.v1.SearchClientImpl
es-v2の場合 :
elasticsearch.client.v2.SearchClientImpl
また、 maven-dependency-plugin
を使用して、 es-v * modules/es-v*/
をmodules/es-v*/
にコピーしmodules/es-v*/
。 プロジェクトをリビルドします。
mvn clean package
そして実行:
. 29, 2017 10:50:17 org.elasticsearch.plugins.PluginsService <init> INFO: [es1] loaded [], sites [] . 29, 2017 10:50:20 org.elasticsearch.plugins.PluginsService <init> INFO: [es2] modules [], plugins [], sites [] Client for version: 1.7.5 Found doc: {field=test 1} Client for version: 2.4.5 Found doc: {field=test 2}
すばらしい、望ましい結果が再び得られます。
前述のハードコアの場合、 SearchClient
インターフェイスを別のspiモジュールにSearchClient
、次のローダーチェーンを使用する必要があります。
core: bootstrap -> system spi: bootstrap -> spi es-v1: bootstrap -> spi -> es-v1 es-v2: bootstrap -> spi -> es-v2
つまり
- spi用に別のブートローダーを作成します(親ではnullをスローします-ブートストラップブートローダーが使用されます)。
- spi (
SearchClient
インターフェース)でロードし、 - 次に、 es-v *モジュールごとにローダーを作成します。これには、 spiローダーが親として含まれます
- ...
- 利益!
OSGiモジュール
私はすぐに正直に告白します-私はOSGiフレームワークに出会ったことがありません(それは良いことですか?)。 Apache FelixとEclipse Equinoxの初心者マナを見ると、これらは(手動で)バンドルがロードされるコンテナに似ています。 埋め込みの実装があったとしても、これは単純なアプリケーションにとっては面倒です。 私が間違っている場合は、反対の視点をコメントで表現します(実際、誰かがそれをどのように使用しているかを見たいです) 。
私はこの問題に深く入りませんでした、なぜなら Java 9では、モジュールはすぐに使用できるようになりました。これについて説明します。
Javaのネイティブモジュール?
先週、プラットフォームの9番目のバージョンがリリースされました。主な革新は、ランタイムとプラットフォーム自体のソースコードのモジュール化でした。 まさに必要なものです!
ヒント:モジュールを使用するには、 JDK 9をまだダウンロードしていない場合は、まずダウンロードしてインストールする必要があります 。
ここで唯一複雑なのは、9の下でモジュールとして実行するために使用されるライブラリの機能です(実際、IntelliJ IDEAでmodule-pathとともにclass-pathを指定する方法が見つからなかったため、module-pathのコンテキストですべてを実行します)。
モジュラーシステムの仕組み
モジュラーシステム用にアプリケーションのコードを変更する前に、まずそれがどのように機能するかを見つけます(私自身はこの問題を理解し始めたばかりなので、間違っているかもしれません)。
上記のモジュールに加えて、それらを含むレイヤーもあります。 アプリケーションがboot
と、アプリケーションを構成する--module-path
で指定されたすべてのモジュールがロードされるboot
層が作成され、それらの依存関係(すべてのモジュールは自動的にjava.base
モジュールに依存します)。 他のレイヤーはプログラムで作成できます。
各層には独自のクラスローダー(または階層)があります。 ブートローダーと同様に、モジュラーレイヤーも階層的に構築できます。 このような階層では、ある層のモジュールは、親層にある他のモジュールを見ることができます。
モジュール自体は互いに分離されており、デフォルトでは、モジュール内のパッケージとクラスは他のモジュールからは見えません。 モジュール記述子( module-info.java
)を使用すると、各モジュールが開くことができるパッケージと、依存するモジュールとそのパッケージを指定できます。 さらに、モジュールは、作業中に特定のインターフェイスを使用していることを通知し、これらのインターフェイスの利用可能な実装を通知する場合があります。 この情報は、モジュールからインターフェイス実装をロードするためにServiceLoader APIによって使用されます。
モジュールは明示的かつ自動です(さらに多くのタイプがありますが、これらに限定します)。
- 明示的なモジュールは、jar-archia(モジュールまたはモジュール化されたjar)のルートにあるファイル記述子
module-info.class
によって明示的に記述されます。 - 自動モジュールは、モジュールパスに配置された記述子のないライブラリです。 このタイプのモジュールとして、古き良き
class-path
使用されている既存のモジュール化されていないライブラリを使用することになっていclass-path
。
これで、この情報をプロジェクトに適用するのに十分になります。
- ブートレイヤーには、 コアモジュールのみがあります。 es-v *モジュールが含まれる場合、
elasticsearch.shaded
推移的モジュールが競合するため、アプリケーションは起動しません。 -
Searcher
クラスは、ServiceLoader APIを使用して、クラスローダーでes-v *モジュールを個別の子レイヤーに手動でロードします。
それは簡単ですか?...
パッケージ地獄
モジュールは(少なくとも1つのレイヤーで)パッケージ名を重複させることはできません。
たとえば、パブリッククラスFunctions
ある種のAPIを提供する特定のライブラリがあります。 このライブラリには、バッチスコープを持つHelpers
クラスがあります。 ここにあります:
com.foo.bar public class Functions class Helpers
2番目のライブラリがシーンに入り、最初のライブラリの機能を補完します。
com.foo.baz public class Additional
そして、彼女は閉じたHelpers
クラスの機能を必要とします。 同じパッケージにいくつかのクラスを配置することで、状況から抜け出します。
com.foo.baz public class Additional com.foo.bar public class AccessorToHelpers
おめでとうございます-モジュラーシステムの観点から分割パッケージの問題を作成しました。 そのようなライブラリで何ができますか? このようなライブラリをクラスパスに残し 、自動モジュールをブリッジとして使用して、モジュールからそれらに到達することが提案されています。 しかし、簡単な方法を探しているわけではないので、別のオプションを使用します。すべての依存関係をライブラリに報告し、単一のjarアーカイブ(ファットjarおよびuber jarとして知られています)を取得し、モジュールパスで自動モジュールとして使用できます。クラスパスをバイパスします。 問題は、このようなオールインワンjarを作成することです。
Elasticsearchは、パッケージプライベートメソッド/フィールドへのアクセスを積極的に使用して、一部のLucene機能にアクセスします。 それを自動モジュールとして使用するには、それをuber jarにして、プロジェクトでさらに使用するためにelasticsaerch-shaded
という名前でローカルリポジトリにインストールします。
Elasticsearch 1.7をビルドする
最初のバージョンでは、アプリケーションプロジェクトが唯一のMavenモジュールであるため、ここで問題は発生しませんpom.xml
番目のモジュールをpom.xml
場合、 pom.xml
といくつかのクラスを修正する必要があります。
リポジトリをあるディレクトリに複製し、v1.7.5タグをチェックアウトしてv1.7.5
を開始しv1.7.5
。
- このプロジェクトはすでに
maven-shade-plugin
使用しているため、uberjarをビルドするには、いくつかのパッケージが含まれていることをコメントアウトして、すべてが含まれるようにする必要があります。
<!-- <includes> <include>com.google.guava:guava</include> <include>com.carrotsearch:hppc</include> <include>com.fasterxml.jackson.core:jackson-core</include> ... </includes> -->
できれば元の、動きのないもの:
<!-- <relocations> <relocation> <pattern>com.google.common</pattern> <shadedPattern>org.elasticsearch.common</shadedPattern> </relocation> <relocation> <pattern>com.carrotsearch.hppc</pattern> <shadedPattern>org.elasticsearch.common.hppc</shadedPattern> </relocation> ... </relocations> -->
- コメントアウトされた
<includes>
ノードの直後に<excludes>
追加することにより、Groovy( このようなあいまいさのために負荷を中断します)とロガー(それらの設定はありません。JULはデフォルトで正常に動作します)を削除する必要があります。
<excludes> <exclude>org.codehaus.groovy:groovy-all</exclude> <exclude>org.slf4j:*</exclude> <exclude>log4j:*</exclude> </excludes>
- 未使用クラスの切断をオフにします-プラグインはServiceLoader / Reflection APIを認識しません:
<!--<minimizeJar>true</minimizeJar>-->
- そして、ServiceLoader APIの実装クラスを持つサービスファイルの接着をプラグインの
<configuration>
ノードに追加します。
<transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers>
-
pom.xml
完成したため、java.lang.management.RuntimeMXBean#getBootClassPath
をスローするUnsupportedOperationException
を排除するために残っていjava.lang.management.RuntimeMXBean#getBootClassPath
。 これを行うには、JvmInfo
クラスで次の行を見つけます。
info.bootClassPath = runtimeMXBean.getBootClassPath();
それを「正しいもの」に守る:
if (runtimeMXBean.isBootClassPathSupported()) { info.bootClassPath = runtimeMXBean.getBootClassPath(); } else { info.bootClassPath = ""; }
この情報は統計にのみ使用されます。
完了、jarをビルドできるようになりました:
$ mvn package
コンパイルとアセンブリの後、必要なelasticsearch-1.7.5.jar
をtarget
ディレクトリにelasticsearch-1.7.5.jar
します。 次に、たとえばelasticsearch-shaded
という名前で、ローカルリポジトリにインストールする必要があります。
$ mvn install:install-file \ > -Dfile=elasticsearch-1.7.5.jar \ > -DgroupId=org.elasticsearch \ > -DartifactId=elasticsearch-shaded \ > -Dversion=1.7.5 \ > -Dpackaging=jar \ > -DgeneratePom=true
これで、このアーティファクトをes-v1 Mavenモジュールの自動モジュールとして使用できます。
<dependencies> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch-shaded</artifactId> <version>1.7.5</version> </dependency> ... </dependencies>
Elasticsearch 2.4をビルドする
ローカルの変更をロールバックし、 v2.4.5
タグにタグを付けます。 2番目のバージョンから、プロジェクトはモジュールに分割されます。 elasticsearch-2.4.5.jar
発行するために必要なモジュールがコアモジュールです。
- 最初にスナップショットを削除します。リリースが必要です。
$ mvn versions:set -DnewVersion=2.4.5
- ここで、シェードプラグインがどこで設定されているかを見てみましょう...そして、そのようなドックに遭遇します:
シェーディングとパッケージの再配置が削除されました
Elasticsearchは、依存関係をシェーディングし、パッケージを再配置するために使用されていました。 シェーディングや再配置は使用しなくなりました。
インポートを元のパッケージ名に変更する必要がある場合があります。
com.google.common
はorg.elasticsearch.common
com.carrotsearch.hppc
はorg.elasticsearch.common.hppc
jsr166e
はorg.elasticsearch.common.util.concurrent.jsr166e
...
コアモジュールに再度シェードプラグインを追加し、サービスファイルのトランスフォーマーとロガーの除外を設定に追加する必要があります。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> <artifactSet> <excludes> <exclude>org.slf4j:*</exclude> <exclude>log4j:*</exclude> </excludes> </artifactSet> </configuration> </plugin>
- コアモジュールの
com.twitter:jsr166e
(9-keには存在しないsun.misc.Unsafe
使用)を削除します。
<!-- <dependency> <groupId>com.twitter</groupId> <artifactId>jsr166e</artifactId> <version>1.1.0</version> </dependency> -->
インポートcom.twitter.jsr166e
をjava.util.concurrent.atomic
変更します。
-
animal-sniffer-maven-plugin
前のステップの変更animal-sniffer-maven-plugin
検出し(7-keにはjsr166eはありません)、削除します:
<!-- <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>animal-sniffer-maven-plugin</artifactId> </plugin> -->
完了、 es-v1と同じアセンブリおよびインストール手順を実行していますが、わずかな違いがあります。
- バージョンを2.4.5に変更し、
- コアモジュールをビルドするだけです。
$ mvn clean package -pl org.elasticsearch:parent,org.elasticsearch:elasticsearch -DskipTests=true
私たちのプロジェクトのモジュール
使用するライブラリがモジュラーJavaシステムで機能することがわかった後、Mavenモジュールから明示的なJavaモジュールを作成します。 これを行うには、ソース( src/main/java
)を使用して各ディレクトリにmodule-info.java
ファイルを作成し、それらのモジュール間の関係を記述する必要があります。
コアモジュールは他のモジュールに依存せず、他のモジュールが実装する必要があるインターフェイスのみを含むため、説明は次のようになります。
// - elasticsearch.client.core module elasticsearch.client.core { // , , // exports elasticsearch.client.spi; // , SearchClient // ServiceLoader' uses elasticsearch.client.spi.SearchClient; }
es-v1およびes-v2モジュールについても同様の説明があります。
- 彼らは
elasticsearch.client.core
モジュールを使用します、なぜなら インターフェースがあります - それぞれが必要なバージョンのElasticsearch自動モジュールを使用し、
- 各モジュールは、
SearchClient
インターフェースの実装を提供することをSearchClient
ます。
es-v1の合計:
// - elasticsearch.client.v1 module elasticsearch.client.v1 { // core SearchClient requires elasticsearch.client.core; // requires elasticsearch.shaded; // core, provides elasticsearch.client.spi.SearchClient with elasticsearch.client.v1.SearchClientImpl; }
es-v2の場合、ほとんどすべてが同じであり、バージョンv2
のみが名前に表示されます。
次に、そのようなモジュールをロードする方法は? この質問に対する答えは、FSからモジュールをロードする小さな例を含むModuleLayerクラスの説明にあります。 すべてのes-v *モジュールが同じディレクトリmodules/es-v*/
にあると仮定すると、次のように書くことができます。
private static SearchClient getClient(String desiredVersion) throws Exception { Path modPath = Paths.get("modules", "es-v" + desiredVersion); ModuleFinder moduleFinder = ModuleFinder.of(modPath); ModuleLayer parent = ModuleLayer.boot(); Configuration config = parent.configuration().resolve(moduleFinder, ModuleFinder.of(), Set.of("elasticsearch.client.v" + desiredVersion)); ModuleLayer moduleLayer = parent.defineModulesWithOneLoader(config, Thread.currentThread().getContextClassLoader()); ServiceLoader<SearchClient> serviceLoader = ServiceLoader.load(moduleLayer, SearchClient.class); Optional<SearchClient> searchClient = serviceLoader.findFirst(); if (searchClient.isPresent()) { return searchClient.get(); } throw new Exception("Module 'elasticsearch.client.v" + desiredVersion + "' not found on " + modPath); }
ModuleLayer#defineModulesWithManyLoaders , es-v* .
, . maven-compiler-plugin
, — 3.7.0:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> </plugin>
Java 9 :
<properties> <maven.compiler.source>1.9</maven.compiler.source> <maven.compiler.target>1.9</maven.compiler.target> </properties>
maven-dependency-plugin
, :
$ mvn clean package
, :
. 29, 2017 10:59:01 org.elasticsearch.plugins.PluginsService <init> INFO: [es1] loaded [], sites [] . 29, 2017 10:59:04 org.elasticsearch.plugins.PluginsService <init> INFO: [es2] modules [], plugins [], sites [] Client for version: 1.7.5 Found doc: {field=test 1} Client for version: 2.4.5 Found doc: {field=test 2}
, — , , Java 8 .
終わり
, /. :
PS Java 9!