Automatic Moduleを(続)
Automatic moduleを - torutkのブログで、JavaOne 2017のセッション"Migrating to Modules"の動画を見ながらデモのtweetsumを倣って作ってみたところ、モジュールのコンパイルでjacksonのJARファイルが参照できないエラーが発生したことを書きました。
この件について@skrbさんから、次のツイートをもらいました。(ありがとうございます!)
JacksonのMANIFEST.MFにはAutomatic-Module-Nameが記載されていて、それがモジュール名になっているからです
これをヒントにいろいろ調べてみました。
jacksonのJARファイルに問題があるか否かを切り分けるため、別なJARファイルとしてlog4jをAutomatic moduleとして参照するサンプルを作ってみました。
log4j をモジュールとして参照
次のディレクトリ構造を作って、module-info.javaとMain.javaを記述します。
また、log4jのライブラリ(JAR)を配置します。
├─src │ └─com.torutk.hello │ │ module-info.java │ │ │ └─com │ └─torutk │ └─hello │ Main.java │ ├─lib │ log4j-api-2.9.1.jar │ log4j-core-2.9.1.jar
module-info.java
module com.torutk.hello { requires log4j.api; requires log4j.core; }
Main.java
package com.torutk.hello; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class Main { private static Logger logger = LogManager.getLogger(); public static void main(String... args) { logger.error("Hello, log4j from Modules"); } }
コンパイルと実行
D:\work> javac -d mods --module-path lib --module-source-path src -m com.torutk.hello
問題なくコンパイルできました。
D:\work> java --module-path mods;lib -m com.torutk.hello/com.torutk.hello.Main ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging. 07:37:08.185 [main] ERROR com.torutk.hello.Main - Hello, log4j from Modules
問題なく実行できました。
なお、エラーメッセージが2つ出ていますが、最初のエラーメッセージはlog4jで設定ファイルを用意していないのでデフォルト設定を使うという内容です。
次のエラーメッセージは、log4jはデフォルトではエラーレベル以上がコンソールに出力されるので、プログラム中からエラーレベルでログ出力をしているためです。
jacksonライブラリのJARを調査する
Windows 10に加えて、Linux(Fedora 26)にJDK 9を入れて確認しましたが、やはりjacksonのライブラリが認識されませんでした。
JavaOneのセッション動画の例と違うのはjacksonライブラリのバージョンが新しいものになっている点です。冒頭の@skrbさんのツイートにあるように、JARファイルの中からMANIFEST.MFを取り出して内容を確認しました。すると、、、
Automatic-Module-Name属性が指定されている!
Manifest-Version: 1.0 Automatic-Module-Name: com.fasterxml.jackson.core Bnd-LastModified: 1504831683959 Build-Jdk: 1.7.0_79 Built-By: tatu Bundle-Description: Core Jackson processing abstractions (aka Streaming API), implementation for JSON Bundle-DocURL: https://github.com/FasterXML/jackson-core Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt Bundle-ManifestVersion: 2 Bundle-Name: Jackson-core Bundle-SymbolicName: com.fasterxml.jackson.core.jackson-core Bundle-Vendor: FasterXML Bundle-Version: 2.9.1 Created-By: Apache Maven Bundle Plugin Export-Package: com.fasterxml.jackson.core;version="2.9.1";uses:="com.fa :(以下略)
となっており、
Automatic-Module-Name: com.fasterxml.jackson.core
という記述がありました。
JigsawのAutomatic Moduleでは、module-info.classが含まれていない非モジュールJARについては、JARファイル名から特定のルールに従ってモジュール名が生成されます。
しかし、MANIFEST.MFのAutomatic-Module-Name属性を記述しておくと、JARファイル名からの生成ではなく、この属性に記述された名前がモジュール名として扱われます。
ということで、jacksonの各JARファイルのMANIFEST.MFの情報からモジュール名を確認し、module-info.javaを修正しました。
module org.tweetsum { requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.annotation; requires java.sql; }
実行
実行にあたっては、2つほど問題があります。
- 先のmodule-info.javaでは不足あり
jacksonのdatabindは、アプリケーションにリフレクションでアクセスします。module-info.javaに、アプリケーション(org.tweetsum)をリフレクションでアクセス可能とする記述(opens)が必要です。
module org.tweetsum { requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.annotation; requires java.sql; opens org.tweetsum to com.fasterxml.jackson.databind; }
- サンプルコードのTimestamp解釈はロケール依存
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "EEE MMM dd HH:mm:ss Z yyyy", timezone = "GMT")
ここのpatternで指定している書式は英語ロケールのものです。そこで、javaのオプションでロケールを英語に指定します(-Duser.language=en)。
D:\work> java -Duser.language=en --module-path mods;lib -m org.tweetsum/org.tweetsum.Main < tweets.json 2017-10-11 16:30:00.0: Hello Jackson, ONE 2017-10-11 16:31:00.0: Hello Jackson, TWO 2017-10-11 16:33:30.0: Hello Jackson, THREE
Automatic Moduleの名前の確認方法
実は、MANIFEST.MFを参照しなくても、jarコマンドのオプション--describe-moduleで調べることができます。
D:\work> jar --describe-module --file lib\jackson-core-2.9.1.jar モジュール・ディスクリプタが見つかりません。自動モジュールが導出されました。 com.fasterxml.jackson.core@2.9.1 automatic requires java.base mandated provides com.fasterxml.jackson.core.JsonFactory with com.fasterxml.jackson.core.jsonfactory contains com.fasterxml.jackson.core :(以下略)
と、
com.fasterxml.jackson.core@2.9.1 automatic
ここからモジュール名が分かります。
まとめ
モジュール名を確認するときは、jar --describe-module を使おう!