前回の続き。
もし、どうしてもモジュールアプリケーションにしたいという場合を考えてみます。
普通のアプリケーションであれば、そんなに急いでモジュール化する必要は私はないと思っています。ただし、ライブラリを開発している場合は、なるべく早くモジュールに対応してほしいです。
前回のサンプルはライブラリではないのですが、モジュール化する場合もあるでしょうし、とりあえずやってみましょう。
前回のサンプルはJersey + Grizzly の単純なアプリケーションでした。
このサンプルは 2 つのクラスから構成されています。リソースを表すMyResouceクラスと、アプリケーションの起動クラスとなるMainクラスです。いずれもcom.exampleパッケージにあります。
MyResourceクラスはこちら。
package com.example; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; /** * Root resource (exposed at "myresource" path) */ @Path("myresource") public class MyResource { /** * Method handling HTTP GET requests. The returned object will be sent * to the client as "text/plain" media type. * * @return String that will be returned as a text/plain response. */ @GET @Produces(MediaType.TEXT_PLAIN) public String getIt() { return "Got it!"; } }
Mainクラスはこちらです。
package com.example; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import java.io.IOException; import java.net.URI; /** * Main class. * */ public class Main { // Base URI the Grizzly HTTP server will listen on public static final String BASE_URI = "http://localhost:8080/myapp/"; /** * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application. * @return Grizzly HTTP server. */ public static HttpServer startServer() { // create a resource config that scans for JAX-RS resources and providers // in com.example package final ResourceConfig rc = new ResourceConfig().packages("com.example"); // create and start a new instance of grizzly http server // exposing the Jersey application at BASE_URI return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc); } /** * Main method. * @param args * @throws IOException */ public static void main(String[] args) throws IOException { final HttpServer server = startServer(); System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadl\nHit enter to stop it...", BASE_URI)); System.in.read(); server.stop(); } }
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.example</groupId> <artifactId>simple-service</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>simple-service</name> <dependencyManagement> <dependencies> <dependency> <groupId>org.glassfish.jersey</groupId> <artifactId>jersey-bom</artifactId> <version>${jersey.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-grizzly2-http</artifactId> </dependency> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0.1</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>javax.activation-api</artifactId> <version>1.2.0</version> </dependency> <!-- uncomment this to get JSON support: <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-binding</artifactId> </dependency> --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <inherited>true</inherited> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.example.Main</mainClass> </configuration> </plugin> </plugins> </build> <properties> <jersey.version>2.27</jersey.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>
依存性を調べる
アプリケーションをモジュール化する場合、必須なのがモジュールを定義するmodule-info.javaの作成です。
基本的には、module-info.javaには使用するモジュールと、公開するパッケージを記述します。
しかし、どのモジュールを使っているかなんて、よく分からないですよね。そういう時に使用するのが、jdepsコマンドです。
では、さっそくjdepsコマンドを使ってみましょう。
jdepsはJARファイルもしくはクラスファイルを引数にして実行します。サンプルのJARファイルはMavenではtargetディレクトリに作成されるので、そこで実行させてみました。
C:\jersey-sample\simple-service\target>jdeps -s simple-service-1.0-SNAPSHOT.jar simple-service-1.0-SNAPSHOT.jar -> java.base simple-service-1.0-SNAPSHOT.jar -> 見つかりません C:\jersey-sample\simple-service\target>
上記の例で使用している-sオプションはサマリー表示を行うためです。
この結果、java.baseモジュールを使っていることは分かります。java.baseモジュールはJavaの基本となるモジュールでjava.langパッケージなどが含まれています。
しかし、その他のモジュールは「見つかりません」と表示されてしまいます。
これはモジュールパスもしくはクラスパスが設定されていないためです。
しかし、Mavenを使っていると、他に使用しているライブラリはローカルレポジトリにあるため、クラスパスを設定するにもちょっとめんどうです。そこで、MavenのDependency Pluginを使用して、使用しているJARファイルを一か所にコピーしてしまいましょう。
pom.xmlの<plugins>要素に以下の記述を付けくわえます。
<plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <phase>install</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <includeScope>runtime</includeScope> </configuration> </execution> </executions> </plugin>
これでtargetディレクトリにlibディレクトリを作成し、使用しているJARファイルをコピーします。
このlibディレクトリ配下のJARファイルをクラスパスに追加して、jdepsを実行してみます。
C:\jersey-sample\simple-service\target>mvn clean install [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:simple-service >--------------------- [INFO] Building simple-service 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- <<省略>> C:\jersey-sample\simple-service\target>cd target C:\jersey-sample\simple-service\target>jdeps -s -cp lib\* simple-service-1.0-SNAPSHOT.jar エラー: jaxb-api-2.3.0.jarはマルチリリースjarファイルですが--multi-releaseオプションが設定されていません C:\jersey-sample\simple-service\target>
ところが、今度はエラーが出てしまいました。
ここで、表示されているマルチリリースJARというのは、Java SE 9で導入された機能で、1つのJARファイルに複数のJavaのバージョンに対応したクラスファイルをまとめることができます。
jdepsはマルチリリースJARファイルがあると、--muliti-releaseオプションを設定する必要があるのですが、これがまた問題なのです。実際にやってみましょう。
C:\jersey-sample\simple-service\target>jdeps -s --multi-release 11 -cp lib\* simple-service-1.0-SNAPSHOT.jar エラー: simple-service-1.0-SNAPSHOT.jarはマルチリリースjarファイルではありませんが--multi-releaseオプションが設定されています C:\jersey-sample\simple-service\target>
ここから分かるのは--multi-releaseオプションを設定するには、すべてのJARファイルがマルチリリースJARになっていないといけないということです。しかし、それはほぼ無理なのです。
マルチリリースJARに対するjdepsの対応は間違っていると思うのですが、どうにかならないですかねぇ。
しかたないので、jaxb-api-2.3.0.jarをlibディレクトリから移動させて、再びjdepsを実行しました。
C:\jersey-sample\simple-service\target>move lib\jaxb-api-2.3.0.jar . 1 個のファイルを移動しました。 C:\jersey-sample\simple-service\target>jdeps -s -cp lib\* simple-service-1.0-SNAPSHOT.jar simple-service-1.0-SNAPSHOT.jar -> lib\grizzly-http-server-2.4.0.jar simple-service-1.0-SNAPSHOT.jar -> java.base simple-service-1.0-SNAPSHOT.jar -> lib\javax.ws.rs-api-2.1.jar simple-service-1.0-SNAPSHOT.jar -> lib\jersey-container-grizzly2-http-2.27.jar simple-service-1.0-SNAPSHOT.jar -> lib\jersey-server-2.27.jar C:\jersey-sample\simple-service\target>
やっと結果が表示されました。
java.baseモジュール以外に、4つのJARファイルを使用していることが分かります。実をいうと、これらの4つのJARファイルはモジュールにはなっていません。
しかし、モジュールアプリケーションが直接アクセスできるのは、モジュールだけです。こういう場合、通常のJARファイルをモジュールとして扱う自動モジュール( Automatic Module)という機能を使用します。
実際にそれを行うのは、もうちょっと後なので、ここではまずモジュールにすることを考えましょう。
module-info.javaの生成
java.baseモジュールはJavaアプリケーションには必ず必要なので、module-info.javaに記述しなくても大丈夫です。
なので、それ以外の4つのJARファイルをmodule-info.javaに記述します。でも、これはめんどうなので、jdepsコマンドにmodule-info.javaを作ってもらいましょう。
そのために使用するのが、--generate-module-infoオプションです。
ただし、--generate-module-infoオプションではクラスパスは使用できません。その理由は、先ほどと同じでモジュールはモジュールしかアクセスできないためです。なので、クラスパスではなく、モジュールパスを設定します。ここではlibディレクトリをモジュールパスとして設定します。自動モジュールの機能があるので、モジュールでないJARファイルであっても、これで大丈夫です。
でも、1つ問題があります。libディレクトリには2つのバージョンのjavax.injectが含まれているはずです。Dependency Pluginはpom.xmlの依存性を見ているだけなので、バージョン違いのJARファイルが含まれることがあるのはしかたありません。
しかし、現状のモジュールシステムはモジュールのバージョンを直接あつかうことができません(バージョンをつけることはできます)。そのため、モジュールパスに複数のバージョンのJARファイルを置くことはできないのです。
しかたないので、libディレクトリのjavax.injectの古い方を削除してから、jdepsを実行します。
下の例では複数のバージョンがあるため、jdepsがエラーを出しています。javax.injectを削除すれば、実行できます。
C:\jersey-sample\simple-service\target>jdeps --generate-module-info . --module-path lib simple-service-1.0-SNAPSHOT.jar Exception in thread "main" java.lang.module.FindException: Two versions of module javax.inject found in lib (javax.inject-2.5.0-b42.jar and javax.inject-1.jar) at java.base/jdk.internal.module.ModulePath.scanDirectory(ModulePath.java:293) at java.base/jdk.internal.module.ModulePath.scan(ModulePath.java:231) at java.base/jdk.internal.module.ModulePath.scanNextEntry(ModulePath.java:189) at java.base/jdk.internal.module.ModulePath.findAll(ModulePath.java:165) at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:544) at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:589) at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:543) at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:519) at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:49) C:\jersey-sample\simple-service\target>del lib\javax.inject-1.jar C:\jersey-sample\simple-service\target>jdeps --generate-module-info . --module-path lib simple-service-1.0-SNAPSHOT.jar writing to .\simple.service\module-info.java C:\jersey-sample\simple-service\target>
なお、--generate-module-infoの引数はmodule-info.javaを出力するディレクトリです。ここではカレントディレクトリを指定しています。
jdeps指定したディレクトリにJARファイルに対応したディレクトリを作成して、そこにmodule-info.javaを生成します。ここではsimple-service-1.0-SNAPSHOT.javaファイルなので、バージョンを取り除いて、ハイフンをピリオドに置き換えたsimple.serviceをディレクトリ名にしています。
では、作成されたmodule-info.javaを見てみましょう。
module simple.service { requires jersey.container.grizzly2.http; requires jersey.server; requires transitive grizzly.http.server; requires transitive java.ws.rs; exports com.example; }
はじめの行のmoduleキーワードの後に記述してあるのがモジュール名です。
生成したモジュール名は、jdepsが作成したディレクトリ名と同じです。つまり、JARファイル名から作成したことが分かります。これは自動モジュールのモジュール名を決めるときと同じルールで行っています。
通常、モジュール名はパッケージ名と同様に、ドメインの逆順を使用して決めます。ここではサンプルなので、このままにしておきましょう。
requires文にモジュール名を指定します。
先ほどの4つのJARファイルに対応するのが、この4行のrequires文です。
ここでも、JARファイル名からバージョン名を取り除いて、ハイフンをピリオドに置き換えたものがモジュール名として使用されています。ところが、java.ws.rsだけはJARファイル名とは異なります。
これは、JARファイルのMANIFEST.MFファイルの中にモジュール名を指定しているためです。
javax.ws.rs-api-2.1.jarのMANIFEST.MFファイルは以下のようになっています。
Manifest-Version: 1.0 Automatic-Module-Name: java.ws.rs Bnd-LastModified: 1501859871759 Build-Id: 08/04/2017 05:17 PM <<以下、省略>>
この中の、Automatic-Module-Nameで指定しているjava.ws.rsがモジュール名になります。
こんなのを1つ1つ調べていくのは大変なので、jdepsコマンドで作ってもらうのが手っ取り早いですね。
さて、module-info.javaのrequires文には、transitiveがついているもののと、ついていないものがあります。
transitiveはこのモジュールが使用しているモジュールであり、かつこのモジュールがそれを外部に対して公開しているモジュールです。
たとえば、grizzly.http.serverがtransitiveになっているのはMainクラスのstartServerメソッドの戻り値の方がorg.glassfish.grizzly.http.server.HttpServerクラスだからです。
jdepsコマンドはこのようにクラスの内部までチェックしてtransitiveかどうかを決めています。
ところが、startServerメソッドはmainメソッドで使われるだけなので、実際にはtransitiveである必要はありません。
まぁ、jdepsもそこまでは調べてくれないということですね。
module-info.javaの最後のexports文はsimple.serviceモジュールで公開するパッケージを指定します。このサンプルは1つのパッケージしか使用していないので、それが表示されているだけです。
しかし、複数のパッケージがあれば、それがずらずらとexports文で記述されてしまうので、ほんとうに公開したいパッケージだけを残して、あとは削除しておきましょう。
さて、生成されたmodule-info.javaは、src/main/javaディレクトリにコピーしておきましょう。つまり、module-info.javaはデフォルトパッケージの位置に配置します。
これで、simple-serviceサンプルはモジュールアプリケーションになります。
では、コンパイルしてみましょう。
C:\jersey-sample\simple-service>mvn clean compile [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:simple-service >--------------------- [INFO] Building simple-service 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ simple-service --- [INFO] Deleting C:\Users\yuichi\Desktop\jersey\simple-service\target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ simple-service --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory C:\Users\yuichi\Desktop\jersey\simple-service\src\main\resources [INFO] [INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ simple-service --- [WARNING] ******************************************************************************************************************** [WARNING] * Required filename-based automodules detected. Please don't publish this project to a public artifact repository! * [WARNING] ******************************************************************************************************************** [INFO] Changes detected - recompiling the module! [INFO] Compiling 3 source files to C:\Users\yuichi\Desktop\jersey\simple-service\target\classes [INFO] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java: C:\Users\yuichi\Desktop\jersey\simple-service\src\main\java\com\example\Main.javaは推奨されないAPIを使用またはオーバーライドしています。 [INFO] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java: 詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。 [INFO] ------------------------------------------------------------- [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java:[25,55] org.glassfish.jersey.ExtendedConfigにアクセスできません org.glassfish.jersey.ExtendedConfigのクラス・ファイルが見つかりません [INFO] 1 error [INFO] ------------------------------------------------------------- [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.897 s [INFO] Finished at: 2018-07-21T21:45:47+09:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile (default-compile) on project simple-service: Compilation failure [ERROR] /C:/Users/yuichi/Desktop/jersey/simple-service/src/main/java/com/example/Main.java:[25,55] org.glassfish.jersey.ExtendedConfigにアクセスできません [ERROR] org.glassfish.jersey.ExtendedConfigのクラス・ファイルが見つかりません [ERROR] [ERROR] -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException C:\jersey-sample\simple-service>
クラスが見つからないと、エラーになってしまいました。
ここで見つからないといわれているorg.glassfish.jersey.ExtendedConfigインタフェースは、Mainクラスで使用してるorg.glassfish.jersey.server.ResourceConfigクラスが実装しているインタフェースです。
そして、ExtendedConfigインタフェースはjersey-server-2.27.jarファイルではなく、jersey-common-2.27.jarに含まれているのです。
このことから分かるように、jdepsの--generate-module-infoオプションで作成されるmodule-info.javaは完璧なものではありません。
あくまでも、ひな型としてあつかい、必要に応じて編集しなくてはなりません。
特にリフレクションに関しては、jdepsは無力です。リフレクションを使用している場合は、自分で依存性を記述してください。
ここでは、jersey.commonのrequires文を追加します。
module simple.service { requires jersey.container.grizzly2.http; requires jersey.server; requires jersey.common; requires transitive grizzly.http.server; requires transitive java.ws.rs; exports com.example; }
これでコンパイルはできるはずです。
ただし、テストは通りません。これはCompiler Pluginのバグで3.7.1で直るということなのですが、テストできないのは困ります。
これについては、ワークアラウンドがあって、pom.xmlのコンパイルのオプションで<source>と<target>を共に9にすれば、テストが通ります。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <inherited>true</inherited> <configuration> <source>9</source> <target>9</target> </configuration> </plugin>
とはいうものの、せっかくJava 11を使っているのに、9にするのは何か負けているような気がして、個人的にはイヤw
実行
コンパイルできたので、実行してみましょう。
C:\jersey-sample\simple-service>mvn exec:java [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:simple-service >--------------------- [INFO] Building simple-service 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service --- 7月 22, 2018 11:46:46 午前 org.glassfish.grizzly.http.server.NetworkListener start 情報: Started listener bound to [localhost:8080] 7月 22, 2018 11:46:46 午前 org.glassfish.grizzly.http.server.HttpServer start 情報: [HttpServer] Started. Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl Hit enter to stop it...
あっさりと実行できてしまいました。http://localhost:8080/myapp/myresource にアクセスすれば、"Got it!" と表示されるはずです。
ところで、このサンプルで実行のために使用しているExec Maven Pluginでは、ゴールとしてexec:javaとexec:execが定義されています。
exec:javaがMavenと同じVMで実行され、exec:execは異なるVMで実行されるという違いがあります。
どうやら、現状のExec Maven Pluginのexec:javaの場合、モジュールであってもモジュールのロードではなく、単なるクラスロードが使われているようです。このため、特に設定をしなくても実行ができてしまったのです。
しかし、これではモジュールとしての動作の確認ができないので、exec:execで実行できるようにしてみます。
そのためには、pom.xmlの編集が必要です。
現在のpom.xmlのExec Maven Pluginの設定は次のようになっています。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.example.Main</mainClass> </configuration> </plugin>
これを次のように変更します。
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <executions> <execution> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <executable>java</executable> <arguments> <argument>-p</argument> <modulepath /> <argument>-cp</argument> <classpath /> <argument>-m</argument> <argument>simple.service/com.example.Main</argument> </arguments> </configuration> </plugin>
変更したのは<configuration>要素です。
残念ながら、現バージョンのExec Maven Pluginではモジュールを使用するには、javaコマンドの引数を<argument>要素で指定する必要があるのです。
-pオプションは、--module-pathオプションの省略形で、モジュールパスを指定するオプションです。
ただし、クラスパスと同様に、モジュールパスでも<modulepath />要素を使用すれば、実際にモジュールパスを指定する必要はありません。これだけでも、ちょっと楽ができる感じですね。
メインクラスを指定するのは-mオプションです。-mオプションは--moduleオプションの省略形で、モジュールをロードする基点となるモジュールとメインクラスを指定します。
先ほどは、exec:javaで実行しましたが、今回はexec:execです。
C:\jersey-sample\simple-service>mvn exec:exec [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:simple-service >--------------------- [INFO] Building simple-service 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ simple-service --- 7月 22, 2018 13:23:06 午前 org.glassfish.grizzly.http.server.NetworkListener start 情報: Started listener bound to [localhost:8080] 7月 22, 2018 13:23:06 午前 org.glassfish.grizzly.http.server.HttpServer start 情報: [HttpServer] Started. Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl Hit enter to stop it...
同じように実行することができたはずです。
単体で実行
ここまではMavenを使ってコンパイル、実行を行ってきました。
しかし、アプリケーションを配布することを考えると、ずっとMavenに頼っているわけにはいきません。
というわけで、単体で実行できるようにしてみましょう。
まずは、Mavenでinstallまで行って、サンプルのJARファイルを作成しておきます。
C:\jersey-sample\simple-service>mvn clean install -Dmaven.test.skip=true [INFO] Scanning for projects... [INFO] [INFO] ---------------------< com.example:simple-service >--------------------- [INFO] Building simple-service 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] <<以下、省略>>
前述したように、sourceとtargetが11のままだとテストが通りません。そこで、ここではテストをスキップさせています。
これで、targetディレクトリにsimple-service-1.0-SNAPSHOT.jarファイルができているはずです。
また、Maven Dependency Pluginによってlibディレクトリには依存性のあるJARファイルがコピーされています。
ここまでモジュールと自動モジュール、モジュールではないJARファイルの3種類が出てきていましたが、それを一度整理しましょう。
- モジュール: module-info.javaでモジュール名、依存性、公開範囲が定義されたモジュール
- 自動モジュール (Automatic Module): モジュールの定義はないが、自動的にモジュールに格上げされたJARファイル
- 無名モジュール (Unnamed Module): モジュールではないJARファイル
自動モジュールは自動的に格上げされたため、すべてのパッケージが外部に公開されています。自動モジュールのモジュール名は前述したようにMANIFEST.MFのAutomatic-Module-Nameで定義された名前が使用されます。Automatic-Module-Nameがない場合はJARファイル名から自動的に作られます。
モジュールと自動モジュールはモジュールパスで指定したディレクトリに配置します。
無名モジュールは、モジュールという名前がついていますが、単なるJARファイルです。もちろん、すべてのパッケージが公開されています。無名モジュールは、今まで同様にクラスパスで指定します。
ここで、重要なのは通常のモジュールはモジュールもしくは自動モジュールにしかアクセスできません。アクセスできるのは、module-info.javaのrequires文に記述したモジュール(自動モジュールを含む)だけです。
つまり、モジュールは無名モジュールにはアクセスできないのです。無名モジュールにアクセスできるのは、自動モジュールだけです。
ただし、例外もあります。これについては、別エントリーで触れたいと思います。
さて、ここではルートとなるモジュールがsimple-service-1.0-SNAPSHOT.jarファイルで、このモジュールは前述したように5つの自動モジュールに依存しています。
つまり、この6個のJARファイルをモジュールパスで指定するディレクトリに配置します。そして、それ以外のJARファイルをクラスパスで指定します。
ここでは、modsディレクトリを作成して、そこに6個のJARファイルを移動させてみます。
C:\jersey-sample\simple-service\target>dir mods /B grizzly-http-server-2.4.0.jar javax.ws.rs-api-2.1.jar jersey-common-2.27.jar jersey-container-grizzly2-http-2.27.jar jersey-server-2.27.jar simple-service-1.0-SNAPSHOT.jar C:\jersey-sample\simple-service\target>
では、実行してみましょう。
C:\jersey-sample\simple-service\target>java -p mods -cp lib\* -m simple.service/com.example.Main 7月 22, 2018 8:57:34 午後 org.glassfish.grizzly.http.server.NetworkListener start 情報: Started listener bound to [localhost:8080] 7月 22, 2018 8:57:34 午後 org.glassfish.grizzly.http.server.HttpServer start 情報: [HttpServer] Started. Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl Hit enter to stop it...
今までと同じように実行することができました。
まとめ
さて、サンプルアプリケーションをモジュール化して、単体で実行できるところまでやってみました。正直な話、かなりめんどくさいです。
新規に作成するアプリケーションであればさほどではないかもしれませんが、既存のアプリケーションをモジュール化するのはかなりタフなことになるのではないでしょうか。
ここまでの手段をまとめてみました。
- 依存性を調べるにはjdepsコマンドを使用する
- マルチリリースJARには気をつける
- module-info.javaのひな型はjdepsコマンドで生成させる
- 必要に応じて、生成したmodule-info.javaを編集する
- Mavenで実行する場合、ゴールがexec:javaであればメインクラスだけの指定でOK
- ゴールがexec:execの場合、モジュールパス、クラスパス、ルートモジュールとメインクラスを指定する
- Mavenを使用せずに実行する場合、モジュールと自動モジュールをモジュールパス、その他をクラスパスで指定する
やっぱり、たいへんですね。