cxf开始使用cxf


备注

本节概述了cxf是什么,以及开发人员可能想要使用它的原因。

它还应该提到cxf中的任何大型主题,并链接到相关主题。由于cxf的文档是新的,您可能需要创建这些相关主题的初始版本。

带提供程序的基本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使用此类而不是所有内置类。将首先检查添加的提供程序。

客户过滤器

使用Filters的一个很好的理由是用于记录。使用此技术可以轻松记录和调整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"}
 

为JAX-RS设置CXF

在Maven中可以找到CXF JAX-RS的罐子:

<!-- 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