10分で出来る!初めてのTwitter4J & Twitterアプリ作り方メモ in 2015-10-18

最近はJava EE系でもJAX-RS研修の担当が多くなっている関係上、OAuthやRESTクライアントにも興味が出てきました。ということで、初めてTwitter4Jでプログラムを作ってみました。参考にしたのは、@kikutaro_さんのこちらのブログです。

Twitter4Jを使ったら10分でつぶやきJavaプログラムが作れました! ~NetBeans編~ - Challenge Java EE !

分かりやすくまとまっていて、基本的な手順は今も変わっていないのですが、管理画面のUIが変わっていたので、本日時点での画面キャプチャ付きで解説します。

目標

Twitter4Jを使って、Javaのmain()メソッドからつぶやきを送信する!

Twitter Developer登録

プログラムを作る前に、Twitter Developer登録を行って、アプリ開発に必要なアクセストークンなどの情報を取得します。この手順は、JavaやTwitter4Jによらないはずです。

まず、下記のTwitterのDeveloperサイトにアクセスします。

https://dev.twitter.com/

すると、トップ画面が表示されます。画面右上のリンクから、ご自分のTwitterアカウントでサインインしてください。このアカウントは、普段のつぶやきなどで使っているアカウントでOKです。

f:id:MasatoshiTada:20151018133412p:plain

トップ画面で下の方にスクロールして、[TOOLS]-[Managing Your App]をクリックします。

f:id:MasatoshiTada:20151018134406p:plain

すると、アプリケーションの管理画面に移ります。まだ何もアプリケーションを作っていないので、[Create New App]ボタンだけが表示されます。

f:id:MasatoshiTada:20151018134644p:plain

では、[Create New App]ボタンをクリックします。すると、アプリケーションの作成画面に移ります。適当なアプリケーション名、アプリの説明、自分のブログなどのURLを入力します。[Callback URL]は必須項目ではないので、今回は空欄のままにします。

f:id:MasatoshiTada:20151018134833p:plain

上記の画面で下の方にスクロールすると、[Developer Agreement]が記載されているので、内容を確認した上で[Yes, I agree]にチェックを入れて、[Create your Twitter app]ボタンをクリックします。

f:id:MasatoshiTada:20151018135333p:plain

そうすると、下記のような画面が表示されます。[Consumer Key]の部分は、後で使います。実際には英数字&記号の羅列が表示されていますが、秘密の情報のため画像を加工して伏せています。

f:id:MasatoshiTada:20151018143316p:plain

次に、この画面の[Keys and Access Tokens]タブをクリックします。表示されている[Consumer Key (API Key)]と[Consumer Secret (API Secret)]という2つの文字列は、後で使います。

f:id:MasatoshiTada:20151018143357p:plain

この画面で下の方にスクロールして、[Create my access token]ボタンをクリックします。

f:id:MasatoshiTada:20151018143448p:plain

画面が遷移するので、また下の方にスクロールすると、[Access Token]と[Access Token Secret]が表示されています。この2つの文字列も、後で使います。

f:id:MasatoshiTada:20151018143521p:plain

以上で、準備は完了です。

Twitter4JでJavaプログラムを作る

さて、後は簡単です。IDEEclipseでもNetBeansでもIntelliJ IDEAでも何でも構いません。ビルドツールは、MavenまたはGradleを使います。

まず、Twitter4Jを依存性に含めます。

Gradleならこうです。

compile 'org.twitter4j:twitter4j-core:4.0.4'

Maven

<dependency>
    <groupId>org.twitter4j</groupId>
    <artifactId>twitter4j-core</artifactId>
    <version>4.0.4</version>
</dependency>

次に、src/main/resourcesに「twitter4j.properties」というプロパティファイルを作ります。そして、下記のように記述します。

debug=true
#右辺にConsumer Key (API Key)をコピーして貼り付ける
oauth.consumerKey=XXXXXXXXXXXXXXXX
#右辺にConsumer Secret (API Secret))をコピーして貼り付ける
oauth.consumerSecret=XXXXXXXXXXXXXXXXXX
#右辺にAccess Tokenをコピーして貼り付ける
oauth.accessToken=XXXXXXXXXXXXXXXX
#右辺にAccess Token Secretをコピーして貼り付ける
oauth.accessTokenSecret=XXXXXXXXXXXXXXXX

src/main/javaにクラスを作ります。菊田さんのブログのプログラムそのままです(^^;

import twitter4j.*;

public class UserInfoMain {
    public static void main(String[] args) throws TwitterException {
        Twitter twitter = TwitterFactory.getSingleton();
        User user = twitter.verifyCredentials();
        System.out.println(user.getName());
        System.out.println(user.getScreenName());
        System.out.println(user.getFriendsCount());
        System.out.println(user.getFollowersCount());

        Status status = twitter.updateStatus("Twitter4Jから初めてのツイート! #twitter4j");
    }
}

これだけです。いざ、実行します。すると・・・

多田真敏
suke_masa
63
199
. . . (他にもデバッグログがいっぱい表示されます)

f:id:MasatoshiTada:20151018142359p:plain

おおお、自分のユーザー情報が表示されて、つぶやきが投稿されてる!

まとめ

  • Developer登録をして、アクセストークンなどの情報を取得する
  • アクセストークンなどは、コピーしてtwitter4j.propertiesに貼り付ける
  • Twitter4J使えば、REST APIやらJSONやら認証やらのコードは書かなくてOK!

感想

あまりに簡単すぎて感動すら覚えました・・・。

Twitter4Jのソースも、これから読んでみたいと思います!

Payara(GlassFish)でMariaDBのJDBC接続プールが作成できない

表題の通りです。

環境

試していませんが、おそらくWindowsGlassFishでも同じ現象が起こると思います。

現象

  • MariaDBJDBCドライバのJARを「<PAYARA_HOME>/glassfish/lib」や「<PAYARA_HOME>/glassfish/domains/domain1/lib」に置く
  • Payaraを起動してlocalhost:4848にアクセスし、管理コンソールからURL/USER/PASSWORDを設定する

という一般的な手順で作成しても、MariaDBへのpingが通りません。

ping時のPayara管理コンソール(ブラウザ上)でのエラーメッセージは、こんなのが出ます。

Access denied for user ‘’@’localhost’ (using password: NO) 

どうやら、ユーザー名やパスワード設定が間違っている時に出てくるMariaDBのエラーメッセージのようなのですが、全く同じURL/USER/PASSWORDでメインメソッドから普通にJDBC接続したら出来ました。 (URL/USER/PASSWORDはPayara管理コンソールからコピペしたので間違いないはずです)

Payaraのプロパティ名も間違えていません。

しかも、ごくたまにpingが通ります。しかし、またすぐにpingが通らなくなります。

対策

今回は、MariaDBをPayaraで使うこと自体が目的ではないので、MySQL 5.6に変更しました。そうすると無事にJDBC接続プールが作成できました。

原因は分かりませんが、Payara管理コンソールで選択するときのDBの種類にはMariaDBは入っていないし、Payara(というか恐らくGlassFish)はMariaDBに対応していないのかもしれません。 (完全に僕の憶測です)

注意

この問題は、あくまでPayara+MariaDBの組み合わせによるものです。

上述の通り、通常のJDBC接続であればMariaDBは問題なく動作しました。

【注意事項あり】Doma 2だけどCDI/EJB使ってJTAでトランザクション管理したい!そしてJAX-RSでREST作りたい!

Doma 2とは?基本的な使い方は?

Doma 2は、SQLを外部ファイルに書くことができるORマッパーです。ネイティブSQLが書けること、依存ライブラリが無い事、国産 OSSで日本語ドキュメントが充実している事などが魅力です。

下記の記事もご参考になさってください。2014年のJavaアドベントカレンダー向けに書いたものです。

美しき青きDoma!~SQLとIDEが奏でる美しきORマッピング~ - Java EE 事始め!

Domaでは、プログラマが作るのはHogeDaoインターフェイスだけで、その実装クラスHogeDaoImplはGradleでビルド時にAnnotation Processorで自動生成されます。

CDI/EJB使ってHogeDao型のフィールドにHogeDaoImplのインスタンスをインジェクションしたり、JTAトランザクション管理するには、実装クラス側(HogeDaoImpl)に@RequestScoped(CDI)や@Stateless(EJB)などのアノテーションを付加する必要があります。

しかし、実装クラスはビルド時に生成されるし、生成後に手作業でアノテーションを付けても、再ビルド時に上書きされて消えてしまいます。

アノテーションをつける方法がずっと分からなかったのですが、遂にやり方が分かったのでご紹介します。

ソースはGitHubに公開しています。

MasatoshiTada/doma-jaxrs · GitHub

APサーバーはPayara Web 4.1.153ですが、Jerseyには依存しないように書いたので、WildFlyでもそのまま動作します(9.0.1.Finalで確認済み)。DBはMySQL 5.6、IDEIntelliJ IDEA 14.1.5です。

CDI/EJBJTAについて

これ以降の内容は、CDI/EJBJTAの知識が前提となります。下記の資料が参考になります。

JavaDayTokyo2015での寺田さんの発表資料です。CDIについて詳しくまとまっています。

http://www.oracle.co.jp/jdt2015/pdf/2-2.pdf

@opengl-8080さんのJTAの解説ブログです。いつもながら勉強になります。

JavaEE使い方メモ(JTA) - Qiita

アノテーションをつける答えは@AnnnotateWith

ツイッターである方々のやり取りを見ていたら、Doma 2の公式ドキュメントの一部が目に入りました。

http://doma.readthedocs.org/ja/stable/config/#id22

@AnnnotateWithというアノテーションを使っていますね。これがポイントです。

1.アノテーションの自作

まず、こんなアノテーションを自作します。自作するアノテーション名は任意ですが、公式ドキュメントに従って@InjectConfigという名前にしておきます。

CDIの場合
package com.example.dao.config;

import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;

@AnnotateWith(annotations = {
        @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class)
})
public @interface InjectConfig {
}
EJBの場合
package com.example.dao.config;

import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;

import javax.ejb.Stateless;
import javax.inject.Inject;

@AnnotateWith(annotations = {
        @Annotation(target = AnnotationTarget.CLASS, type = Stateless.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class)
})
public @interface InjectConfig {
}

注意!! Payara 4.1.1.154までのAPサーバーでは、CDI+JPA以外のORマッパー(というよりJDBC)では、JTAが正しく動作しない可能性があることが判明しました。なので、現段階ではEJBを使ってください。このことについては、PayaraのGitHubにIssueとして報告済みです。次バージョンでの改善を期待しています。WildFlyでは、CDIでもEJBでも正しく動作します。 参考URL @OpenGL_8080さんのブログ(コメント欄をご確認ください) JavaEE使い方メモ(JTA) - Qiita PayaraのGitHub Payara does NOT rollback when RuntimeException occurs in CDI @Transactional method using JDBC · Issue #505 · payara/Payara · GitHub

@Annotationのtype属性はアノテーション名.class、target属性はtypeで指定したアノテーションを付加する対象を表します。

この例だと、クラスに@Dependentコンストラクタ@Injectを付けるということになります。

2. 自作アノテーションをDaoインターフェイスに付加

次に、自作した@InjectConfingアノテーションを、自作Daoインターフェイスに付加します。

package com.example.dao;

import com.example.dao.config.InjectConfig;
import com.example.entity.Employee;
import org.seasar.doma.Dao;
import org.seasar.doma.Script;
import org.seasar.doma.Select;

import java.util.List;
import java.util.Optional;

@Dao
@InjectConfig
public interface EmployeeDao {

    @Script
    void create();

    @Select
    Optional<Employee> selectById(Integer empId);

    @Select
    List<Employee> selectLikeName(String name);
}

3.Gradleでビルド

Gradleでビルドすると、EmployeeDaoインターフェイスの実装クラスが生成されます。

CDIの場合
package com.example.dao;

/** */
@javax.enterprise.context.Dependent()
@javax.annotation.Generated(value = { "Doma", "2.5.0" }, date = "2015-10-15T20:48:55.929+0900")
public class EmployeeDaoImpl extends org.seasar.doma.internal.jdbc.dao.AbstractDao implements com.example.dao.EmployeeDao {

    /**
     * @param config the config
     */
    @javax.inject.Inject()
    public EmployeeDaoImpl(org.seasar.doma.jdbc.Config config) {
        super(config);
    }
EJBの場合
package com.example.dao;

/** */
@javax.ejb.Stateless()
@javax.annotation.Generated(value = { "Doma", "2.5.0" }, date = "2015-10-15T20:48:55.929+0900")
public class EmployeeDaoImpl extends org.seasar.doma.internal.jdbc.dao.AbstractDao implements com.example.dao.EmployeeDao {

    /**
     * @param config the config
     */
    @javax.inject.Inject()
    public EmployeeDaoImpl(org.seasar.doma.jdbc.Config config) {
        super(config);
    }

@AnnnotateWithで指定した通り、クラスに@Dependentまたは@Statelessコンストラクタ@Injectが付加されています。これで、このEmployeeDaoImplを他のクラスにCDIでインジェクトできるようになります。

また、コンストラクタには@Injectが付加されています。

引数のConfigインターフェイスは、データソースなどを保持するもので、後ほど実装クラスを作成します。

Config実装クラスを作成して@ApplicationScopedなどを付加しておけば、この実装クラスのインスタンスが、コンストラクタインジェクションされます。

4. Config実装クラスの作成

package com.example.dao.config;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.sql.DataSource;

@ApplicationScoped
public class AppConfig implements Config {

    @Inject
    private DataSource dataSource;

    @Inject
    private Dialect dialect;

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

}

@ApplicationScopedを付加してCDI管理ビーン(このクラスはEJBでなくでOKです)にします。アプリケーションに関する設定情報を保持するクラスなので、スコープはアプリケーションスコープが適切と思われます。

DataSourceDialectについては、別途プロデューサークラスを作成して、@Injectで取得できるようにしています。

package com.example.dao.config;

import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.MysqlDialect;

import javax.annotation.Resource;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.sql.DataSource;
import java.io.Serializable;

@Dependent
public class DataSourceProducer implements Serializable {

    @Resource(lookup = "jdbc/sandbox")
    private DataSource dataSource;

    private Dialect dialect = new MysqlDialect();

    @Produces
    public DataSource getDataSource() {
        return dataSource;
    }

    @Produces
    public Dialect getDialect() {
        return dialect;
    }
}

このクラスもEJBでなくてOKです。

訂正とお詫び:@ResourceJDBCリソースを取得する際、name属性を使っていたのですが、正しくはlookup属性でした。訂正してお詫びします。

Config実装クラスが複数ある場合は?

データソースが複数ある場合など、Config実装クラスが複数になる可能性があると思います。その場合はQualifierを自作して、それをConfig実装クラスと@AnnotatedWithに追加すれば良いはずです。

@AnnotateWith(annotations = {
        @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = 自作Qualifier1.class
})
public @interface InjectConfig1 {
}
@ApplicationScoped
@自作Qualifier1
public class AppConfig1 implements Config {
    // 省略
}
@AnnotateWith(annotations = {
        @Annotation(target = AnnotationTarget.CLASS, type = Dependent.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Inject.class)
        , @Annotation(target = AnnotationTarget.CONSTRUCTOR_PARAMETER, type = 自作Qualifier2.class
})
public @interface InjectConfig2 {
}
@ApplicationScoped
@自作Qualifier2
public class AppConfig2 implements Config {
    // 省略
}

JTAトランザクション管理する

DataSource@Resourceで取ってきているし、DaoImplクラスはCDI管理にできたので、後は簡単です。

CDIの場合

package com.example.service;

import com.example.dao.EmployeeDao;
import com.example.entity.Employee;
import com.example.resource.dto.EmployeeDto;

import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.io.Serializable;
import java.util.Optional;

@Dependent
public class EmployeeService implements Serializable {
    @Inject
    private EmployeeDao employeeDao;

    @Transactional(Transactional.TxType.REQUIRED)
    public Optional<EmployeeDto> selectById(Integer empId) {
        Optional<Employee> employeeOptional = employeeDao.selectById(empId);
        return employeeOptional.map(this::convertToDto);
    }

EJBの場合

package com.example.service;

import com.example.dao.EmployeeDao;
import com.example.entity.Employee;
import com.example.resource.dto.EmployeeDto;

import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import java.io.Serializable;
import java.util.Optional;

@Stateless
public class EmployeeService implements Serializable {
    @Inject
    private EmployeeDao employeeDao;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public Optional<EmployeeDto> selectById(Integer empId) {
        Optional<Employee> employeeOptional = employeeDao.selectById(empId);
        return employeeOptional.map(this::convertToDto);
    }

@Injectでインジェクトすると、EmployeeDaoImplインスタンスが注入されます。で、メソッド@Transactionalを付加すればOK!

JAX-RSでRESTを作る

package com.example.resource;

import com.example.resource.dto.EmployeeDto;
import com.example.service.EmployeeService;
import org.hibernate.validator.constraints.NotBlank;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.validation.constraints.Pattern;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("employees")
@RequestScoped
public class EmployeeResource {

    @Inject
    private EmployeeService employeeService;

    @GET
    @Path("{empId}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response selectById(@PathParam("empId")
                                   @Pattern(regexp = "[1-9][0-9]*") String empIdStr) {
        Integer empId = Integer.valueOf(empIdStr);
        EmployeeDto employeeDto = employeeService.selectById(empId)
                .orElseThrow(() -> new NotFoundException("該当する社員が見つかりませんでした"));
        return Response.ok(employeeDto).build();
    }
}

感想

これで去年からの疑問点が解決しました。ようやっとスッキリです。

あとはDomaの使い方さえ知れれば、怖いものなし・・・のはず!

併せて読みたい

うらがみさんのブログ。Doma+JAX-RS連携について書かれています。

Thymeleaf + JAX-RS + DomaをGlassFishで試してみる - 裏紙

今回利用した、@siosioさん作のIntelliJ IDEA Doma Support Plugin

IntelliJ IDEA用のDomaプラグイン作ってみた - しおしお

Doma公式ドキュメント

Welcome to Doma — Doma 2.0 ドキュメント

Domaにこの機能が入った経緯のブログ記事です。

DomaのEJB3.1対応 - taediumの日記

MVC 1.0 EDR2リリース&変更点解説!

公式資料

MVC 1.0のアーリードラフト第2版がリリースされました!

JSR 371はこちら

JSR-000371 Model-View-Controller Early Draft Review 2

Ozark(MVC 1.0の参照実装)のソースコードはこちら

spericas/ozark · GitHub

僕の解説資料

スライド資料

それに伴って、先月のGlassFish勉強会で発表した資料も更新しました。

www.slideshare.net

サンプルコード

GitHubのサンプルコードも修正しましたので、git cloneしてお使いください。

GlassFish勉強会の時は、mvc-specとozark)をgit cloneしてローカルでMavenでビルドして使ってたんですが、MavenにEDR2版がアップされているので、pom.xmlを編集しました。

EDR1->EDR2の大きな変更点

セキュリティ機能

CSRF対策とXSS対策機能が入りました。

ValidtionResult->BindingResult

バリデーション結果を保持するインターフェイスが、BindingResultに変更になりました。

MvcContextの追加

アプリケーションのパスなどを取得できるメソッドが用意されています。EL式からは${mvc}で参照できます。

リダイレクト

コントローラーで返すビューへのパスに接頭辞「redirect:」をつけることで、リダイレクトが可能になりました。また、@RedirectScopedというCDIスコープアノテーションも追加され、リダイレクト先にも値を渡せるようになりました。

次のリリースは?

来年の3月31日の予定です。

mvc-spec - Java.net JIRA

CDI/JTAを使うなら読んでおきたいリンクまとめ

日本語書籍

「わかりやすいJava EE」「Java EE 7徹底入門」です。

金魚本およびEE 5本には記載がありません。

Amazon CAPTCHA

Amazon CAPTCHA

まず概要を掴む

CDIに関しては、まずは下記の上妻さんの記事をご確認ください。バージョンが上がるごとに注意点をまとめていらっしゃいます。順番に読んでいただければOKです。

CDI 1.0 (Java EE 6)

CDIをはじめよう - 見習いプログラミング日記

CDI 1.1 (Java EE 7)

Java EE環境におけるCDIのデフォルト化 - 見習いプログラミング日記

CDI 1.2 (Java EE 7)

CDI1.2によるbean-discovery-modeの見直し - 見習いプログラミング日記

CDI 1.0から2.0 + αまで (Java EE 6, 7, 8)

CDI2.0アップデート&クックブック #JavaDayTokyo #jdt2016_4c

寺田さんの資料

Java Day Tokyo 2015の寺田さんの資料を見ていただくと、CDIのその他の機能が一通り学習できます。

PDF(直リンク)

http://www.oracle.co.jp/jdt2015/pdf/2-2.pdf

動画

【Java Day Tokyo 2015】 [2-2] Java EE 7エッセンシャル・レシピ - YouTube

羽生田さんの資料

2016年2月のJJUGナイトセミナーでの資料です。

BeanManagerなど、上記の資料では載っていないことまで解説されています。

3.Java EE7 徹底入門 CDI&EJB

各機能の詳細な使い方

こちらの記事が、きれいにまとまっています。openglさんのブログは本当に凄い・・・。

JavaEE使い方メモ(CDI) - Qiita

JTAトランザクション管理)

Java EE 7のTransactionalアノテーションを試してみる - DENの思うこと

JavaEE使い方メモ(JTA) - Qiita

多分どこよりも早いJSON-B解説(Java EE 8・Java API for JSON Binding)

JSON-Bって何?

Java EE 8で追加される予定の、JavaオブジェクトとJSONの相互変換を行う機能です。例えば、下記のようなJAX-RSコードがあるとします。

public class Employee {
  private int id;
  private String name;
  private java.time.LocalDate joinedDate;
  // コンストラクタ、setter、getterは省略
}

@Path("employee")
public class EmployeeResource {
  @GET
  @Produces("application/json")
  public Response getEmployee() {
    Employee emp = new Employee(100, "Hoge", LocalDate.of(2015, 4, 1));
    return Response.ok(emp).build();
  }
}

で、http://localhost:8080/sample/api/employeeのようなURLにGETリクエストを送ると、下記のようなJSONが返ってきます。

{
  "id" : 100,
  "name" : "Hoge",
  "joinedDate" : "2015-04-01"
}

実は今まで、JAX-RS(およびJava EE)自体にはJavaオブジェクトをJSONに変換する機能を持っていませんでした。このJSON変換の部分は、APサーバーが内包している「JSONパーサー」と呼ばれる別ライブラリ(EclipseLink MOXy、Jackson、Jettisonなど)が行っています。Java EE 7で「JSON Processing (JSON-P)」という機能が追加されましたが、これはJSONを動的に読み書きするもので、前述のJSONパーサーのような、オブジェクトを一括でJSONに変換するものではありません。

そこで、JSONパーサーをJava EE標準に取り入れようということで、Java EE 8に追加される予定の機能が「JSON-B」です。2015年8月に、JSR 367のEarly Draft第1版が公開されました。下記のURLからダウンロード可能です。ダウンロードできるZIPには、クラスファイル・ソース・JavadocのJARが1つずつ、およびJSR文書のPDFが含まれています。

JSR-000367 Java API for JSON Binding Early Draft Review

ただし、JSRで決まっているのはほとんどインターフェイスアノテーション・例外なので、プログラムとして動かすためには「実装」が必要になります。しかしJSON-Bの参照実装(公式の実装)であるEclipseLink MOXyには、探した限りではまだJSON-B実装が含まれていないようです。そこで今回は、JSRやJavadocから分かる範囲で、JSON-Bの仕様を解説したいと思います。プログラムとしての作成・実行を確認できていないので、その点はご了承くださいm( )m

JavaオブジェクトとJSONの相互変換

変換の中心となるのは、javax.json.bind.Jsonbインターフェイスです。ただし、Jsonbインターフェイスについては、まだJSRには記載されていませんので、Javadocに載っていたサンプルコードからご紹介します。

Javaオブジェクト→JSONへの変換(マーシャリング)

JsonbインターフェイスのtoJson()メソッドを使います。

Employee emp = new Employee(100, "Hoge", LocalDate.of(2015, 4, 1));
// Jsonbオブジェクトの取得
Jsonb jsonb = JsonbBuilder.create();
// JavaオブジェクトをJSON文字列に変換する
String json = jsonb.toJson(emp);

toJson()メソッドオーバーロードされており、第2引数にOutputStreamを指定することも可能です。

jsonb.toJson(emp, new PrintWriter(System.out));

他にも、引数が違うtoJson()メソッドがいくつか存在します。

JSONJavaオブジェクトへの変換(アンマーシャリング)

JsonbインターフェイスのfromJson()メソッドを使います。

String json = "{\"id\":100,\"name\":\"Hoge\",\"joinedDate\":\"2015-04-01\"}";
Employee emp = jsonb.fromJson(json, Employee.class);

fromJson()メソッドオーバーロードされており、第1引数にInputStreamを指定することも可能です。

InputStream is = …;
Employee emp = jsonb.fromJson(is, Employee.class);

他にも、引数が違うfromJson()メソッドがいくつか存在します。

JAX-RSでの利用について

これは予測ですが、Java EE 8でJAX-RSを利用する際は、上記のようなtoJson() / fromJson()を使ったコードを書く機会は、そんなに多くないと思います。おそらく、JSON-Bを利用したMessageBodyWriter / MessageBodyReader(JSONパーサーを呼び出して、実際にJSON変換を行うJAX-RSのクラス)が、JAX-RS実装内(Jersey、RESTEasyなど)で組み込みで提供されるはずです。しかし、MessageBodyWriter / MessageBodyReaderを自分でカスタマイズしたい場合は、上記のようなコードを書く機会があるかもしれません。

対応しているデータ型

基本的なデータ型

  • java.lang.Character
  • java.lang.Byte
  • java.lang.Short
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Float
  • java.lang.Double
  • java.lang.Boolean
  • 上記に対応するプリミティブ型
  • java.lang.String

日付

  • java.util.Date, Calendar, GregorianCalendar
  • java.util.TimeZone, SimpleTimeZone
  • java.time.*

コレクション

リスト・セット・マップのほぼ全て、および配列

JSON-P

  • javax.json.JsonObject
  • javax.json.JsonArray
  • javax.json.JsonStructure
  • javax.json.JsonValue
  • javax.json.JsonPointer
  • javax.json.JsonString
  • javax.json.JsonNumber

その他

マッピングのカスタマイズ

特定のフィールドをJSONに含めたくない

JSONに含めたくないフィールドに@javax.json.bind.annotation.JsonbTransientアノテーションを付加します。

public class Employee {
  // idはJSONに含まれない
  @JsonbTransient
  private int id;
  …
}

nullの場合もJSONに含めたい

デフォルトのルールでは、null値のフィールドはJSONに含まれません。nullの場合もJSONに含めたい場合は、@javax.json.bind.annotation.JsonbPropertyアノテーションnillable属性をtrueにします。

public class Employee {
  // nullでもJSONに出力される
  @JsonbProperty(nillable = true)
  private LocalDate joinedDate;
  …
}

フィールド名とJSONプロパティ名を変えたい

名前を変えたいフィールドに@javax.json.bind.annotation.JsonbPropertyアノテーションを付加し、value属性に任意の名前を指定します。

public class Employee {
  // JSONには「joined_date」という名前で出力される
  @JsonbProperty(value = "joined_date")
  private LocalDate joinedDate;
  …
}

その他のカスタマイズ事項

ネーミングルールを一括で決めるjavax.json.bind.config.PropertyOrderStrategy
  • IDENTITY
  • LOWER_CASE_WITH_DASHES
  • LOWER_CASE_WITH_UNDERSCORES
  • UPPER_CAMEL_CASE
  • UPPER_CAMEL_CASE_WITH_SPACES
  • CASE_INSENSITIVE

の6種類が指定可能です。

プロパティの順序を決めるjavax.json.bind.config.PropertyOrderStrategy
  • LEXICOGRAPHICAL(辞書順)
  • REVERSE(辞書順の逆)
  • ANY("the order of properties is not guaranteed to retain any order."とありますが、ちょっと意味が取れませんした・・・(^^; )

の3種類が指定可能です。

I-JSON (Internet JSON)サポート

I-JSONはあまり詳しくないので、詳細はこちらをお読みくださいm( )m

https://www.tbray.org/ongoing/When/201x/2015/03/23/i-json

可視性のカスタマイズ

ここで言う「可視性」とは、Javaのpublicとかprivateとかのことです。デフォルトでは、フィールドまたはgetterはpublicなもの以外は無視される(getterとフィールドが共にpublicの場合は、getterが優先されるようです)のですが、その設定を変えることができるのかな?ちょっとJSRとJavadocからは読み取れませんでした。

日付のフォーマット

@javax.json.bind.anntation.JsonbDateFormatアノテーションで指定します。属性でパターンとロケールの指定が可能です。

数値のフォーマット

@javax.json.bind.anntation.JsonbNumberFormatアノテーションで指定します。属性でパターンとロケールの指定が可能です。

バイナリデータ

javax.json.bind.config.BinaryDataStrategyクラスで指定します。以下の3つのエンコード方式が利用できます。

  • BYTE
  • BASE_64
  • BASE_64_URL
JSONの整形(Pretty Print)

デフォルトでは、多くのJSONパーサーは、JSONを下記のような形で生成します。

{"id":100,"name":"Hoge","joinedDate":"2015-04-01"}

これだと人間には読みづらいので、改行・空白・インデントを入れて読みやすくするのがPretty Printです。

{
  "id" : 100,
  "name" : "Hoge",
  "joinedDate" : "2015-04-01"
}

この機能はまだJSRには入っていませんが、Javadocには記載されています。javax.json.bind.JsonbConfigクラスを利用します。

JsonbConfig confing = new JsonbConfig();
// Pretty Print設定を有効化する
config.withFormatting(true);
Jsonb jsonb = JsonbBuilder.create(config);
// 整形されたJSONが生成される
String json = jsonb.toJson(…);

まとめ

今回の記事では、JSRおよびJavadocのうち主要と思われる部分のみ解説しました。全内容は解説していませんので、他の機能も知りたいという方は原典をお読みください。

JSR-000367 Java API for JSON Binding Early Draft Review

今までは、Java EE標準でないJSONパーサーやJAXBを使うしかなかったので、JSON-Bの追加は非常に嬉しいです。まだ仕様の策定中なので、今後さらに機能が追加されると思います。期待して待ちましょう!