tokuhirom's blog

JMX でシステムの状況をざっくり確認したい

Perl の世界では、よく管理画面にヘルスチェック的な数字等を表示する機能を組み込みますが、Java の世界では JMX で見れるようにしておくのが一般的なようです。


JMX の使い方を調べてみると、出自が古いこともあって、古い情報が多いのと、使える機能を細かく説明しているサイトが多く、実際の僕のユースケースにあう形の実装方法に行き着くまでに時間がかかりました(得てしてプログラミング関係の情報、「これもできます」「あれもできます」という説明が多くて、実際のユースケースにたどりつくのに時間がかかるものですね。Java にかぎらず)。

実際、ゆるふわに JMX にメトリックス数字を追加するだけなら、そんなに考えることは多くないことがわかりました。


まず、以下のようなインターフェースを定義します。なんかゲッターっぽいのを定義しておけばよいです。 名前は MBean で終わる必要があるとのこと。public にしておかないと死にます。

public interface HelloMBean {
    public String getHello();

    public String getName();
    public void setName(String name);
}

そして実装クラスを作ります。得に難しいことはないです。普通につくればよい。

private static class Hello implements HelloMBean {
    private String name;

    public String getHello() {
        System.out.println("MBEEAAAAN");
        if (name == null) {
            return "Hello, world";
        } else {
            return "Hello, " + name;
        }
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }
}

あとは、実際に MBeanServer に登録すればよろしい。

public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException, IOException {
    // visualvm などでどれを見たらいいのか分かりやすくするために、PID を表示しておく。
    System.out.println(ManagementFactory.getRuntimeMXBean().getName());

    final MBeanServer mbeanServer =
            ManagementFactory.getPlatformMBeanServer();
    ObjectName objectName = new ObjectName("me.geso:type=MBeanSample");
    final Hello helloMbean = new Hello();
    mbeanServer.registerMBean(helloMbean, objectName);

    // 試すためにメインスッドレに待っててもらう
    System.in.read();
}

後はまあ、visualvm-mbeans プラグインを visualvm に入れて、確認すればいい。jconsole で見てもいい。


JMX 自体、太古より伝わる技術なので、最近の Java に比べると使い方がだいぶ違う。のでとっつきにくい。 最近の Java ならば以下のように、アノテーションでやるところだと思う。

private static class Hello {
    @JMXDeMireruYo
    public String getHello() {
        System.out.println("MBEEAAAAN");
        return "World";
    }
}

とか思ってたら、なんかあるっぽい? javax.management.ManagedAttribute でできるっぽいけどよくわからんな。。 EE のアレの側にアノテーション入ってるっぽいし、がんばって使う必要があるのかよくわからん。

http://tomee.apache.org/examples-trunk/mbean-auto-registration/README.html


Setter でデータを変更することもできるので、ちょっとした管理上の操作などを行う事もできるんだと思う。 閾値を一時的にいじるとか。。


DynamicMBean とやらを使えば、インターフェースを実装するのではなく、フレキシブルに項目を変動させることが可能だが、まーなんというかけっこうダルそうだったので、必要になるまで考えないことにした。


JMX を使うと java 以外の言語で数字とるのが面倒という説もありますが、その場合は JVM に jolokia (httpd を立ち上げて JSON API として metrics 数字を取得可能にするやつ)をロードすればいいのであまり大した問題ではないです。

Created: 2014-12-17 08:11:16
Updated: 2014-12-17 08:11:16

[java] Math.abs(Integer.MIN_VALUE) するとき

Java において Math.abs(Integer.MIN_VALUE)Integer.MIN_VALUE を返す。

MINVALUE が -2147483648 で MAXVALUE が 2147483647 なので、まあそういうものです。

で、通常はそれで問題がないのだが、Math.abs(SecureRandom.getInstance("SHA1PRNG").nextInt()); とかやってると極めて稀にネガティブバリューになる可能性があるからやめろ!! って findbugs に言われたので、今後はしないようにしようと思います。

結果として、今回は心底どうでもいいところですが、テストコードの中だったので Math.abs((long)SecureRandom.getInstance("SHA1PRNG").nextInt()); としました。

Created: 2014-12-16 18:23:37
Updated: 2014-12-16 18:23:37

json に値を直接書く話

昔の JSON は Object か Array しかルートに書けなかったけど RFC 7159 では true/false/null/number/string を書けるようになってて便利だっておじいちゃんが言ってました。

Created: 2014-12-14 18:18:26
Updated: 2014-12-14 18:18:26

avans 0.39.0 における @QueryParam と @BodyParam の廃止について

Avans に非互換の変更を入れます。

移行処置として、次のバージョンでは @Deprecated をつけた状態で一旦リリースします。その次のバージョンで削除します。

なにが問題か

Servlet Filter で HttpServletRequest#getParameter() を呼んでしまうと、POST で application/x-www-urlencoded または multipart/form-data の場合に content-body が読めない。

なぜ起きるか

現行の実装では HttpServletRequest#getInputStream() から POST body を自前でパースしている。Servlet Filter が getParameter() を呼んでしまうと input stream が消費され、POST body を処理できなくなる。

互換性を維持したままの解決策はないか

HttpServletRequest#getInputStream() の結果から Query parameter の値を引いたものを body parameter とするように変更することにより互換性を維持することは可能である。

互換性を維持する必要はあるか?

できれば維持したいが、そもそも query parameter と body parameter をわけて処理することに実用上の意味はないので、そこを頑張って実装してもしょうがない。

もともと分けたのは JAX-RS を意識しすぎた結果なので。。。

ついでに、、

commons-fileupload への依存を取り除き、Servlet 3.0 の upload API を利用するようにする。

移行プランについて

  • webscrew 0.9.0 で WebRequest, WebRequestUpload 等を @Deprecated とする
  • avans 0.39.0 で @QueryParam と @BodyParam を @Deprecated とし、@Param を追加する
    • @QueryParam と @BodyParam は @Param のエイリアスとなる(非互換)
    • javax.servlet.http.Part のサポートを追加する
    • WebRequest を直接とれる getRequest() が廃止され getServletRequest() が新設
  • webscrew 0.10.0 で前バージョンで Deprecated にしたものを削除する。リリース時期は一週間後を予定。
  • avans 0.40.0 で前バージョンで Deprecated にしたものを削除する。リリース時期は一週間後を予定。

移行方法

  • perl -pe 's/\@(Query|Body)Param/\@Param/g' src/**/*.java とする
  • WebRequestUpload を使っている部分を javax.servlet.http.Part に変更する
  • Controller#getRequest() を叩いているところを Controller#getServletRequest() 使うように変更
  • Controller#getParameters() を利用しているところを Controller#getParams() 使うように変更

移行時に留意するべき点について

File upload まわりについてはサーブレットコンテナの設定が必要な場合があるので注意。 Embedded jetty の場合には以下のように設定する。

servletHolder.getRegistration().setMultipartConfig( new MultipartConfigElement(tmpDirName));

Created: 2014-12-08 10:40:11
Updated: 2014-12-08 10:40:11

Validation Night で話してきた

なんかしらんけどスライドアップしようとしたら slideshare に拒否られてるので悲しみ溢れております。

とりあえず思ってることをいくつか。

相関バリデーションどうよ?

ベーシックな単項目の Validation 以上のものを Validation framework でやろうとするのは悲しみしか産まないので、たとえば以下のようにしています。

List<String> messages = new Validator().validate(params);
if (params.isHojin()) {
    if (!params.has代表者名()) {
        messages.add("法人は代表者名いれてね!");
    }
} else {
}

そこあんま気張ってもね、的な。

HTTP で Validation error 上げるとき 200 を返すべきか

200 を返すべきだと思います。

僕はあまり REST 好きではなく、RPC 派なので。。

RPC の概念でいいますと、エンドポイントのコールそのものの状態を返すのに HTTP Status コードを利用し、処理の詳細については response body にて述べるのが極めて自然であります。

あとまあ、弊社の場合、fluentd でログを集めて、閾値以上に 4xx 出てたらアラート上がるみたいなことをやっており、そういったインフラを利用するためにはそういうふうになっていた方が便利だという事情もあります。

Servlet について

Servlet は DELETE とかに対応してないのでは?みたいな話があったが、今は普通に doDelete とかもサポートされているようだ。昔どうだったかはしらない。

Bean Valiation どうよ?

Bean Validation の JSR は、なんというかこう、実装をドキュメント化しただけで、あれを自分たちで実装しようとは思えない感じですよね。。

Created: 2014-12-05 09:32:43
Updated: 2014-12-05 09:32:43

avans アプリの処理フロー

avans にかぎらず、java の web application 全般そうですが、

src/main/webapp/WEB-INF/web.xml がエントリポイントになっています。

最近では Spring boot や Dropwizard などのような、web.xml いらずのものもありますが、avans では web.xml で記述することにしています。

web.xml はコンテナがいい感じに読んで実行してくれると思いねえ。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>me.geso.avans.AvansServlet</servlet-class>
    <init-param>
      <param-name>class</param-name> 
      <param-value>com.example.helloworld.Main$Foo</param-value> 
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

servlet を定義して、それをマッピングしています。

これは以下のような擬似コードの挙動になることを示しています。

val config = new ServletConfigImpl();
config.setInitParameter('class', 'com.example.helloworld.Main$Foo');
val hello = new AvansServlet();
hello.init(config);
tomcat.addServletMapping(hello);

AvansServlet は初期化パラメータとして class と package をサポートしており、クラス一個一個を登録するかパッケージ単位で登録するかを選べます。複数個登録したいときはコンマで区切って登録します。 https://github.com/tokuhirom/avans/blob/master/avans/src/main/java/me/geso/avans/AvansServlet.java#L26

各コントローラは Controller インターフェースを実装している必要があり、Controller クラスを実装していない場合、ルーターに登録されません。 https://github.com/tokuhirom/avans/blob/master/avans/src/main/java/me/geso/avans/Dispatcher.java#L61

AvansServlet はすべてのリクエストを受け取って、各コントローラにディスパッチしていきます。 https://github.com/tokuhirom/avans/blob/master/avans/src/main/java/me/geso/avans/AvansServlet.java#L106

Dispatcher#handler はこのへんで、routes をよんで、コントローラを特定して、new して呼んでる。 https://github.com/tokuhirom/avans/blob/master/avans/src/main/java/me/geso/avans/Dispatcher.java#L106

まあ、そんな感じです。

Created: 2014-12-05 09:03:41
Updated: 2014-12-05 09:03:41

Shipped routes 0.4.0

Now, "/{x}" pattern allows '.', '-', '_'.

Created: 2014-12-05 08:40:10
Updated: 2014-12-05 08:40:10

com.mysql.jdbc.PreparedStatement.executeUpdate が Can not issue executeUpdate() for SELECTs と言ってくる件

com.mysql.jdbc を使っていて、executeUpdate で SELECT 文を発行しようとした場合、以下のような例外があがる。

Caused by: java.sql.SQLException: Can not issue executeUpdate() for SELECTs
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:996)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:935)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:924)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:870)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2113)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2077)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2062)

この例外は以下の位置から発生している。

        if ((this.firstCharOfStmt == 'S') && isSelectQuery()) {
            throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), "01S03", getExceptionInterceptor());
        }

isSelectQuery の実装は以下のようになっている。

protected boolean isSelectQuery() throws SQLException {
    synchronized (checkClosed().getConnectionMutex()) {
        return StringUtils.startsWithIgnoreCaseAndWs(StringUtils.stripComments(this.originalSql, "'\"", "'\"", true, false, true, true), "SELECT");
    }
}

こういうのをドライバが実装するのは、あまり筋がよくないように思うが、まあそうなっているよ、ということで。

Created: 2014-12-03 15:22:13
Updated: 2014-12-03 15:22:13

maven archetype の作り方

Java の世界では maven archetype を作成して、それを使って行うのが基本となっている。

なんだかめんどくさそうなので LL で生成していたのだが、やはりその LL のスクリプトをインストールさせる方法が特になく、辛い。

ということで諦めて maven archetype の作成方法を学ぶことにしたわけであります。


maven archetype の作成方法は公式サイトのこのあたりにのっています。 http://maven.apache.org/guides/introduction/introduction-to-archetypes.html

archetype は velocity を用いて生成されます。

チュートリアルがここにあるので、これを順番にやっていけばそれなりのことはできる。 http://maven.apache.org/guides/mini/guide-creating-archetypes.html

最低限の前提として以下のようなディレクトリ構成となっていることを覚えておく必要がある。

pom.xml
src/main/resources/META-INF/maven/archetype.xml
src/main/resources/archetype-resources/ この中にテンプレートを入れる

pom.xml がいわゆるいつもの pom.xml であって、いつもどおりに作成すればよい。

src/main/resources/META-INF/maven/archetype.xml が archetype の設定ファイルであり、ここにこまかな設定を書けばいい。

チュートリアルに書いてある例だと以下のようになっている。

    <archetype xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0 http://maven.apache.org/xsd/archetype-1.0.0.xsd">
        <id>quickstart</id>
        <sources>
            <source>src/main/java/App.java</source>
        </sources>
        <testSources>
            <source>src/test/java/AppTest.java</source>
        </testSources>
    </archetype>

ここで、ディレクトリ構成は以下のようになっている、と。

archetype
|-- pom.xml
`-- src
    `-- main
        `-- resources
            |-- META-INF
            |   `-- maven
            |       `--archetype.xml
            `-- archetype-resources
                |-- pom.xml
                `-- src
                    |-- main
                    |   `-- java
                    |       `-- App.java
                    `-- test
                        `-- java
                            `-- AppTest.java

ここからたとえば com.example.foo プロジェクトを生成しようとしたら src/main/java/com/example/foo/App.java にファイルが生成されてほしいとか思うわけだが、その辺は sources に書いてある時点で、よしなに配置してくれる。

src/main/resources/archetype-resources/pom.xml は以下のように記述すれば、よい。${groupId} のようになっている部分はコード生成時に置換される。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>${groupId}</groupId>
    <artifactId>${artifactId}</artifactId>
    <version>${version}</version>
    <packaging>jar</packaging>

    <name>A custom project</name>
    <url>http://www.myorganization.org</url>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

できあがったら、あとは

    mvn install

として、~/.m2/repository/ にコピーする。以下のコマンドで試せる。

    mvn archetype:generate                                  \
        -DarchetypeGroupId=<archetype-groupId>                \
        -DarchetypeArtifactId=<archetype-artifactId>          \
        -DarchetypeVersion=<archetype-version>                \
        -DgroupId=<my.groupid>                                \
        -DartifactId=<my-artifactId>

と、ここまでが tutorial の内容である。

チュートリアルだけでリファレンスがないよ?

見当たらないけど、なんかググれば stackoverflow が出てきてなんとかなるよ。

README.md も生成したいよ?

README.md とかを生成させる方法がさっぱりわからないのだが、これは簡単。

osrc/main/resources/META-INF/maven/archetype.xml で resource として指定してあげればOK。その他の、うまくコピーされないファイルもだいたい resource 指定でいけそう。

<?xml version="1.0" encoding="UTF-8"?>
<archetype xmlns="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/plugins/maven-archetype-plugin/archetype/1.0.0 http://maven.apache.org/xsd/archetype-1.0.0.xsd">
    <id>avans-setup</id>
    <resources>
        <resource>src/main/webapp/WEB-INF/web.xml</resource>
        <resource>src/main/resources/templates/index.mustache</resource>
        <resource>README.md</resource>
    </resources>
</archetype>

archetype 側のバージョンを埋め込んだりしたい

archetype を単体でリリースする場合もあるが、フレームワークと同じレポジトリで管理され archetype は sub project として管理されている場合も多い。その場合、archetype で生成する pom.xml では最新版を常に指定したい。

たとえば avans の場合だと以下のようなディレクトリ構成となっていて、parent pom でバージョン番号を指定している。

avans/pom.xml   ← parent pom
avans/avans/pom.xml
avans/avans-setup/pom.xml

このような場合、どうやって

avans/avans-setup/src/main/resources/archetype-resources/pom.xml

にバージョン情報を伝えたらよいか。

avans/avans-setup/pom.xml に以下のような記述をする。これにより、src/main/resources の中のファイルの @jetty.version@ のような記述の部分にバージョン番号などが埋め込み可能となる。

<build>
    <plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>2.6</version>
        <configuration>
        <!-- Redefining delimiters. So that we can filter the -->
        <!-- archetype resources. -->
        <!-- The regular delimiter is already used by the archetype -->
        <!-- generation process.  -->
        <!-- By redefining the delimimter we can access  -->
        <!-- variables like @jetty.version@ or @project.version@ -->
        <useDefaultDelimiters>false</useDefaultDelimiters>
        <delimiters>
            <delimiter>@</delimiter>
        </delimiters>
        </configuration>
    </plugin>
    </plugins>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>**/*</include>
            </includes>
        </resource>
    </resources>
</build>

この状態で、src/main/resources/archetype-resources/pom.xml に以下のように記述すればよい。

<modelVersion>4.0.0</modelVersion>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>${artifactId}</name>
<description></description>
<packaging>war</packaging>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <avans.version>@project.version@</avans.version>
    <tomcat.version>7.0.56</tomcat.version>
</properties>

ひな形いじるのが辛いよ、って人~

こういうふうに、sed で置換してひな形つくるのが良いとのこと。

https://github.com/making/spring-boot-blank/blob/master/create-maven-archetype.sh

まとめ

maven archetype 思ったより使いやすい。maven 動かしまくるから動作確認にやや時間がかかるが、それを除けば快適である。

社内向けの archetype も社内 maven repo にあげておけばいい。

Created: 2014-11-30 11:33:11
Updated: 2014-11-30 11:33:11

Shipped avans 0.37.0

I shipped avans 0.37.0.

It includes following exceptions:

  • Show full stack trace in full stack trace logs.
  • Fixed null pointer exception in JSON parsing error.
Created: 2014-11-28 08:15:43
Updated: 2014-11-28 08:15:43