その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Jacksonでtop levelのpropertyを省略してMap<K, V>をserializeしたいんですけどってとき

public static class Something {
    private Map<String, String> prop;
}

をJacksonでserializeすると

{
  "prop": {
    "foo": "bar"
  }
}

と,トップレベルにpropのようなpropertyが出てくるので微妙……となるシチュエーションがまれによくあります.

で,どうすると良いかというと @JsonUnwrappedを使うという方法がまず考えられると思うんですが,これは問題があって期待通りに動かない.かれこれ6年くらいチケットがオープンになっています: @JsonUnwrapped not supported for Map-valued properties · Issue #171 · FasterXML/jackson-databind · GitHub

public static class Something {
    @JsonUnwrapped
    private Map<String, String> prop;
}

つまりこれは動かない.というわけでどうするかと言うと,チケット中にも示されているように@JsonAnyGetterを使うという方法があります.

public static class Something {
    private Map<String, String> prop;

    // workaround: https://github.com/FasterXML/jackson-databind/issues/171#issuecomment-117794241
    @JsonAnyGetter
    public Map<String, String> getProp() {
        return prop;
    }
}

このようにすると,

{
  "foo": "bar"
}

というふうにトップレベルのpropertyが省略された,MapのKey-Valueがそのままserializeされることとなります.よかったよかった.

arnoldというJavaのbuilderライブラリを書いた

今時builderライブラリも無いだろという感じですが,書いたのです.

github.com

Motivationにも書いているのですが,普通に作ったJavaのbuilderは不完全なインスタンスを作成できる可能性を孕んでいます.注意深くコードを書けば問題ないかもしれませんが,えてして我々 (主語が大きい) はミスを犯しがちです.そしてNPEがやってきて……我々はしぬ.

というわけで今回作ったのがarnoldです.
端的にいうと「必要なすべての要素を取りこぼさないbuilder」であり,「そのジェネレータ (annotation processor)」でもあります.Basic usageにあるように,

import net.moznion.arnold.annotation.ArnoldBuilder;
import net.moznion.arnold.annotation.Required;

@ArnoldBuilder
public class Example {
    @Required
    private String foo;
    @Required
    private int bar;
    @Required
    private double buz;
}

というようにclassに @ArnoldBuilder を付与することでinstantiate対象のclassであることを示し,builderでbuildしたいフィールドについては @Required を付与することで必要なフィールドであることを示すことができます.そしてプロジェクトをビルド *1 するとAnnotation Processorが走り,builder classが自動生成されます.
自動生成されたbuilderはこのように使うことができます.

Example example = new ExampleBuilder().foo("foo")
                                      .bar(42)
                                      .buz(2.71828)
                                      .build();

このBuilderは分解して書いてみると

ExampleBuilder exampleBuilder = new ExampleBuilder();
ExampleBuilder1 exampleBuilder1 = exampleBuilder.foo("foo");
ExampleBuilder2 exampleBuilder2 = exampleBuilder1.bar(42);
ExampleBuilder3 exampleBuilder3 = exampleBuilder2.buz(2.71828);
Example example = exampleBuilder3.build();

というふうになっており,つまり

  • ExampleBuilderfoo を受け取って ExampleBuilder1 を返すsetterを提供する
  • ExampleBuilder1bar を受け取って ExampleBuilder2 を返すsetterを提供する
  • ExampleBuilder2buz を受け取って ExampleBuilder3 を返すsetterを提供する
  • ExampleBuilder2build() を提供する

というようなコードをAnnotation Processorが一式生成するという挙動となっています.なかなか富豪的ですね.

このように,1つのbuilder (もはやbuilderではない気もしますが) が高々1つの要素を受け取り次のbuilderを返却し,終端のbuilderが build() して所望のclassのインスタンスをinstanciateする,という仕組みにすることで

  • 「build途中の不完全な状態のインスタンス」は何らかのbuilder classになるので,build対象のインスタンスをinstantiateすることは出来ない
  • 必須なフィールドがすべて揃ったらその時点でbuilderはbuild()を提供し,ようやくbuild対象のインスタンスをinstantiate出来るようになる

という安全な挙動を実現しています.


今後の課題としてはprivate classのinstantiateができないというものがあり,これはなんとかしたい *2


それはそうと今回Javaのコード生成には square/javapoet を使ってみたんですが,使いやすく,なおかつパワフルで良いですね.
現場からは以上です.ぜひご利用下さい.

*1:紛らわしいですね

*2:これなんとかなるんですかね?

Multiple projectなgradleのprojectでcheckstyle pluginを有効にする

Multiple projectなgradle projectだと, apply plugin: "checkstyle" と書いてもそれだけではcheckstyle pluginがうまく動かない.おそらく Unable to create Root Module: config ... などというエラーとともに死ぬであろう.
というのも,gradle checkstyle pluginのデフォルトの状態だと,各sub projectがおのおの checkstyle.xml を持っていることを期待しているからである (つまり[sub-project-path]/config/checkstyle/checkstyle.xml の存在が期待されている).
たいていの場合, checkstyle.xml を各sub projectが持つというのはやってはおれんので,どこかで一括管理をしてそれを各sub projectから利用したいでしょう.というわけでこう書いてやると良い;

subprojects {
    // do something...

    apply plugin: "checkstyle"

    checkstyle {
        // do something...

        configFile = rootProject.file('code_quality/checkstyle/checkstyle.xml') // <= here!
    }
}

subprojects.checkstyle.configFile に所望の checkstyle.xml を指定してやると良い.
この例ではproject rootに配置されている code_quality/checkstyle/checkstyle.xml が各sub projectからも参照されるようになる.
めでたしめでたし.

ミニマルなサンプルを置いておきます.

github.com

checkstyle.xml に誤りがある場合の挙動

それはそうと checkstyle.xml に誤りがあると

Execution failed for task ':your-project:checkstyleMain'.
> Unable to create Root Module: config {/path/to/your/checkstyle.xml}, classpath {...}.

みたいなエラーとともに死んでしまう.このエラーメッセージで checkstyle.xml にエラーがあるとは思うまい.しかしそうなのです.
仕方がないので --stacktrace オプションを有効にして再度 checkstyleMain を実行すると,それらしきstacktraceがゲロッと吐かれるので,それをもとにxmlファイルを修正していくことができるようになります.マジかよ,つらい.
このエラーメッセージは本当に不親切だと思うしなんとかなってほしい…… これに出くわした時,checkstyle.xml に誤りがあるとは全く思っておらず数時間を浪費してしまった.

checkstyle.xml に変数を埋める方法
checkstyle {
    // do something...

    configProperties = [
            'checkstyle.cache.file': "${buildDir}/checkstyle.cache",
    ]
}

みたいな感じで build.gradle で定義してやると, checkstyle.xml 上で

<property name="cacheFile" value="${checkstyle.cache.file}"/>

という感じで変数を利用できるようになる.この build.gradle 側の設定を怠ると不親切なエラーとともに死にます.勘弁してくれ.

MyBatis + GroovyでMapperを作っている時に良い感じでWHERE IN使いたいんですけど〜って時

MyBatisのMapperをGroovyのannotationを使って書くと何かと便利 (主に「XMLを書かなくても良い」という点で便利) なわけですが,そんな中で「WHERE IN」を利用したSELECTを @Select annotationベースでどうやって書くのかという件です.要は SELECT * FROM users WHERE id IN (?, ... , ?) のようにプレースホルダを利用した感じのクエリを書きたい.SQLインジェクションを防ぎたいので,その辺はORMの機構に任せてしまいたいのです.

結論から書くと,MyBatis XMLマクロの foreachを使うのがシンプルな様子.

@Select("""
<script>
  SELECT *
  FROM users
  WHERE user_id IN
  <foreach item='userId' collection='userIds' open='(' separator=',' close=')'>
    #{userId}
  </foreach>
</script>
""")
List<User> findUsersByIds(@Param('userIds') List<Long> userIds)

結局のところXMLを書く必要があるのだ!!!! (XMLを使うことでシンプルに済ませることが出来るのであればXMLを使ったら良いという事がわかりました).

Official doc:
http://www.mybatis.org/mybatis-3/dynamic-sql.html

GuiceとSpringを共存させたい

もともとGuiceを使っているプロジェクトがあってそれをSpringに移植したい,だとか,Guiceをバリバリ使っているコンポーネントをSpringのアプリに組み込むことで資源の再利用をしたい,だとか,そういう事になることがある.あるのです.この前実際になった.

で,"spring guice together" とかそういう適当な検索ワードで調べると大体「そんな事はするな」「そうしたい意味がわからん」「そう考える時点でおかしいのでは」などといった,「とにかくやるな」という結果が引っかかる.確かに.しかしやらなければならない時があるのだ……というわけでそのメモを記す.

TL;DR

やらないで済むならやらないほうが良い.

本編

ご存知の通りGuiceGoogle製のDI Frameworkで,一方のSpring frameworkはWeb Application Frameworkである.この二者間になぜ共存の問題が発生するかというと,Spring Frameworkもその内部にDI Frameworkを持っているから,というかそもそもDI機構がSpringの中核を担っているからである.つまりDI Frameworkが2個ある状態となるなのでそれらを矛盾なく管理する必要が出てくる.どうすれば良いのか.

アプローチ

SpringのDIをメインで利用して,Guiceはサブとして扱うという方法を今回は採用した.簡単に言うと,SpringのDIコンテナに対してGuiceのInjectorをInjectするという手法.
こんな感じ.

@Configuration
public class GuiceModuleConfig {
    @Bean
    Injector injector() {
        return Guice.createInjector(new AppModule());
    }
}

AppModuleGuiceのModuleで,このModuleを元にinjectorを作成し,そのinjectorをSpringのDIにinjectしてSpring DIから取得可能なようにしておく.そんでもって以下のようにAppModuleで提供されているinjecteeをSpringのDIを通じて取得できるようにしてやる.

@Bean
DataSource dataSource(final Injector injector) {
    return injector.getInstance(DataSource.class);
}

@Bean
FluentLogger fluentLogger(final Injector injector) {
    return injector.getInstance(FluentLogger.class);
}

つまりはSpringのDIにinjectしたGuiceのinjectorをSpring DI経由で引っ張ってきて,更にそのinjectorからGuice経由で任意のinstanceを取ってくるという感じ.とりあえずこれで基本的なGuiceSpring FrameworkのDI機構を共存させることが可能となる.なおSpringのDIについてはGuiceがあろうとなかろうといつものように利用できる.

Guiceで言うところのServletScopes.REQUEST使いたいときにどうするのか

@RequestScopeを使う.以下のように.

@Bean
@RequestScope
Connection connection(final Injector injector) {
    return injector.getInstance(Connection.class);
}

しかしGuice経由でinstanceを取ってきている場合はこれだけでは不十分で,GuiceServletContextListenerを継承したClassをServletContextListenerとして登録してやる必要がある.

@WebListener
public class AppGuiceConfig extends GuiceServletContextListener {
    private ServletContext servletContext;

    @Override
    public void contextInitialized(final ServletContextEvent servletContextEvent) {
        servletContext = servletContextEvent.getServletContext();
        super.contextInitialized(servletContextEvent);
    }

    @Override
    protected Injector getInjector() {
        return Guice.createInjector(new AppModule());
    }
}

なおSpring-Boot等でweb.xmlを利用しない場合は上記のように@WebListenerを用いてListenerとして認識させてやる必要がある.

雑感

2つのDI Frameworkを共存させ,管理する事となるので複雑さとコストが増すように感じる.また,適切に双方のinjecteeのライフサイクルのハンドリングも正しく行わなければならないのでその点においても複雑さが増大する.やらなくて済むのであればやらないに越したことは無いと思う.

以上です.我々はやっていっています.

[追記]

https://github.com/spring-projects/spring-guice

これ使えば楽に出来そうな雰囲気があるんだけど,maven centralに上がっていなくて面倒だったので採用を見送ったという経緯があります.未検証.

Springの起動時に好きな画像をAAにして表示する

Spring Boot 1.4.1 1.4.0 (訂正しました.詳細は id:dayflower さんのコメントを参照下さい) の機能として,Springの起動時に任意の画像をAAとして表示できる機能が追加されたようです.
なんかここに書いてた.

www.infoq.com

やり方は非常に簡単で,

  • banner.jpgbanner.pngあるいはbanner.gifという名前の画像ファイルを/src/main/resourcesの直下に置く (あるいはbanner.image.locationで設定した場所に置く)

以上.早速やってみましょう.

f:id:moznion:20161024232806p:plain

普段のSpringの起動後のターミナルといえばこんなのでしたが……

f:id:moznion:20161024232835p:plain

この機能を使うとこの通り! ちなみに元画像は僕のアイコンのこれです.

f:id:moznion:20161024232911p:plain

若干ホラー感がある……と思いきや遠目からAAが中々の再現度であることがわかります.すごい!


当初はこの機能について「なぜウェブアプリケーションフレームワークの機能として入ったんだ……」と疑いの眼差しを向けていたのですが,実際に使ってみると想像以上に気分が上がります.自分のプロジェクトのロゴなんかを入れると良い感じですね!

以上です.


[追記]
画像が大きいとAAも大きくなります.「そんなAA大きくなくて良い!」という時は,application.propertiesのbanner.image.widthもしくはbanner.image.heightを設定することでサイズの調整ができます.便利便利.

tinyorm 1.11.0 is out

tinyormのversion 1.11.0が出ました.

Maven Repository: me.geso » tinyorm » 1.11.0

新しい機能としては

に書いたように

  • Connection を2つ持てるようになった
  • Lazily な connection borrowing に対応した

という2つが挙げられます.
rc1の時点では中々にバギーだったんですが,改善を重ねていった結果,プロダクションでも安定して動作するようになったのでこの度rcを外して正式なバージョンとしてリリースしました *1

今回入った機能についてはConnectionというセクションのドキュメントでも説明しているので併せて参照ください.

*1:その結果rc7までバージョンが育ったという逸話があります