Jersey Client APIの使い方、その1
Jersey Client APIの相手としては、特にJAX-RSサーバに限らず、一般的なウェブサービスとのやりとりに使用できる。ここでは、その方法を見ていく。
※なお、ここではJava9モジュールを使うが、使わない人でもモジュール対応部分を無視すればよい。
依存
以下の依存を入れる。excludeする理由はError occurred during initialization of boot layerに記述した。
// モジュール対応のため
configurations {
implementation.exclude module:'jakarta.inject' // defines javax.inject
implementation.exclude module:'jsr305' // defines javax.annotation
implementation.exclude module:'aopalliance-repackaged' // defines org.aopalliance.aop
}
dependencies {
implementation group: 'javax.inject', name: 'javax.inject', version: '1'
implementation group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.1.1'
implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.29'
implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.29'
implementation group: 'org.glassfish.jersey.core', name: 'jersey-client', version: '2.29'
implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.29'
}
module-info
モジュール定義は以下。openにしておかないと、Jersey側からアプリ側のクラスにアクセスできない(個別にやっても良いが面倒なので全開にしている)。
open module some_test {
requires java.ws.rs;
requires jersey.media.json.jackson;
requires com.fasterxml.jackson.databind;
requires jersey.client;
}
JSONの結果をJavaオブジェクトとして受け取る
ここでは、既にウェブサービスが動作しているものとする。URLを叩くとJSONを返すだけだ。
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;
public class Test1 {
// Clientはスレッドセーフとのこと
static Client client = ClientBuilder.newClient();
public static void main(String[]args) {
WebTarget target =
client.target("http://localhost:8080").path("/api/employees/1");
Response r = target.request().get();
Employee e = r.readEntity(Employee.class);
System.out.println(e);
}
public static class Employee {
public int id;
public String name;
@Override
public String toString() {
return "id:" + id + ", name:" + name;
}
}
}
出力結果は以下だ。
id:1, name:佐藤
Javaクラスに存在しないフィールドを無視する1
長期的な使用を見越すと、サーバ側から返されたJSON中のフィールドがアプリ側に存在しないことも考えられる。これを単純に無視したいのだが、このままではエラーが発生してしまう。先のEmployeeを以下にすると、
public static class Employee {
public int id;
@Override
public String toString() {
return "id:" + id;
}
}
以下のエラーになる。
Exception in thread "main" javax.ws.rs.ProcessingException: Error reading entity from input stream.
at jersey.common@2.29/org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:865)
(略)
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "name" (class some_test.Test1$Employee), not marked as ignorable (one known property: "id"])
at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 17] (through reference chain: nato_test.Test1$Employee["name"])
at com.fasterxml.jackson.databind@2.9.9/com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
受け取ったJSONの不明なフィールドを無視するにはこうする。
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;
import org.glassfish.jersey.client.*;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.*;
import com.fasterxml.jackson.databind.*;
public class Test2 {
static final Client client = ClientBuilder.newClient(
new ClientConfig(
new JacksonJaxbJsonProvider()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
)
);
public static void main(String[]args) {
WebTarget target =
client.target("http://localhost:8080").path("/api/employees/1");
Response r = target.request().get();
Employee e = r.readEntity(Employee.class);
System.out.println(e);
}
public static class Employee {
public int id;
@Override
public String toString() {
return "id:" + id;
}
}
}
出力結果は以下になる。
id:1
※これはもちろん「すべてのクラスについてフィールドがなければ無視する」というやり方で、クラスごとに「無視する」設定としてはクラスにアノテーションをつける方法がある。
Javaクラスに存在しないフィールドを無視する2
MessageBodyReaderというものを定義して、完全に自前でJSON・Javaオブジェクト変換を行うこともできる。
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import javax.ws.rs.*;
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.*;
import com.fasterxml.jackson.databind.*;
public class Test3 {
static final Client client = ClientBuilder.newClient().register(MyReader.class);
public static void main(String[]args) {
WebTarget target =
client.target("http://localhost:8080").path("/api/employees/1");
Response r = target.request().get();
Employee e = r.readEntity(Employee.class);
System.out.println(e);
}
@Consumes("application/json")
public static class MyReader implements MessageBodyReader<Object> {
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.equals(Employee.class);
}
@Override
public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
return mapper.readValue(entityStream, type);
}
}
public static class Employee {
public int id;
@Override
public String toString() {
return "id:" + id;
}
}
}
出力結果は前と同じだ。
非同期で実行する
以上の例は同期、つまりサーバ側から結果が返ってくるまでブロックされるのだが、非同期で実行することもできる。
get()を実行する前にasync()を挟むと、結果はResponseではなく、Future
public static void main(String[]args) {
WebTarget target =
client.target("http://localhost:8080").path("/api/employees/1");
// get()の前にasync()を挟む
Future<Response>future = target.request().async().get();
ExecutorService service = Executors.newSingleThreadExecutor();
System.out.println("start");
service.submit(()-> {
Response r;
try {
r = future.get();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
Employee e = r.readEntity(Employee.class);
System.out.println(e);
});
service.shutdown();
System.out.println("finished");
}
出力結果は以下だ。
start
finished
id:1name:佐藤
POSTしてみる
新たな従業員を作成してPOSTしてみる。返り値はその従業員を返す。
public static void main(String[]args) {
WebTarget target =
client.target("http://localhost:8080").path("/api/employees/1");
Response r = target.request().post(Entity.json(new Employee(2, "田中")));
System.out.println(r.readEntity(Employee.class));
}
public static class Employee {
public int id;
public String name;
public Employee() {}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id:" + id + ",name:" + name;
}
}
出力結果は以下。
id:2,name:田中
ディスカッション
コメント一覧
まだ、コメントがありません