cxf チュートリアル
cxfの使い方
サーチ…
備考
このセクションでは、cxfの概要と開発者がなぜそれを使用したいのかを概説します。
また、cxf内の大きな件名についても言及し、関連するトピックにリンクしてください。 cxfのドキュメンテーションは新しいので、これらの関連トピックの初期バージョンを作成する必要があります。
プロバイダとの基本Webクライアント
まず、WebClientを生成するファクトリが必要です。
public class ClientFactory {
private Map<String, WebClient> cache = new HashMap<>();
public enum RESTClient {
PORTAL;
}
public WebClient fetchRestClient(RESTClient restClient) {
if (this.cache.containsKey(restClient)) {
return WebClient.fromClient(this.cache.get(rc));
}
if (RESTClient.enum.equals(rc)) {
List<Object> providers = new ArrayList<Object>();
providers.add(new GsonMessageBodyProvider());
WebClient webClient = WebClient.create("https://blah.com", providers);
HTTPConduit conduit = WebClient.getConfig(webClient).getHttpConduit();
conduit.getClient().setReceiveTimeout(recieveTimeout);
conduit.getClient().setConnectionTimeout(connectionTimout);
this.cache.put(RESTClient.CAT_DEVELOPER_PORTAL.name(), webClient);
return WebClient.fromClient(webClient);// thread safe
}
}
}
- 最初にプロバイダのリストを作成します(後でそれらに到達します)
- 次に、静的ファクトリ "create()"を使用して新しいWebクライアントを作成します。ここでは、基本認証の信用度とスレッドの安全性のようないくつかを追加することができます。今のところこれを使用してください。
- 次に、HTTPConduitを取り出して、タイムアウトを設定します。 CXFにはJavaベースクラスのクライアントが付属していますが、Glassfishなどを使用できます。
- 最後にWebClientをキャッシュします。これまでのコードは作成するのに費用がかかるので、これは重要です。次の行は、それをスレッドセーフにする方法を示しています。これはキャッシュからコードを引き出す方法でもあることに注意してください。本質的には、REST呼び出しのモデルを作成し、それが必要になるたびにそれを複製しています。
- ここにはないことに注目してください:パラメータやURLを追加していません。これらはここに追加することができますが、それは特定のエンドポイントを作成し、一般的なものを必要とします。また、要求に追加されたヘッダーもありません。これらは "クローン"を通過させないので、後で追加する必要があります。
これで、準備が整ったWebClientが完成しました。コールの残りの部分を設定します。
public Person fetchPerson(Long id) {
long timer t = System.currentTimeMillis();
Person person = null;
try {
wc = this.factory.findWebClient(RESTClient.PORTAL);
wc.header(AUTH_HEADER, SUBSCRIPTION_KEY);
wc.header(HttpHeaders.ACCEPT, "application/person-v1+json");
wc.path("person").path("base");
wc.query("id", String.valueOf(id));
person = wc.get(Person.class);
}
catch (WebApplicationException wae) {
// we wanna skip these. They will show up in the "finally" logs.
}
catch (Exception e) {
log.error(MessageFormat.format("Error fetching Person: id:{0} ", id), e);
}
finally {
log.info("GET HTTP:{} - Time:[{}ms] - URL:{} - Content-Type:{}", wc.getResponse().getStatus(), (System.currentTimeMillis() - timer), wc.getCurrentURI(), wc.getResponse().getMetadata().get("content-type"));
wc.close();
}
return p;
}
- "try"の内部では、工場からWebClientを取得します。これは新しい "冷たい"ものです。
- 次に、いくつかのヘッダーを設定します。ここでは、ある種のAuthヘッダとAcceptヘッダを追加します。カスタム受け入れヘッダーがあることに注目してください。
- 次にパスとクエリ文字列を追加します。これらの手順には順序がないことに注意してください。
- 最後に、「取得」を行います。もちろんこれを行うにはいくつかの方法があります。ここでは、CXFがJSONマッピングを行っています。このようにすれば、WebApplicationExceptionsを処理する必要があります。したがって、404を取得した場合、CXFは例外をスローします。ここでは、私は最後に応答を記録するので、私はそれらの例外を食べることに注意してください。私はしかし、彼らが重要かもしれないので、他の例外を取得したいです。
- この例外処理の切り替えが気に入らなければ、 "get"からResponseオブジェクトを返すことができます。このオブジェクトはエンティティとHTTPステータスコードを保持します。それは決してWebApplicationExceptionをスローしません。唯一の欠点は、あなたのためのJSONマッピングをしないということです。
- 最後に、 "finally"節には "wc.close()"があります。 Get Clauseからオブジェクトを取得した場合、実際にこれを行う必要はありません。何かが間違っているかもしれないので、それは良いフェイルセーフです。
だから、その "受け入れる"ヘッダはどうでしょうか?application / person-v1 + json CXFはどうやってそれを解析するのか知っていますか? CXFにはJSONパーサーが組み込まれていますが、私はGsonを使いたいと思っています。 Jsonパーサーの他の実装の多くは、何らかのアノテーションを必要としますが、Gsonは必要ありません。使い方が簡単ではありません。
public class GsonMessageBodyProvider<T> implements MessageBodyReader<T>, MessageBodyWriter<T> {
private Gson gson = new GsonBuilder().create();
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return StringUtils.endsWithIgnoreCase(mediaType.getSubtype(), "json");
}
@Override
public T readFrom(Class<T> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
try {
return gson.fromJson(new BufferedReader(new InputStreamReader(entityStream, "UTF-8")), type);
}
catch (Exception e) {
throw new IOException("Trouble reading into:" + type.getName(), e);
}
}
@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return StringUtils.containsIgnoreCase(mediaType.getSubtype(), "json");
}
@Override
public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return 0;
}
@Override
public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
try {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(entityStream, "UTF-8"));
writer.setIndent(" ");
gson.toJson(t, type, writer);
writer.close();
}
catch (Exception e) {
throw new IOException("Trouble marshalling:" + type.getName(), e);
}
}
}
コードは簡単です。 MessageBodyReaderとMessageBodyWriterの2つのインターフェイス(または1つだけ)を実装し、WebClientを作成するときに「プロバイダ」に追加します。 CXFはそれをそこから把握します。 1つのオプションは、isReadable()、isWriteable()メソッドで "true"を返すことです。これは、CXFが組み込みクラスのすべてではなく、このクラスを使用することを保証します。追加されたプロバイダが最初にチェックされます。
JAX-RS用CXFのセットアップ
CXF JAX-RSのジャーはMavenにあります:
<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-rt-rs-client -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-client</artifactId>
<version>3.1.10</version>
</dependency>
これらのジャーは、実行するために必要なものです。
cxf-rt-rs-client-3.1.10.jar
cxf-rt-transports-http-3.1.10.jar
cxf-core-3.1.10.jar
woodstox-core-asl-4.4.1.jar
stax2-api-3.1.4.jar
xmlschema-core-2.2.1.jar
cxf-rt-frontend-jaxrs-3.1.10.jar
javax.ws.rs-api-2.0.1.jar
javax.annotation-api-1.2.jar
クライアントフィルタ
フィルタを使用する良い理由の1つは、ログ用です。この手法を使用すると、REST呼び出しをログに記録して簡単にタイムアウトさせることができます。
public class RestLogger implements ClientRequestFilter, ClientResponseFilter {
private static final Logger log = LoggerFactory.getLogger(RestLogger.class);
// Used for timing this call.
private static final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private boolean logRequestEntity;
private boolean logResponseEntity;
private static Gson GSON = new GsonBuilder().create();
public RestLogger(boolean logRequestEntity, boolean logResponseEntity) {
this.logRequestEntity = logRequestEntity;
this.logResponseEntity = logResponseEntity;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
startTime.set(System.currentTimeMillis());
}
@Override
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("HTTP:").append(responseContext.getStatus());
sb.append(" - Time:[").append(System.currentTimeMillis() - startTime.get().longValue()).append("ms]");
sb.append(" - Path:").append(requestContext.getUri());
sb.append(" - Content-type:").append(requestContext.getStringHeaders().getFirst(HttpHeaders.CONTENT_TYPE.toString()));
sb.append(" - Accept:").append(requestContext.getStringHeaders().getFirst(HttpHeaders.ACCEPT.toString()));
if (logRequestEntity) {
sb.append(" - RequestBody:").append(requestContext.getEntity() != null ? GSON.toJson(requestContext.getEntity()) : "none");
}
if (logResponseEntity) {
sb.append(" - ResponseBody:").append(this.logResponse(responseContext));
}
log.info(sb.toString());
}
private String logResponse(ClientResponseContext response) {
StringBuilder b = new StringBuilder();
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = response.getEntityStream();
try {
ReaderWriter.writeTo(in, out);
byte[] requestEntity = out.toByteArray();
b.append(new String(requestEntity));
response.setEntityStream(new ByteArrayInputStream(requestEntity));
}
catch (IOException ex) {
throw new ClientHandlerException(ex);
}
return b.toString();
}
}
上では、応答が送信され、ThreadLocal Longが設定される前にリクエストがインターセプトされていることがわかります。応答が返されると、要求と応答、およびあらゆる種類の関連データを記録できます。もちろん、これはGsonレスポンスなどでのみ機能しますが、簡単に変更できます。これは次のように設定されています。
List<Object> providers = new ArrayList<Object>();
providers.add(new GsonMessageBodyProvider());
providers.add(new RestLogger(true, true)); <------right here!
WebClient webClient = WebClient.create(PORTAL_URL, providers);
提供されるログは次のようになります。
7278 [main] INFO blah.RestLogger - HTTP:200 - Time:[1391ms] - User:unknown - Path:https://blah.com/tmet/moduleDescriptions/desc?languageCode=en&moduleId=142 - Content-type:null - Accept:application/json - RequestBody:none - ResponseBody:{"languageCode":"EN","moduleId":142,"moduleDescription":"ECAP"}