ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

MockK と JMockit の組み合わせで AttachNotSupportedException 例外が発生することがあるっぽい

Kotlin で Mockito を使うのが辛くなってきた *1 ので、「よーしパパ MockK 入れちゃうぞー」 と言って MockK 1.8.12 を導入したのだけど、その結果テストを実行すると以下のような初期化エラーが発生するようになってしまった。

java.lang.ExceptionInInitializerError
	at MyTest.<init>(MyTest.kt:101)
Caused by: java.lang.IllegalStateException: Error during attachment using: net.bytebuddy.agent.ByteBuddyAgent$AttachmentProvider$Compound@718207
	at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:384)
	at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:358)
	at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:326)
	at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:312)
	at io.mockk.proxy.jvm.JvmMockKAgentFactory.initInstrumentation(JvmMockKAgentFactory.kt:122)
	at io.mockk.proxy.jvm.JvmMockKAgentFactory.init(JvmMockKAgentFactory.kt:31)
	at io.mockk.impl.JvmMockKGateway.<init>(JvmMockKGateway.kt:45)
	at io.mockk.impl.JvmMockKGateway.<clinit>(JvmMockKGateway.kt:163)
	... 22 more
Caused by: java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at net.bytebuddy.agent.Attacher.install(Attacher.java:84)
	at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:379)
	... 29 more
Caused by: com.sun.tools.attach.AttachNotSupportedException: no providers installed
	at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:208)
	... 35 more

スタックトレースの一番下を見ると com.sun.tools.attach.VirtualMachine なので、JDK の内部でなんかなってるのかなー、と最初は思っていたのだけど、IDE で辿っていくと VirtualMachine クラスは JMockit に含まれているものだということがわかった。 使っていた JMockit が 1.22 と少し古いものだったので、最新の 1.43 を使うようにしたら無事エラーは発生しなくなった。

Mockito でも同様のエラーが発生することがある模様。

ちなみに com.sun.tools.attach.VirtualMachine は、JVM にアタッチするための API に含まれるものらしい。

JerseyTest で Servlet のリソースを扱う JAX-RS 部品のユニットテストを行う方法

要約

前提知識 : ServletJAX-RS

Jakarta EE (旧 Java EE) における HTTP サーバーアプリケーションのための API として有名なもの *1ServletJAX-RS がある。 これらは依存関係にはなく、Servlet 単体でも JAX-RS 単体でも使用できる。

一方で、JAX-RSServlet コンテナ内で実行し、JAX-RS コンポーネントServletコンポーネントをインジェクトするというような使い方もできる。

In a product that also supports the Servlet specification, implementations MUST support JAX-RS applications that are packaged as a Web application. See Section 2.3.2 for more information Web application packaging.

JAX-RS: Java™ API for RESTful Web Services version 2.1 (11. Environments - 11.2.1 Servlets)

The @Context annotation can be used to indicate a dependency on a Servlet-defined resource. A Servlet-based implementation MUST support injection of the following Servlet-defined types: ServletConfig, ServletContext, HttpServletRequest and HttpServletResponse.

JAX-RS: Java™ API for RESTful Web Services version 2.1 (11. Environment - 11.1 Servlet Container)

Servlet のリソースを扱う JAX-RS コンポーネントユニットテストする方法 (JerseyTest を使用)

JAX-RS コンポーネントユニットテストする方法は様々あるだろうが、個人的には JAX-RS の参照実装である Jersey のテストフレームワークを使う方法に親しみがある。

Jersey のテストフレームワークJerseyTest というクラスを提供しており、これを継承してテストクラスを作ると簡単に JAX-RS コンポーネントのテストを記述できる。

public class SimpleTest extends JerseyTest {
    // SimpleResource という JAX-RS リソースをテストしたい場合は、
    // それを ResourceConfig に渡してテスト用の Application を作ればよい。
    @Override
    protected Application configure() {
        return new ResourceConfig(SimpleResource.class);
    }

    // テストメソッド 
    @Test
    public void test() {
        final String responseContent = target("simple").request().get(String.class);
        assertEquals("Hello World!", responseContent);
    }
}

問題 : 単に JerseyTest を継承してテストクラスを定義するだけだと Servlet リソースが扱われない

上のようなテストコードを書いた場合、基本的には問題なくテストが動く。 しかし、SimpleResourceServlet のリソースを扱っている場合 (@ContextHttpServletRequest をインジェクトしている場合など) には、期待通りに動かない。

なぜなら、(おそらく) デフォルトで選択されるテストコンテナは Servlet をサポートしておらず、Servlet のリソースのインジェクトが行われないからである。

解決策 : Servlet をサポートしているテストコンテナを利用する

テストコンテナの決まり方については、ドキュメントに下記のように書かれている。

A test container is selected based on various inputs. JerseyTest#getTestContainerFactory() is always executed, so if you override it and provide your own version of TestContainerFactory, nothing else will be considered. Setting a system variable TestProperties#CONTAINER_FACTORY has similar effect. This way you may defer the decision on which containers you want to run your tests from the compile time to the test execution time. Default implementation of TestContainerFactory looks for container factories on classpath. If more than one instance is found and there is a Grizzly test container factory among them, it will be used; if not, a warning will be logged and the first found factory will be instantiated.

Chapter 26. Jersey Test Framework

一番簡単な方法は JerseyTest#getTestContainerFactory() をオーバーライドすることだろう。

Servlet をサポートしているテストコンテナもドキュメントに書かれている。

Second factory is GrizzlyWebTestContainerFactory that is Servlet-based and supports Servlet deployment context for tested applications. This factory can be useful when testing more complex Servlet-based application deployments.

Chapter 26. Jersey Test Framework

GrizzlyWebTestContainerFactory を使えばよい。 ちなみに GrizzlyWebTestContainerFactoryDeploymentContext として ServletDeploymentContext を求めるので、その点は注意が必要である。

ということで、下記のようなテストクラスを書けば Servlet のリソースを扱う JAX-RSユニットテストを記述できる。

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;

public class CookieSessionResponseFilterTest extends JerseyTest {

    private TestContainerFactory testContainerFactory;

    /**
     * テスト用のコンテナを生成するメソッドをオーバーライド。
     *
     * ここで {@link GrizzlyWebTestContainerFactory} を指定することで、サーブレットベースのアプリケーションのテストが可能となる。
     */
    @Override
    protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
        // こういう感じで同じインスタンスを返す必要があるのかはよくわからないが
        // JerseyTest の実装がそういう感じになっているのでそれに合わせている。
        if (testContainerFactory == null) {
            testContainerFactory = new GrizzlyWebTestContainerFactory();
        }
        return testContainerFactory;
    }

    /**
     * テスト用のコンテナにデプロイする内容。
     *
     * {@link GrizzlyWebTestContainerFactory} を使うためには {@link ServletDeploymentContext} を返す必要がある。
     */
    @Override
    protected ServletDeploymentContext configureDeployment() {
        ServletContainer jaxRsServlet = ;
        return ServletDeploymentContext
                // JAX-RS コンポーネントは `ServletContainer` に包んでやる。
                .forServlet(new ServletContainer(configure()))
                // Servlet フィルタの追加も可能。
                .addFilter(TestServletFilter.class, TestServletFilter.class.getSimpleName())
                .build();
    }

    // SimpleResource という JAX-RS リソースをテストしたい場合は、
    // それを ResourceConfig に渡してテスト用の Application を作ればよい。
    // {@link #configureDeployment()} をオーバーライドしたらこのメソッドを
    // オーバーライドしなくても良いはず (configureDeployment 内に書けばよいはず) だが、
    // 一応オーバーライドして {@link #configureDeployment()} から呼ぶようにした。
    @Override
    protected ResourceConfig configure() {
        return new ResourceConfig(SimpleResource.class);
    }

    // テストメソッド 
    @Test
    public void test() {
        final String responseContent = target("simple").request().get(String.class);
        assertEquals("Hello World!", responseContent);
    }
}

自分でテストコンテナのファクトリを書いても良い

上ではもともと Jersey のテストフレームワークに用意されている GrizzlyWebTestContainerFactory を利用したが、独自に TestContainerFactory を実装しても良い。 サーブレットを複数含むテストを書きたい場合などにはそうする必要がありそうである。 (そもそもそういうテストを書く必要がある設計にすべきではないという気もするが。)

JerseyTestでHttpServletRequestを使いたい - Chonaso's Commentary』 が参考になる。 (実際に自分で書いたらわりと手を入れる必要はあったが。)

おわり

まとめると、下記のとおり。

  • Jersey のテストフレームワークが便利。
  • Servlet のリソースも含めてテストしたい場合には GrizzlyWebTestContainerFactory を使用すると良い。

とはいえ、JAX-RSServlet のリソースを扱う必要がある場面は非常に限定的であるはずで、JAX-RSAPI だけで完結できる場面ではそうすべきである。 その方がテストも書きやすいし移植性も高い。 複数の API 仕様の知識 (JAX-RS の知識も Servelt の知識も必要) を求めることもなくなるので、開発者に対しても優しいはずである。

仕事で触っているコードには JAX-RS の中で Servlet のリソースを扱っているものがわりとあるのだが、少しずつ JAX-RSAPI に置き換えていきたい。

*1:これ以外にあるのかどうかは知らない。

Gradle のマルチモジュールプロジェクトで maven-publish プラグインを使う場合の依存関係

Gradle でビルド成果物 (build artifact) を Maven リポジトリに公開するためのプラグインとして、maven-publish プラグインがある。

maven-publish プラグインとマルチプロジェクトの依存関係

簡単な例

Java ライブラリのプロジェクトがあったとして、JAR ファイルを Maven リポジトリに公開するための build.gradle の記述は以下のようになる。

// Java ライブラリのビルド用の設定などは省略

apply plugin: 'maven-publish'

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}

これで publishToMavenLocal タスクなどが使えるようになるので、例えばローカルホスト上の Maven リポジトリに公開するには ./gradlew publishToMavenLocal コマンドを実行すればよい。

依存関係

上の例では、ソフトウェアコンポーネントとして java コンポーネントを指定している。 このとき、依存関係としては runtime コンフィギュレーションのものが使われる。

Name Provided By Artifacts Dependencies
java The Java Plugin Generated jar file Dependencies from 'runtime' configuration
web The War Plugin Generated war file No dependencies
Maven Publishing (new) - Gradle User Manual

マルチモジュールプロジェクトでの依存関係

例えば :foo プロジェクトと :bar プロジェクトからなるマルチモジュールプロジェクトで、:bar プロジェクトから :foo プロジェクトに依存している場合に、出力される pom.xml に記載される依存関係はどうなるのか?? 具体的には bar/build.gradle に以下のように書かれている状況である。

dependencies {
    compile project(':foo')
}

これはドキュメントには書かれていないが、maven-publish プラグインソースコードを確認したところ、依存先プロジェクトの設定に応じて自動的に依存先ライブラリの指定がなされるようになっていた。

例えば、依存先プロジェクトで maven-publish プラグインが使われていない場合は、プロジェクトのグループやプロジェクト名、バージョンが使われるようである。 依存先プロジェクトで maven-publish プラグインが使われており、特定の条件を満たす publication が存在する場合は、その publication で指定されているグループ・artifactId・バージョンが使われる。

publishing {
    publications {
        maven(MavenPublication) {
            groupId 'org.example'
            artifactId 'foo-sample'
            version '1.1'

            from components.java
        }
    }
}

foo/build.gradle に上のように書かれていたら、./gradlew :bar:publishToMavenLocal タスクで公開される成果物の pom ファイルには以下の内容が含まれることになる。

    <dependency>
      <groupId>org.example</groupId>
      <artifactId>foo-sample</artifactId>
      <version>1.1</version>
      <scope>compile</scope>
    </dependency>

気を付ける必要があること

依存先プロジェクトの中で maven-publish プラグインが使用されて、publication が定義されている必要がある。

下記のように、親プロジェクトで子プロジェクトの publication をまとめて定義することもできるのだが、そうすると依存関係の解決時に 「依存先プロジェクトでは publication がない」 という扱いになってしまって、デフォルト値が使用される。

publishing {
    publications {
        mavenFoo(MavenPublication) {
            groupId 'org.example'
            artifactId 'foo-sample'
            version '1.1'

            from findProject(':foo').components.java
        }

        mavenBar(MavenPublication) {
            groupId 'org.example'
            artifactId 'bar-sample'
            version '1.1'

            from findProject(':bar').components.java
        }
    }
}

私はこれでハマってしまった。 (Exposed の build.gradle を見て、親の build.gradle で子プロジェクトの publication を宣言していたので、それを参考にした、という。) 注意されたし。

ThreeTenABP と ThreeTenBP の関係について (Android における JSR-310 バックポート)

「ThreeTenABP って Android フレームワークに依存するけど、ドメインレイヤとかで Android 依存を排除したい場合どうするのがいいんですかね」 という相談を受けて、ThreeTenABP 周りを調べたのでメモ。

ThreeTenABP って何?

JSR 310 Date and Time API 周りの話

ThreeTenABP が存在する意味は?

  • ThreeTen Backport (threetenbp) があるならそれでいいのでは? → ThreeTen Backport はタイムゾーン周りの情報を JAR から読み込むという仕組みになっており、Android では非効率。

というわけで

ThreeTenABP を使いたいけど Android フレームワークに依存させたくないというモジュールでは、ThreeTen Backport に依存させて、テストを書く際にタイムゾーン情報の扱いだけなんとかする、みたいなことをすれば良さそう。

タイムゾーン情報周りを厳密にテストしなくていいなら、compileOnly "org.threeten:threetenbp:${threetenbp_version}"testCompile "org.threeten:threetenbp:${threetenbp_version}" を依存に追加しちゃうのが手軽でいい気がする。 (バージョン周りはよしなに。 これでうまくいくだろうと思って書いてるけど特に何も確認はしてない。)

関連ページ

読んだ : RESTful Web Services with Dropwizard / Alexandros Dallas 著

Dropwizard に関わる仕事をしているので読んでみました。

RESTful Web Services with Dropwizard

RESTful Web Services with Dropwizard

Dropwizard について

Dropwizard は Java の web アプリケーションフレームワークです。 基本的には既存ライブラリの組み合わせで web アプリケーションを構築するというもので、Dropwizard 固有の仕組みはさほど多くありません。 (例えば Web リクエストを受け取るのは Jersey で、DB アクセスには Hibernate か jDBI が使われる。)

特に Java EE 系の知識を持っている人であれば、とっつきやすい感じです。

本書について

本書は、Dropwizard を使って web アプリケーションを構築するための方法を説明するものです。 プロジェクトの準備や、HTTP リクエストを受けるエンドポイントの記述、DB アクセス、ユーザー認証、HTML を返す View テンプレートについてなど、基本的な要素について、サンプルコードを交えながら仕組みが解説されます。

Java EE などについてある程度わかっている人が読むと Dropwizard の公式ドキュメントを読むのと大差ないと思いますが、初心者の人が読むと結構わかりやすいんじゃないかなと思います。 (ある程度わかってる人にとっても、Dropwizard についてざっと知ることができて良いかもしれませんが、そういう使い方だとちょっと値段は高めに感じる気がします。)

本書での学び

参考のリンクとしては現時点での最新バージョンのドキュメントへのリンクです。 閲覧時の最新バージョンのドキュメントは各自探してください。

  • Dropwizard では、maven-shade プラグイン を使って、単体で web アプリケーションとして実行可能な JAR ファイル (uber-jar) を作る。
  • Hibernate Validator によるアプリケーション設定のバリデーションが可能。
  • jDBI で DB から取得した結果をオブジェクトにマップする方法として、@MapResultAsBean アノテーションを使うという方法もある。
    • 参考 : MapResultAsBean (jDBI 2.48.2 API)
    • とはいえ公式的なドキュメントは何もなく、ドキュメント化されていない挙動に依存することになるので不安。 (JDBI の機能の多くがドキュメント化されてないのでまあこれに限った話ではないのだけど。)
  • HTTP リクエストパラメータのバリデーション周り。
    • JAX-RS のリソースメソッドのパラメータのバリデーションを明示的に実行することも可能。 (@Valid アノテーションでのバリデーション実行しか知らなかった。)
    • 複数フィールドにまたがるバリデーション。
    • 参考 : Dropwizard Validation | Dropwizard
  • Dropwizard には HTTP クライアント用モジュールも含まれている。
  • 認証周り。
    • Basic 認証用のクラスが用意されている。
    • オプションの認証も可能。 (認証されたユーザーの場合はそのユーザー専用のコンテンツを表示し、さもなければ一般ユーザー向けのコンテンツを表示する、みたいな。)
    • CachingAuthenticator によるキャッシング。
    • 参考 : Dropwizard Authentication | Dropwizard
  • Fixtures for Easy Software Testing (FEST) というプロジェクトがある。 TestNGJUnit と一緒に使える。 ソフトウェアテストを書きやすくするライブラリ。

感想

Dropwizard をそこそこ使ってて公式ドキュメントも (全部ではないけど) 読んでいたので、本書での学びはあんまりなかったです。 とはいえ上に書いたように新たに知れたこともいくつかあったので読んでよかったです。 (そんなに時間もかけずにざっと読めましたし。)

とはいえ紙の本だと 30 ドル以上するので、ちょっと高い感じはしますね。