带提供程序的基本 Webclient

要开始,我们需要一个生产 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()”创建一个新的 webclient。在这里,我们可以添加一些其他的东西,如 Basic Auth creds 和线程安全。现在只需使用这个。
  • 接下来,我们拉出 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;
}
  • 尝试中,我们从工厂中获取 WebClient。这是一个新的冷淡的。
  • 接下来我们设置一些标题。在这里,我们添加一些 Auth 标头,然后添加一个接受标头。请注意我们有一个自定义接受标头。
  • 接下来是添加路径和查询字符串。请记住,这些步骤没有顺序。
  • 最后我们做得到。当然,有几种方法可以做到这一点。在这里,我们让 CXF 为我们做 JSON 映射。完成这种方式后,我们必须处理 WebApplicationExceptions。因此,如果我们得到 404,CXF 将抛出异常。请注意,我在那里吃了那些例外,因为我只是在最后记录响应。但是,我确实希望得到任何其他例外,因为它们可能很重要。
  • 如果你不喜欢此异常处理切换,则可以从 get 返回 Response 对象。该对象包含实体和 HTTP 状态代码。它永远不会抛出 WebApplicationException。唯一的缺点是它没有为你执行 JSON 映射。
  • 最后,在 finally 子句中,我们有一个“wc.close()”。如果从 get 子句中获取对象,则不必执行此操作。虽然有些东西可能会出错,但这是一个很好的故障保险。

那么,那个接受标题呢: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(或只是一个),然后在创建 WebClient 时将其添加到提供者。CXF 从中得出结论。一种选择是在“isReadable()”“isWriteable()”方法中返回 true。这将确保 CXF 使用此类而不是所有内置类。将首先检查添加的提供程序。