Jersey の JSON/JAXB をシリアル化に再利用するにはどうすればよいですか?
質問
Jersey を使用して JAX-RS REST サービスを実装しました。JAX-RS/Jersey のすばらしい機能の 1 つは、いくつかの Java アノテーションを散りばめるだけで、POJO を REST サービスに簡単に変えることができることです。これには、JAXB アノテーションを使用して POJO を JSON に変換する簡単なメカニズムが含まれています。
ここで、このクールな JSON 化機能を REST 以外の目的でも利用できるようにしたいと考えています。これらのオブジェクトの一部を JSON テキストとしてディスクにシリアル化できるようにしたいと考えています。シリアル化したい JAXB オブジェクトの例を次に示します。
@XmlRootElement(name = "user")
public class UserInfoImpl implements UserInfo {
public UserInfoImpl() {}
public UserInfoImpl(String user, String details) {
this.user = user;
this.details = details;
}
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
public String getDetails() { return details; }
public void setDetails(String details) { this.details = details; }
private String user;
private String details;
}
Jersey は、追加情報なしでこれらの 1 つを json に変換できます。私のようなニーズのために、Jersey が API でこの機能を公開しているかどうか疑問に思っています。今まで見つけられなかったのですが…
ありがとう!
更新 2009 年 7 月 9 日:Providers オブジェクトを使用して次のことができることを学びました。 ほとんど まさに私がやりたいことをします:
@Context Providers ps;
MessageBodyWriter uw = ps.getMessageBodyWriter(UserInfoImpl.class, UserInfoImpl.class, new Annotation[0], MediaType.APPLICATION_JSON_TYPE);
uw.writeTo(....)
...これにより、オブジェクトが json として任意の出力ストリームに書き込まれます。これは私にとっては完璧ですが、@Component オブジェクトから @Context を使用して Providers オブジェクトにアクセスすることしかできません。注釈のない通常の POJO からアクセスする方法を知っている人はいますか?ありがとう!
解決
Jersey は、mapped()、badgerfish()、または Natural() のいずれの表記法を使用するかに応じて、いくつかの異なるフレームワークを使用します。通常、人々が求めるのはナチュラルです。そしてそれは、オブジェクト -> JAXB -> JSON という非常に優れた (そして非常に高速な) スタンドアロンの Jackson JSON プロセッサを使用して実装されていると思います。ただし、Jackson は、オブジェクトから JSON に直接移動するための独自の JAX-RS プロバイダーも提供しています。
実際、JAXB アノテーションのサポートも追加されました。見て
http://wiki.fasterxml.com/JacksonJAXBAnnotations
それが最終的にあなたが求めているものだと思います。Jackson は Object<->JSON 処理を実行します...Jersey は呼び出しを行うだけです
他のヒント
JAXB を使用してオブジェクトを JSON にマップする簡単な例を次に示します (Jackson を使用)。
http://ondra.zizka.cz/stranky/programovani/java/jaxb-json-jackson-howto.texy
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(pojoObject);
JAXB アノテーションは、XML にシリアル化するときに正常に機能します。主な問題は、JAXB が空の配列をサポートしていないことです。なので、こういう連載をするときは…
List myArray = new ArrayList();
...jaxb アノテーション経由で json に変換すると、空の配列はすべて [] ではなく null になります。
これを解決するには、jackson 経由で pojo を直接 json にシリアル化するだけです。
Jersey のユーザー ガイドからこれを見てください。http://jersey.java.net/nonav/documentation/latest/user-guide.html#d0e1959
これは、JAXB を使用せずに Jackson プロバイダーを使用する最良の方法です。さらに、jackson-all-x.y.z-jar を Web からダウンロードすると、いつでも最新バージョンの jackson を使用できます。
この方法は JAXB アノテーションに干渉しないので、試してみることをお勧めします。
Jersey は JAX-RS のリファレンス実装であり、JAX-RS は REST サービスのエンドポイントを実装する標準的な方法を提供することに完全に焦点を当てているため、ペイロードのシリアル化の問題は他の標準に委ねられます。
JAX-RS 標準にオブジェクトの直列化が組み込まれたら、それはすぐに実装が困難な大きな多頭の獣になり、焦点の一部を失うことになると思います。
Jersey がクリーンで使いやすい REST エンドポイントの提供に注力していることに感謝します。私の場合、すべての JAXB プラミングを含む親をサブクラス化しただけなので、バイナリと XML の間でオブジェクトをマーシャリングするのは非常に簡単です。
Jersey 固有のブートストラップを少し使用すると、必要な JSON オブジェクトを作成するために使用できます。次の依存関係を含める必要があります (バンドルを使用できますが、テストに Weld を使用している場合は問題が発生します)。
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.12</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.12</version>
</dependency>
そこから、JAXB アノテーション付きクラスを作成できます。以下は例です。
@XmlRootElement
public class TextMessage {
private String text;
public String getText() { return text; }
public void setText(String s) { this.text = text; }
}
次に、次の単体テストを作成できます。
TextMessage textMessage = new TextMessage();
textMessage.setText("hello");
textMessage.setUuid(UUID.randomUUID());
// Jersey specific start
final Providers ps = new Client().getProviders();
// Jersey specific end
final MultivaluedMap<String, Object> responseHeaders = new MultivaluedMap<String, Object>() {
@Override
public void add(final String key, final Object value) {
}
@Override
public void clear() {
}
@Override
public boolean containsKey(final Object key) {
return false;
}
@Override
public boolean containsValue(final Object value) {
return false;
}
@Override
public Set<java.util.Map.Entry<String, List<Object>>> entrySet() {
return null;
}
@Override
public List<Object> get(final Object key) {
return null;
}
@Override
public Object getFirst(final String key) {
return null;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public Set<String> keySet() {
return null;
}
@Override
public List<Object> put(final String key, final List<Object> value) {
return null;
}
@Override
public void putAll(
final Map<? extends String, ? extends List<Object>> m) {
}
@Override
public void putSingle(final String key, final Object value) {
}
@Override
public List<Object> remove(final Object key) {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public Collection<List<Object>> values() {
return null;
}
};
final MessageBodyWriter<TextMessage> messageBodyWriter = ps
.getMessageBodyWriter(TextMessage.class, TextMessage.class,
new Annotation[0], MediaType.APPLICATION_JSON_TYPE);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
Assert.assertNotNull(messageBodyWriter);
messageBodyWriter.writeTo(textMessage, TextMessage.class,
TextMessage.class, new Annotation[0],
MediaType.APPLICATION_JSON_TYPE, responseHeaders, baos);
final String jsonString = new String(baos.toByteArray());
Assert.assertTrue(jsonString.contains("\"text\":\"hello\""));
このアプローチの利点は、すべてが JEE6 API 内に保持され、プロバイダーのテストと取得を除いて外部ライブラリが明示的に必要ないことです。ただし、標準では何も提供されておらず、実際には使用しないため、MultivaluedMap の実装を作成する必要があります。それ 5月 また、GSON よりも遅く、必要以上に複雑です。
XML ビューについては理解していますが、POJO の JSON サポートを標準装備として要求するのは、ある程度先見の明があったと思います。実装が JSON でクライアントが JavaScript RIA である場合、特殊文字を使用して JSON 識別子を加工する必要はありません。
また、Java Beans が POJO ではないというわけでもありません。Web 層の外側で次のようなものを使用したいと考えています。
public class Model
{
@Property height;
@Property weight;
@Property age;
}
デフォルトのコンストラクターやゲッター/セッターのノイズはなく、独自のアノテーションを備えた POJO だけです。