2017年に1つのライブラリの複数のバージョンでJavaアプリケーションを実行する方法

2017年に1つのライブラリの複数のバージョンでJavaアプリケーションを実行する方法



KDPV、何も面白い







私が直面しなければならなかった1つの問題の解決策と、Java 9のコンテキストでのこの問題の調査を共有したいと思います。







免責事項

私の作家はまだです(私は初めて書いています)ので、投げます おいしい 理由を示すトマトは大歓迎です。

記事が以下のガイドとして適切ではないことに直ちに同意します。







  • Java 9
  • Elasticsearch
  • メイヴン


ネットワーク内の情報の姓がいっぱいの場合、最初に...が表示されます 。少なくとも必要な情報があります。







単純な状況を想像してください。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つのモジュールで同じライブラリの複数のバージョンを接続しても機能しないため、プロジェクトはマルチモジュールである必要があります。









コアモジュールには、 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}
      
      





ビンゴ!







(1.7はJRE 9では機能しません)

Elasticsearch 1.7の再構築で後述するように、JvmInfoにパッチを適用しない場合。







非常に筋金入りのケースでは、 コアモジュールもElasticsearchライブラリのユーティリティメソッドを使用することを想定しています。 クラスのロード順序が原因で、現在のソリューションは機能しなくなります。







  1. findLoadedClass(String)を呼び出して、クラスがすでにロードされているかどうかを確認します。
  2. 親クラスローダーでloadClassメソッドを呼び出します。 親がnullの場合、代わりに仮想マシンに組み込まれているクラスローダーが使用されます。
  3. 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(); }
      
      





すべてが非常に簡単です:









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
      
      





つまり







  1. spi用に別のブートローダーを作成します(親ではnullをスローします-ブートストラップブートローダーが使用されます)。
  2. spiSearchClient



    インターフェース)でロードし、
  3. 次に、 es-v *モジュールごとにローダーを作成します。これには、 spiローダーが親として含まれます
  4. ...
  5. 利益!


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によって使用されます。







モジュールは明示的かつ自動です(さらに多くのタイプがありますが、これらに限定します)。









これで、この情報をプロジェクトに適用するのに十分になります。







  1. ブートレイヤーには、 コアモジュールのみがあります。 es-v *モジュールが含まれる場合、 elasticsearch.shaded



    推移的モジュールが競合するため、アプリケーションは起動しません。
  2. 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の再組み立て手順

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モジュールについても同様の説明があります。









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!








All Articles