torutkのブログ

ソフトウェア・エンジニアのブログ

Automatic Moduleを(続)

id:torutk:20171010で、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に加えて、LinuxFedora 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;
}
コンパイル
D:\work> javac -d mods --module-path lib --module-source-path src -m org.tweetsum

今度はうまくいきました。

実行

実行にあたっては、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 を使おう!