tokuhirom's blog

CPU の system が妙に高いぞって時に犯人さがすには pidstat 使えば良い。

pidstat -h -u | sort -nr -k 5,5 | head すれば良い。

sudo yum install -y sysstat してインストール。

簡単に誰が system 消費してるかわかる [tokuhirom@centos-1gb-sgp1-01 ~]$ pidstat -h -u | sort -nr -k 5,5 | head 1462874412 0 27 0.00 0.22 0.00 0.22 0 kswapd0 1462874412 0 19281 0.16 0.09 0.00 0.25 0 cadvisor 1462874412 0 342 0.01 0.02 0.00 0.03 0 systemd-journal 1462874412 0 377 0.00 0.01 0.00 0.01 0 auditd 1462874412 0 262 0.00 0.01 0.00 0.01 0 jbd2/vda1-8 1462874412 0 16958 0.02 0.01 0.00 0.02 0 tuned 1462874412 0 13775 0.00 0.01 0.00 0.01 0 xfsaild/dm-9 1462874412 0 11 0.00 0.01 0.00 0.01 0 rcuos/0 1462874412 0 10 0.00 0.01 0.00 0.01 0 rcu_sched

Created: 2016-05-10 19:01:11 +0900
Updated: 2016-05-10 19:01:11 +0900

blog のバグを直した

ブログシステムをうっかり書き直したらちょっとバグってたので直した。

  • ctime/mtime を timestamp で保持していたが git なので保持されてなくて死んでたのでファイルの中に ctime/mtime を入れるようにした
  • entry の個別ページの title タグがちゃんと入ってなかったので入れた
Created: 2016-05-09 23:40:25 +0900
Updated: 2016-05-09 23:40:25 +0900

[java][linux] Java の各プロセスのスレッド数を得るワンライナー

jps -l | ruby -ne 'pid, cmd = $_.chomp.split(/ /); puts pid + "\t" + File.read("/proc/#{pid}/stat").split(/ /)[19] + "\t" + cmd'

linux の procfs から取得していくスタイル

Created: 2016-05-09 19:08:27 +0900
Updated: 2016-05-09 19:08:27 +0900

javaagent として動く java のサンプラを書いた

java アプリケーションのサンプリングを簡単にやるための簡単なライブラリを書いた。javaagent の習作的なやつです。

https://github.com/tokuhirom/java-samples/tree/master/nanojsampler

たんに、定期的に Thread.getAllStackTraces() して、よしなに集計した結果を jmx に流し込んでいる。

package me.geso.nanojsampler;

import javax.management.*;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class Nanojsampler {
    private final Map<StackTraceElement, Integer> map = new ConcurrentHashMap<>();

    public static void premain(String agentArgs, Instrumentation inst) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        int sleepMillisec = Integer.parseInt(agentArgs);
        Nanojsampler nanojsampler = new Nanojsampler();
        nanojsampler.initJmx();

        Thread thread = new Thread(() -> {
            try {
                while (true) {
                    nanojsampler.poll();
                    Thread.sleep(sleepMillisec);
                }
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
        });
        thread.setName("nanojsampler");
        thread.start();

        System.out.println("done");
    }

    public void initJmx() throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName("nanojsampler:type=report");
        NanojsamplerReport nanojsamplerMBean = new NanojsamplerReport(map);
        mbs.registerMBean(nanojsamplerMBean, objectName);
    }

    public void poll() {
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> threadEntry : allStackTraces.entrySet()) {
            process(threadEntry);
        }
    }

    private void process(Map.Entry<Thread, StackTraceElement[]> threadEntry) {
        Thread thread = threadEntry.getKey();
        StackTraceElement[] traceElements = threadEntry.getValue();

        // ignore waiting threads
        if (thread.getState() == Thread.State.TIMED_WAITING || thread.getState() == Thread.State.WAITING) {
            return;
        }

        for (StackTraceElement traceElement : traceElements) {
            map.compute(traceElement,
                    (key2, oldValue) -> oldValue == null ? 1 : oldValue + 1);
        }
    }

    public interface NanojsamplerReportMBean {
        List<String> getReport();
    }

    public static class NanojsamplerReport implements NanojsamplerReportMBean {
        private final Map<StackTraceElement, Integer> map;

        public NanojsamplerReport(Map<StackTraceElement, Integer> map) {
            this.map = map;
        }

        @Override
        public List<String> getReport() {
            return this.map.entrySet().stream()
                    .sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
                    .map(NanojsamplerReport::convertEntry)
                    .collect(Collectors.toList());
        }

        private static String convertEntry(Map.Entry<StackTraceElement, Integer> stackTraceElementIntegerEntry) {
            StackTraceElement element = stackTraceElementIntegerEntry.getKey();
            Integer value = stackTraceElementIntegerEntry.getValue();

            return value + "\t" + element.getClassName() + "." + element.getMethodName() + ":" + element.getFileName() + ":" + element.getLineNumber();
        }
    }
}

build.gradle に以下のように記載して、Premain-class を定義する。src/main/resources/META-INF/MANIFEST.MF に直接ファイル置いても上書きされるので注意。 jar { manifest { attributes( "Agent-Class": "me.geso.nanojsampler.Nanojsampler", "Premain-Class": "me.geso.nanojsampler.Nanojsampler") } }

集計処理をインプロセスでやっているので重くなりがちかもしれない。が、現実的にはそれほど重くはならないか。 いずれにせよサンプルコードなのでまあ良し。

Created: 2016-05-09 09:18:59 +0900
Updated: 2016-05-09 09:18:59 +0900

[docker] alpine linux を使うなら gosu じゃなくて su-exec で良い

gosu という setuidgid みたいなツールがあるが、よく README を読むと

When using Alpine, it's probably also worth checking out su-exec (apk add --no-cache su-exec), which since version 0.2 is fully gosu-compatible in a fraction of the file size.

と記載されており、su-exec を使えば良いとのこと(su-exec のほうが C なので読みやすいし)。

Created: 2016-05-09 06:10:30 +0900
Updated: 2016-05-09 06:10:30 +0900

Java mixed-mode flame graphsについてのメモ

http://www.brendangregg.com/blog/2015-11-06/java-mixed-mode-flame-graphs.html

Java mixed-mode flame graphs ってエントリがどんな問題を解決しているか

  • perf を用いた軽量なプロファイリングを java でも
    • perf-map-agent を利用することで java のメソッドもプロファイリング可能
    • Java のプロファイリングと kernel/libc などの system profiling をいっぺんにできる
    • この2つのものを同時にプロファイリングできることを mixed-mode といっているようだ
  • flame graphs で見やすいグラフを
    • NYTProf とかで出てる奴ね

といった問題を解決しているのだが、解決している問題が多岐に渡っていて理解しにくい。

必要な環境

  • jdk8u60+
  • linux
  • perf

とりあえず試す

sudo yum install -y perf

で perf を install

sudo yum install -y java-1.8.0-openjdk-devel

openjdk なら -devel パッケージもインストールしないと perf-map-agent をビルドできない(oraclejdk でどうなのかは未検証)。

perf のメトリクス取るのの基本

sudo perf record -F 99 -ag -- sleep 30 で、99Hz で 30 sec のプロファイリング。

sudo perf report -f でレポートを curses で見れる

perf-map-agent を使う

https://github.com/jrudolph/perf-map-agent

[tokuhirom@develop perf-map-agent]$ git clone https://github.com/jrudolph/perf-map-agent.git                                                                                                        
Cloning into 'perf-map-agent'...
remote: Counting objects: 336, done.
remote: Total 336 (delta 0), reused 0 (delta 0), pack-reused 336
Receiving objects: 100% (336/336), 95.16 KiB | 0 bytes/s, done.
Resolving deltas: 100% (165/165), done.
[tokuhirom@develop perf-map-agent]$ cmake .
-- Found JNI: /usr/lib/jvm/jre/lib/amd64/libjawt.so  
-- JNI_INCLUDE_DIRS=/usr/lib/jvm/java/include;/usr/lib/jvm/java/include/linux;/usr/lib/jvm/java/include
-- JNI_LIBRARIES=/usr/lib/jvm/jre/lib/amd64/libjawt.so;/usr/lib/jvm/jre/lib/amd64/server/libjvm.so
-- JAVA_INCLUDE_PATH=/usr/lib/jvm/java/include
-- JAVA_INCLUDE_PATH2=/usr/lib/jvm/java/include/linux
-- Found Java: /bin/java (found version "1.8.0.91") 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tokuhirom/perf-map-agent

perf-map-agent というコマンドラインツールを用意する。これが特定の PID の java process に attach して、JVM TI (JVM Tool Inteface) で情報を集めて perf 用の形式で出力してくれる。このツールを利用するには -XX:+PreserveFramePointer をターゲットの jvm の起動時につける必要がある(このオプションは java 8u60+ で利用可能)。

create-java-perf-map.sh

./bin/perf-java-top

localdev-provisioning%20%E2%80%94%20tokuhirom@develop:~%20%E2%80%94%20ssh%20%E2%97%82%20vagrant%20ssh%20and%20blog%20admin

net.virtualvoid.perf.AttachOnce 実行して map を取得したあと、perf top している。↑↑のスクショを見ると、java method の情報も取得できていることがわかる。

まとめ

  • perf-map-agent 便利
    • 得に、Atlassian など導入しているときにボトルネックさっくり知りたいとかそういうケースでは恩恵がおおきそう
    • あるいは norikra とか
  • 通常の web app の場合、適用範囲は限定的かもしれない
    • しかし、perf ほど気軽に使えるものもあんまないのでこの方法は有りかもしれない
  • perf-map-agent はjvmti を利用するCコードをターゲットVMにねじ込む
    • バグがあるとこれ起因でクラッシュするかもしれない
Created: 2016-05-08 23:37:03 +0900
Updated: 2016-05-09 12:38:10 +0900

ansistrano について

https://github.com/ansistrano/deploy/

Ansible 上で capistrano 的にデプロイできるようにするというプロジェクトがある。 これは一見良さそうだが、設定を真面目にやっていくと、同等のことを自分で手で書くのと同程度に大量の設定を書くことになるのが気になる。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

Alfred のワークフローを Perl で書きたい!

https://github.com/tokuhirom/p5-Alfred

なんか Alfred2 を久々に使い始めている。

最近は IDE で生活する時間が長いので、terminal を開いてそこから peco を開いてみたいなことをするよりは、Alfred2 からサーバー選んで、そこから open してターミナル開いたほうがいいなぁと思ってみたという感じです。

で、perl で workflow を書いたらいいんだけど、perl 用のライブラリがなかったので書いた。

要するに、定義にしたがって、XML を吐くスクリプトがありゃいいんで、吐くようにしたってだけ。

ご利用ください。

これを使って、社内のデプロイツールからサーバーリスト撮ってきて、ターミナル開く機能と、サーバーリストからサーバーグループをいっきに csshX で開くってなツールを書きました。

あと、kurado を開くやつを作ろうと思ったけど、kurado には service list を取得する API がないのであきらめた。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-05 13:28:37 +0900

rxjava で peek() みたいなことしたい

java8 stream api でいうところの peek をするには doOnNext を利用すればよい。

http://stackoverflow.com/questions/30760768/rxjava-performing-a-peek-or-void-operation-within-an-observable-chain

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

spring-boot のメトリクスデータを graphite に送る

graphite とはまあザックリ言うと growthforecast みたいなものだが、python で書かれていて、ダッシュボード的な機能が充実してるって感じのものです。

画面の様子は以下の様な感じ。

Graph Tree:

Dashboard:

以下の様な感じで、nc で直接書き込める。http based な gf とはちょっと違う。

PORT=2003
SERVER=graphite.your.org
echo "local.random.diceroll 4 `date +%s`" | nc -q0 ${SERVER} ${PORT}

で、こういうやつにメトリクスデータを送るにはどうしたらいいか。spring-boot 自体はメトリクスの出力先として statsd, redis, opentsdb しかサポートしていない。 statsd から graphite に書き込ませることもできるのだが、今回は直接 graphite に書かせてみた。statsd 経由でやる場合は、各ホストに statsd を動かすってことになるのかなあ。 あんまり statsd 使ってる話を聞かないのでよくわからない。そして、graphite に送るだけなら、直接 graphite に送ればいい気もする。


さて、spring-boot で graphite を送るには、例によって dropwizard-metrics を利用すれば良い。

依存に以下を足す。

compile('io.dropwizard.metrics:metrics-core:3.1.0')
compile('io.dropwizard.metrics:metrics-graphite:3.1.2')

で、実際に動くコードは以下。依存にいれておけば、org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration が動いて、 com.codahale.metrics.MetricRegistry を Autowired できるようになるんで、これを利用すれば良い。

package com.example.config;

import com.codahale.metrics.MetricFilter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.graphite.Graphite;
import com.codahale.metrics.graphite.GraphiteReporter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.MetricsDropwizardAutoConfiguration;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

@Configuration
@Slf4j
public class GraphiteConfig {
    /**
     * {@link MetricsDropwizardAutoConfiguration} configures Dropwizard's MetricRegistry
     * if it's available in classpath.
     */
    @Autowired
    private MetricRegistry registry;

    @PostConstruct
    public void initialize() {
        final Graphite graphite = new Graphite(new InetSocketAddress("localhost", 2003));
        final GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
                .prefixedWith("web1.example.com")
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .filter(MetricFilter.ALL)
                .build(graphite);
        reporter.start(1, TimeUnit.MINUTES);

    }
}

簡単ですね。 full sample code is here: https://github.com/tokuhirom/java-samples/tree/master/spring-boot-graphite-demo

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

aspectj の post compile weaving を gradle で行う方法

spring boot の起動が極めて遅くて辛いなと感じていたところ、どうやら spring aop が極めて多くの時間を浪費しているということが判明した。 spring aop をオフにすると起動時間が16秒なのに対し、オンにすると 26 秒に増える。しかも、利用しているエンドポイントの数に比例しているようで、これは今後さらに遅くなりそうだ。。

調べてみると spring aop は load time weaving を採用していることがわかった。 ref. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

AOP には3種類のやり方がある。

  • Compile-time weaving
    • ajc という eclipse java compiler を modified した版で .java を .class にコンパイルする際に AOP コードをねじ込む
    • lombok は ajc をサポートしていない。
  • Post compile weaving
    • ajc を利用するが、.class から .class への変換を行う。
  • load time weaving
    • 実行時に DI コンテナからの bean 取得時などに、proxy object を挟むことによって、AOP を実現する
    • JDK dynamic proxies を使う方法と CGLib 等によるバイトコード生成を利用する方法がある
    • JDK dynamic proxy は interface には適用可能だが class に対しては適用できない。
    • 実行時にオーバーヘッドがかかる。特に起動時にダミークラス挟まなきゃいけないし、ビーンのロード時にいちいちチェックしないといけない。

ref. https://eclipse.org/aspectj/doc/next/devguide/ltw.html

このうち compile-time weaving は、lombok を利用できないために却下。

そういうわけで、Post compile weaving を試してみた。 しかし、gradle で post compile weaving を利用する方法は、なかなか見つからない。 見つからないのでゴリゴリと書いた。

gradle では、プロジェクト内にプラグインを置くことができるので、aspectj プラグインを書く。 ディレクトリ構成は以下のようにする。

buildSrc
├── build.gradle
└──c
    └── main
        ├── groovy
        │   └── aspectj
        │       └── AspectJPlugin.groovy
        └── resour
            └── METAF
                └── gradle-plugins
                    └── aspectj.properties

resources/META-INF/gradle-plugins/aspectj.properties に、implementation-class=aspectj.AspectJPlugin と書くことで、aspectj plugin の実装がどこにあるのかを指定する。

src/main/groovy/aspetctj/AspectJPlugin.groovy は、class AspectJPlugin implements org.gradle.api.Plugin<org.gradle.api.Project> { } のように、プラグインクラスを継承した、 クラスを配置する。このPluginクラスの void apply(Project project) メソッドが、apply plugin: 'aspectj' された時に呼ばれるので、ここにコードを記載していく。

spring security に入っていた aspectj plugin をベースに書いたが、もはや原型はとどめていないコードがこちらになります。 ``` package aspectj

import org.aspectj.bridge.IMessage import org.aspectj.bridge.MessageHandler import org.aspectj.tools.ajc.Main import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.TaskAction

import java.nio.file.Path

// taken from spring security. // https://github.com/spring-projects/spring-security/blob/master/buildSrc/src/main/groovy/aspectj/AspectJPlugin.groovy /** * * @author Luke Taylor */ class AspectJPlugin implements Plugin {

void apply(Project project) {
    project.plugins.apply(JavaPlugin)

    if (!project.hasProperty('aspectjVersion')) {
        throw new GradleException("You must set the property 'aspectjVersion' before applying the aspectj plugin")
    }

    if (project.configurations.findByName('ajtools') == null) {
        project.configurations.create('ajtools')
        project.dependencies {
            ajtools "org.aspectj:aspectjtools:${project.aspectjVersion}"
            compile "org.aspectj:aspectjrt:${project.aspectjVersion}"
        }
    }

    project.afterEvaluate {

        project.tasks.create(name: 'compileAspect', overwrite: true, description: 'Compiles AspectJ Source', type: Ajc) {
            dependsOn project.processResources, project.compileJava

            tmpDir = "${project.buildDir}/aspect/"
            args = [
                    "-inpath", project.sourceSets.main.output.classesDir.toPath(),
                    "-showWeaveInfo",
                    "-1.8",
                    "-d", tmpDir,
                    "-classpath", project.sourceSets.main.compileClasspath.asPath,
            ];
            dstDir = project.sourceSets.main.output.classesDir.toPath()
        }
        project.tasks.classes.dependsOn project.tasks.compileAspect

        project.tasks.create(name: 'compileTestAspect', overwrite: true, description: 'Compiles AspectJ Test Source', type: Ajc) {
            dependsOn project.processTestResources, project.compileTestJava

            tmpDir = "${project.buildDir}/test-aspect/"
            def classpath = project.sourceSets.test.compileClasspath.files.grep({ it.exists() }).join(":")
            args = [
                    "-inpath", project.sourceSets.test.output.classesDir.toPath(),
                    "-aspectpath", project.sourceSets.main.output.classesDir.toPath(),
                    "-aspectpath", project.sourceSets.test.output.classesDir.toPath(),
                    "-showWeaveInfo",
                    "-1.8",
                    "-d", tmpDir,
                    "-classpath", classpath
            ];
            dstDir = project.sourceSets.test.output.classesDir.toPath()
        }
        project.tasks.testClasses.dependsOn project.tasks.compileTestAspect
    }
}

}

class Ajc extends DefaultTask { String[] args String tmpDir Path dstDir

Ajc() {
    logging.captureStandardOutput(LogLevel.INFO)
}

//http://www.eclipse.org/aspectj/doc/released/devguide/ajc-ref.html

// http://stackoverflow.com/questions/3660547/apt-and-aop-in-the-same-project-using-maven
// https://github.com/uPhyca/gradle-android-aspectj-plugin/blob/8d580d8117932a23209421193da77f175d19d416/plugin/src/main/groovy/com/uphyca/gradle/android/AspectjCompile.groovy
@TaskAction
def compile() {
    logger.info("Running ajc ...")

    MessageHandler handler = new MessageHandler(false);
    logger.info("args: $args")
    new Main().run(args as String[], handler);
    for (IMessage message : handler.getMessages(null, true)) {
        switch (message.getKind()) {
            case IMessage.ABORT:
            case IMessage.ERROR:
            case IMessage.FAIL:
                logger.error message.message, message.thrown
                break;
            case IMessage.WARNING:
                logger.warn message.message, message.thrown
                break;
            case IMessage.INFO:
                logger.info message.message, message.thrown
                break;
            case IMessage.DEBUG:
                logger.debug message.message, message.thrown
                break;
        }
    }

    ant.move(file: tmpDir, tofile: dstDir)
}

} ```

ネットで情報を gradle+aspectj の例を探していると、aspectj の ant task を利用する方法がいくつか出てくるが、aspectj の ant plugin の記述方法を元に gradle で ant task を呼ぶのは、 デバッグしにくいので難しい。aspectj の Main を呼ぶ方法が便利だと思う。 (もちろん、Main を呼ぶのはdocumentedなインターフェースではないので、壊れる可能性があるが、壊れたら別の Main を呼べばいいだけ)

この Main を直接叩く方法は、gradle の android で aspectj するプラグインを参考にした。

まあそういう感じでできるようになったので良かったね、と。

で、load time weaving についても、こんなに遅いわけもないと思うから、もうちょい頑張れば速くなるのかもしれないけど知見がまったくないです。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

dropwizard metrics servlet で spring-boot app のメトリクスをいい感じに取得する

spring-boot の MetricsFilter はしょぼい。ステータスコードごとのリクエスト数のカウンタと、最後にアクセスされたリクエストの処理時間ぐらいしか取得できない。

これは不便。Dropwizard ならデフォルトで以下の項目を取得可能。

  • status コードごとのrequest の件数のカウンタ
    • 件数/平均リクエスト数/過去1,5,15分の移動平均
    • *.responseCodes.*
  • 現在 web server が処理中の件数
    • "instrumented.activeRequests" : 1,
  • リクエストの平均処理速度など。
    • "instrumented.requests.count" : 2003,
    • "instrumented.requests.fifteenMinuteRate" : 2.2068657173426,
    • "instrumented.requests.fiveMinuteRate" : 6.51051261011083,
    • "instrumented.requests.meanRate" : 37.8061917507824,
    • "instrumented.requests.oneMinuteRate" : 29.5187887307098,
    • "instrumented.requests.snapshot.75thPercentile" : 4,
    • "instrumented.requests.snapshot.95thPercentile" : 18,
    • "instrumented.requests.snapshot.98thPercentile" : 40,
    • "instrumented.requests.snapshot.999thPercentile" : 135,
    • "instrumented.requests.snapshot.99thPercentile" : 72,
    • "instrumented.requests.snapshot.max" : 155,
    • "instrumented.requests.snapshot.mean" : 5,
    • "instrumented.requests.snapshot.median" : 2,
    • "instrumented.requests.snapshot.min" : 0,
    • "instrumented.requests.snapshot.stdDev" : 12,

こういった値を簡単に dropwizard-metrics-servlet を利用して取得するための設定方法を紹介する。

設定方法

依存に以下を入れる。

  compile 'io.dropwizard.metrics:metrics-core:3.1.2'
  compile 'io.dropwizard.metrics:metrics-servlet:3.1.2'

以下のように、Dropwizard Metrics Servlet に入っているフィルタを利用する。これだけ。

package com.example.config;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.servlet.AbstractInstrumentedFilter;
import com.codahale.metrics.servlet.InstrumentedFilter;
import com.codahale.metrics.servlet.InstrumentedFilterContextListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class InstrumentedFilterConfig {
    @Bean
    public FilterRegistrationBean instrumentedFilter() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new MyInstrumentedFilter());
        registration.addInitParameter("name-prefix", "instrumented");
        return registration;
    }

    public static class MyInstrumentedFilter extends AbstractInstrumentedFilter {
        private static final String NAME_PREFIX = "responseCodes.";
        private static final int OK = 200;
        private static final int CREATED = 201;
        private static final int NO_CONTENT = 204;
        private static final int BAD_REQUEST = 400;
        private static final int NOT_FOUND = 404;
        private static final int SERVER_ERROR = 500;

        /**
         * Creates a new instance of the filter.
         */
        public MyInstrumentedFilter() {
            super(InstrumentedFilter.REGISTRY_ATTRIBUTE,
                    createMeterNamesByStatusCode(),
                    NAME_PREFIX + "other");
        }

        private static Map<Integer, String> createMeterNamesByStatusCode() {
            final Map<Integer, String> meterNamesByStatusCode = new HashMap<Integer, String>(6);
            meterNamesByStatusCode.put(OK, NAME_PREFIX + "ok");
            meterNamesByStatusCode.put(CREATED, NAME_PREFIX + "created");
            meterNamesByStatusCode.put(NO_CONTENT, NAME_PREFIX + "noContent");
            meterNamesByStatusCode.put(BAD_REQUEST, NAME_PREFIX + "badRequest");
            meterNamesByStatusCode.put(NOT_FOUND, NAME_PREFIX + "notFound");
            meterNamesByStatusCode.put(SERVER_ERROR, NAME_PREFIX + "serverError");
            return meterNamesByStatusCode;
        }
    }

    // Pass MetricRegistry configured by Spring boot to InstrumentedFilter.
    @Bean
    public MyListener myListener() {
        return new MyListener();
    }

    public static class MyListener extends InstrumentedFilterContextListener {
        @Autowired
        private MetricRegistry metricRegistry;

        @Override
        protected MetricRegistry getMetricRegistry() {
            return metricRegistry;
        }
    }
}

これだけ設定すると、Actuator の /metrics に、以下の様な出力が出るようになる。


{
   "classes" : 5922,
   "classes.loaded" : 5922,
   "classes.unloaded" : 0,
   "counter.status.200.hello.id" : 1000,
   "counter.status.200.metrics" : 3,
   "counter.status.404.star-star" : 1000,
   "gauge.response.hello.id" : 3,
   "gauge.response.metrics" : 24,
   "gauge.response.star-star" : 105,
   "gc.ps_marksweep.count" : 1,
   "gc.ps_marksweep.time" : 85,
   "gc.ps_scavenge.count" : 9,
   "gc.ps_scavenge.time" : 151,
   "heap" : 1864192,
   "heap.committed" : 274944,
   "heap.init" : 131072,
   "heap.used" : 205912,
   "httpsessions.active" : 0,
   "httpsessions.max" : -1,
   "instance.uptime" : 52108,
   "instrumented.activeRequests" : 1,
   "instrumented.requests.count" : 2003,
   "instrumented.requests.fifteenMinuteRate" : 2.2068657173426,
   "instrumented.requests.fiveMinuteRate" : 6.51051261011083,
   "instrumented.requests.meanRate" : 37.8061917507824,
   "instrumented.requests.oneMinuteRate" : 29.5187887307098,
   "instrumented.requests.snapshot.75thPercentile" : 4,
   "instrumented.requests.snapshot.95thPercentile" : 18,
   "instrumented.requests.snapshot.98thPercentile" : 40,
   "instrumented.requests.snapshot.999thPercentile" : 135,
   "instrumented.requests.snapshot.99thPercentile" : 72,
   "instrumented.requests.snapshot.max" : 155,
   "instrumented.requests.snapshot.mean" : 5,
   "instrumented.requests.snapshot.median" : 2,
   "instrumented.requests.snapshot.min" : 0,
   "instrumented.requests.snapshot.stdDev" : 12,
   "instrumented.responseCodes.badRequest.count" : 0,
   "instrumented.responseCodes.badRequest.fifteenMinuteRate" : 0,
   "instrumented.responseCodes.badRequest.fiveMinuteRate" : 0,
   "instrumented.responseCodes.badRequest.meanRate" : 0,
   "instrumented.responseCodes.badRequest.oneMinuteRate" : 0,
   "instrumented.responseCodes.created.count" : 0,
   "instrumented.responseCodes.created.fifteenMinuteRate" : 0,
   "instrumented.responseCodes.created.fiveMinuteRate" : 0,
   "instrumented.responseCodes.created.meanRate" : 0,
   "instrumented.responseCodes.created.oneMinuteRate" : 0,
   "instrumented.responseCodes.noContent.count" : 0,
   "instrumented.responseCodes.noContent.fifteenMinuteRate" : 0,
   "instrumented.responseCodes.noContent.fiveMinuteRate" : 0,
   "instrumented.responseCodes.noContent.meanRate" : 0,
   "instrumented.responseCodes.noContent.oneMinuteRate" : 0,
   "instrumented.responseCodes.notFound.count" : 1000,
   "instrumented.responseCodes.notFound.fifteenMinuteRate" : 1.1078032687181,
   "instrumented.responseCodes.notFound.fiveMinuteRate" : 3.3036876086696,
   "instrumented.responseCodes.notFound.meanRate" : 18.8739613014361,
   "instrumented.responseCodes.notFound.oneMinuteRate" : 15.9438096464588,
   "instrumented.responseCodes.ok.count" : 1003,
   "instrumented.responseCodes.ok.fifteenMinuteRate" : 1.09906858728133,
   "instrumented.responseCodes.ok.fiveMinuteRate" : 3.20687964000899,
   "instrumented.responseCodes.ok.meanRate" : 18.929889559489,
   "instrumented.responseCodes.ok.oneMinuteRate" : 13.5762576633774,
   "instrumented.responseCodes.other.count" : 0,
   "instrumented.responseCodes.other.fifteenMinuteRate" : 0,
   "instrumented.responseCodes.other.fiveMinuteRate" : 0,
   "instrumented.responseCodes.other.meanRate" : 0,
   "instrumented.responseCodes.other.oneMinuteRate" : 0,
   "instrumented.responseCodes.serverError.count" : 0,
   "instrumented.responseCodes.serverError.fifteenMinuteRate" : 0,
   "instrumented.responseCodes.serverError.fiveMinuteRate" : 0,
   "instrumented.responseCodes.serverError.meanRate" : 0,
   "instrumented.responseCodes.serverError.oneMinuteRate" : 0,
   "mem" : 325332,
   "mem.free" : 69031,
   "nonheap" : 0,
   "nonheap.committed" : 51392,
   "nonheap.init" : 2496,
   "nonheap.used" : 50388,
   "processors" : 4,
   "systemload.average" : 3.869140625,
   "threads" : 24,
   "threads.daemon" : 22,
   "threads.peak" : 24,
   "threads.totalStarted" : 28,
   "uptime" : 60801
}

パフォーマンス

実際のところ、こんなに細かく数字とったらパフォーマンス劣化しないの?と気にする貧乏性の人もいると思う。 しかし実際、これは以下の程度のパフォーマンスがでるので、十分速いので気にしなくてよい。

Score:

benchHistogram:  1 wallclock secs ( 1.12 usr +  0.08 sys =  1.20 CPU) @ 3801478.48/s (n=4573152)
benchTimer:  1 wallclock secs ( 1.02 usr +  0.01 sys =  1.03 CPU) @ 3164606.01/s (n=3264693)
benchMetrics:  1 wallclock secs ( 1.15 usr +  0.04 sys =  1.19 CPU) @ 25211790.53/s (n=30020990)

Comparison chart:

                        Rate  benchHistogram  benchTimer  benchMetrics
  benchHistogram   3801478/s              --         20%          -85%
      benchTimer   3164606/s            -17%          --          -87%
    benchMetrics  25211791/s            563%        697%            --

まとめ

Dropwizard metrics を利用することで spring boot application のメトリクスを詳細に取得する方法について紹介した。 実際に動作するサンプルを以下のレポジトリに配置したのでご利用ください。

https://github.com/tokuhirom/spring-boot-dropwizard-metrics-filter-sample

SEE ALSO

参考: http://blog.64p.org/entry/2016/03/21/205935

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

How do I set TemplateExceptionHandler with spring-boot+freemarker?

You should use one of following properties.

-Dspring.freemarker.template_exception_handler=rethrow
-Dspring.freemarker.template_exception_handler=debug
-Dspring.freemarker.template_exception_handler=html_debug

Thanks,

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

jspm で angular2 を使ってみる。

jspm+systemjs を angular2 では推してるっぽいし使ってみるかとおもって使ってみるとなかなか一筋縄ではいかない。 ベータ! という感じがしてよい。

そういうわけで、jspm+systemjs でやってみた記録です。

node はすでにセットアップ済みという前提です。

npm install -g typescript jspm

として、必要なツールをインストールする。

次に jspm の初期設定をする。

$ jspm init
Package.json file does not exist, create it? [yes]:
Would you like jspm to prefix the jspm package.json properties under jspm? [yes]:
Enter server baseURL (public folder path) [./]:
Enter jspm packages folder [./jspm_packages]:
Enter config file path [./config.js]:
Configuration file config.js doesn't exist, create it? [yes]:
Enter client baseURL (public folder URL) [/]:
Do you wish to use a transpiler? [yes]:
Which ES6 transpiler would you like to use, Babel, TypeScript or Traceur? [babel]:typescript
(以下略)

なんかいろいろ聞かれてセットアップが完了する。

次に依存モジュールをインストールする。

jspm install angular2 reflect-metadata zone.js es6-shim typescript

config.js の中身を以下の様な感じにいじる。そんなに意味不明な設定はない。

System.config({
    baseURL: "/",
    defaultJSExtensions: true,
    transpiler: "typescript",
    typescriptOptions: {
        "module": "commonjs",
        "emitDecoratorMetadata": true
    },
    paths: {
        "github:*": "jspm_packages/github/*",
        "npm:*": "jspm_packages/npm/*"
    },

    packages: {
        "app": {
            "main": "main",
            "defaultExtension": "ts"
        }
    },

    map: {
        /* ここは自動生成される */
    }
});

で、app/main.ts に以下のように書く。基本的には、通常の angular2 アプリと一緒なのだが、依存を明示しないといけない点があってダサい。 この依存は config.js の中に 'meta' 項目として記載することも可能なのだが、まあ import を明示的にしておいたほうがわかりやすいだろうということで import した。

//import deps
import 'zone.js';
import 'zone.js/dist/long-stack-trace-zone';

import 'reflect-metadata';
import 'es6-shim';

// app code
import {Component} from 'angular2/core';
import {bootstrap} from "angular2/platform/browser";

@Component({
    selector: 'test-app',
    template: '<h4>Hello {{name}}</h4>'
})
class TestApp {
    name: string;

    constructor(){
        this.name = 'Angular2';
        setTimeout(() => {
        this.name = 'Angular2!!!'
        },1500);
    }
}

//start our app
bootstrap(TestApp);

最後に index.html に以下のように書く。

<html>
<head>
    <title>Demo App</title>
    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
</head>
<body>
    <test-app>
        Loading...
    </test-app>

    <script>
        System.import('app');
    </script>
</body>
</html>

bundle

jspm bundle-sfx --minify app

とするだけで bundle 版が作成可能。bundle-sfx で作成した場合、system.js も同梱されてるので、取り扱いが楽。

で、production 環境では以下のように、build.js だけを読めばOK.

<html>
<head>
    <title>Demo App</title>
</head>
<body>
    <test-app>
        Loading...
    </test-app>
    <script src="build.js"></script>
</body>
</html>

今後の展望

java app での良い感じの取り回しについて考えていきたい。

たとえば以下のような構成にするのが良いのではないかと考える。

src/main/java
src/main/resources/static/index.html ← build.js を読む
front/app/main.ts
front/index.html                     ← config.js 読んで、動的に処理

で、web server は file:front/,classpath:/static/ を static file として扱うようにして、gradle で jspm をキックして、build/resources/static/build.js を生成すればよい。

というところまで考えたのでよろしくお願いします。

まとめ

JS 系のツールは、なんかいろいろと設定が膨らみがちだが、jspm は最小限の設定ですむのでいい感じっぽい。 (最初 webpack でやろうと思っていたが、jspm の方が楽だった)

Java アプリとの連携については、ある程度、こんな感じでやればできるなあという感触をつかめた。

どうしてもブラウザでの確認がめんどくさいので、Ajax 通信部分のモッキング含めて、いい感じにテストできるようにしたい。そのへんもちゃんと読まないとな。 https://angular.io/docs/ts/latest/guide/testing.html

あと、本稿の内容は以下の gist の内容を参考に最新版でも動くようにしたものです。 https://gist.github.com/robwormald/429e01c6d802767441ec

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

spring boot アプリケーションのMetrics値取得について

Spring boot の metrics 機能について

spring boot には metrics 機能が標準でついている。metrics では Counter と Gauge の2種類のメトリクスを食わせることが可能である。

Counter は、単純に増加/現象する値を表すのに使うメトリクス値である。 たとえば、アクセスの件数、エラーの件数、現在利用中の値を取得したりするのに使う。

サンプルコードは以下の通り。

@Service
public class FooService {
    @AutoWired
    private CounterService counterService;
    @Autowired
    private FooRepository fooRepository;

    public Foo get(String key) {
        counterService.increment("foo");
        return fooRepository.get(key);
    }
}

Gauge は、単純に数値を渡すのに使う。普通に使うと、直近の一個の値がそのまま metrics 数字を取得できるだけなので、利用方法がちょっと思いつかない。 サンプルコードとしては以下の様な感じで利用される。

gaugeService.set("foo", 3.14);

Metrics 値の利用

Actuator での閲覧

特に設定せずに spring boot アプリケーションを起動すると、http://localhost:8080/metrics からアプリケーションのメトリクス値が取得可能になっている。

ここを閲覧して、変な値が出ていないか確認することができる。 ここを cron で取得して、alert を上げるなどの利用法が行われている。と思う。

外部レポジトリへの送信

spring-boot の metrics では、データを外部のミドルウェアに送信することができる。以下のミドルウェアに送信するための exporter が標準添付されている。

  • graphite
  • open tsdb
  • redis

gauge の値は actuator で確認するぶんには、最後の一個が見れるだけなので、使いにくいが、各値を外部 middleware に保存することで可能性が広がる。

Dropwizard Metrics との統合

dropwizard という web application framework には metrics という似たようなしくみがあるのだが、dropwizard の metrics の方がよく作りこまれている。。んですよ。 で、spring-boot の metrics は、classpath に dropwizard metrics があれば、そっちを使うように実装が差し替わります。

Dropwizard Metrics を利用するようにすると、gauge を取得するときに、prefix に 'histogram.' あるいは 'timer.' をつけると、生の値ではなく、min/max/stddev/percentile などの値が取得できるようになります。

Dropwizard metrics では、高速にメトリクス集計することが可能。Reservoir というコンポーネントで、集計を行っている。 Reservoir は性質のことなるによって幾つかの実装が存在している。

Uniform Reservoirs

Uniform Reservoirs は、Vitter's Algorithm R ってやつでランダムサンプリングして集計している。 長期間の間のメトリクスを取るのに向いてる。データの傾向が変わったことを検出するのとかには向いてない。

Exponentially Decaying Reservoirs

http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf 詳しくはこのへん。

過去、約5分間の傾向をしる事ができる。メトリクス傾向の変化を即座に検知したいなんてときにはこれ。 spring-boot-actuator の histogram 実装はこれをデフォルトとして選択している。 とりあえずこれをそのまま使っておけばいい。と思う。

Sliding Window Reservoirs

過去N個のデータを利用して値を出す

Sliding Time Window Reservoirs

過去N秒のデータを利用して結果を出す。データの流量が多いとメモリ量めっちゃ食って死ぬ。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

oh-my-zsh やめて zplug にしようかと思ったけどやっぱり zgen にした

mput さんの記事→ https://moneyforward.com/engineers_blog/2016/03/15/dotfiles/ を見て、zplug というものの存在を知った。

zplug を設定しようと思ったが、そもそも zsh の設定をこまごまとやるのがあんま好きじゃないし、コマンドのインストールとかまで出来るのが zplug だということで、ややオーバースペックに感じた。 いろいろ見ていると zgen というのが好みにあってる感じっぽかったので zgen にした。

source "${HOME}/.zgen/zgen.zsh"

if ! zgen saved; then
    zgen oh-my-zsh

    zgen oh-my-zsh plugins/git
    zgen oh-my-zsh plugins/ssh-agent

    zgen load zsh-users/zsh-syntax-highlighting
    zgen load "zsh-users/zsh-completions"

    if [[ $HOST = 'www3301gi.sakura.ne.jp' ]]; then
        zgen oh-my-zsh themes/clean
    else
        zgen oh-my-zsh themes/sonicradish
    fi

    zgen save
fi

oh-my-zsh の機能もそんなに使ってなかったし、まあこれでいいかという感じ。

mput さんがおすすめしていた predict on も試してみたが、git stash ってウトウトしたらうっかり git stash pop 実行してしまったりして、イライラするばかりだったのでやめました。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

spring で controller 書くほどでもないんだけどテンプレートエンジンでレンダリングして欲しい時のやり方

addViewControllers ッテの使うと良いっぽい。

@Configuration
@Slf4j
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
}

もともと java だと JSP でゴーリゴリみたいな世界観の人もいるので、そういう人が使いやすいようにする処置っぽい。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

Spring の Freemarker で参照可能な変数について

Spring で freemarker を利用している場合、いくつかの変数がデフォルトで利用可能になっている。

    protected SimpleHash buildTemplateModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
        AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel(getObjectWrapper(), getServletContext(), request);
        fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory);
        fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel);
        fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response));
        fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(request, response, getObjectWrapper()));
        fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request));
        fmModel.putAll(model);
        return fmModel;
    }

https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/view/freemarker/FreeMarkerView.java#L295-L304

これはつまり、Freemarker がデフォルトで提供している Servlet と同等の変数を埋めているということになる。 参考: http://freemarker.incubator.apache.org/docs/pgui_misc_servlet.html

  • JspTaglibs
  • Application
  • Session
  • Request
  • RequestParameters

といった値が利用可能なので、場合によったら利用してもよい。(controller できちんと処理したほうがよいケースも多いので、乱用しないように気をつけるべし)

たとえば、Spring Security の login form などは以下のように書けばよいってなわけ。(というか spring security のサンプルコードが thymeleaf だったので悩んでて見つけた)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
>
<head>
    <title>Spring Security Example </title>
</head>
<body>
<#if RequestParameters.error??>
Invalid username and password.
</#if>
<#if RequestParameters.logout??>
You have been logged out.
</#if>
<form action="/login" method="post">
    <div><label> User Name : <input type="text" name="username"/> </label></div>
    <div><label> Password: <input type="password" name="password"/> </label></div>
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
    <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

[spring] spring mvc で、一部のコントローラは先に処理させたい時のライフハック

/{user_id} はエンドユーザーのページだけど、 /entrylist は entry の list が出る、みたいな、ちょっと アレな URL 設計になってしまったサイトを spring mvc でどのように制御するか。

↓に書いてあるとおり、SimpleUrlHandlerMapping で処理できる。 http://www.mkyong.com/spring-mvc/configure-the-handler-mapping-priority-in-spring-mvc/

ということはわかったら、"SimpleUrlHandlerMapping spring boot" とかでググると、Java configuration での書き方がみつかる。

↓以下参照。 See http://stackoverflow.com/questions/25037113/java-configuration-of-simpleurlhandlermapping-spring-boot

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

fluentd を手元で動かしてデバッグしたいとき

以下のようにして fluentd をインストール。

gem install fluentd --no-ri --no-rdoc

/etc/fluent/fluent.conf に以下のようにかく。

<source>
    type forward
</source>
<match *.**>
    type stdout
</match>

んで、以下のようにして起動。

fluentd --no-supervisor --verbose

起動後は、メッセージ確認可能。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

【解決済み】jackson 2.7 以後は @ConstructorProperties を参照するようになっているので、lombok を使っている場合に何も考えずにアップグレードすると死ぬ

Jackson 2.7 がリリースされていますが、このバージョンから @ConstructorProperties を参照するようになっています。

See

@java.beans.ConstructorProperties は、Java Beans の標準にある annotation です。 これにより、引数を持つコンストラクタを Jackson で利用できるようになります。

たとえば、以下のように、 immutable なオブジェクトを Jackson で扱えるようになるのです!

public class Point {
    @ConstructorProperties({"x", "y"})
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    private final int x, y;
}

そういうわけで、便利なんですが、ここに一つ落とし穴があります。

lombok の @AllArgsConstructor または @Value を利用していて、かつ @JsonProperty でフィールド名を変更している場合です。 つまり、以下のようなケース。

@AllArgsConstructor
@Value
public class Foo {
    @JsonProperty("y")
    private String x;
}

この場合、生成コードは以下のようなものになります。

public class Foo {
    @JsonProperty("y")
    private String x;

    public String getX() { return this.x; }

    @ConstructorProperties({"x"})
    public Foo(String x) { return this.x; }
}

で、こうなった時に、Jackson 氏は @ConstructorProperties@JsonProperty で割り当てられてる名前が違うやんけ! と文句を言ってくるわけです。

再現コードは以下のようになります。

import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;

import lombok.AllArgsConstructor;
import lombok.Data;

public class Boo {
    @Data
    @AllArgsConstructor
    public static class Foo {
        @JsonProperty("foo_id")
        private String fooId;
    }

    public static void main(String[] args) {
        ObjectMapper objectMapper = new ObjectMapper();
        JavaType javaType = objectMapper.getTypeFactory().constructType(Foo.class);
        BeanDescription desc = objectMapper.getSerializationConfig().introspect(javaType);
        List<BeanPropertyDefinition> properties = desc.findProperties();
        System.out.println(properties);
    }
}

で、この挙動を抑制するには、@ConstructorProperties の付与を停止すればよいです。

プロジェクトのルート・ディレクトリに lombok.config というファイルを配置します。 build.gradle と同じディレクトリにおけばOK.

このファイルに以下のように記述すれば、@ConstructorProperties の生成が抑制されるので問題なくなる。

lombok.anyConstructor.suppressConstructorProperties = true

そんな感じです。

【2016.09.08 追記】 最新版では問題発生しなくなっています。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900
Next page