cxfНачало работы с cxf

замечания

В этом разделе представлен обзор того, что такое cxf, и почему разработчик может захотеть его использовать.

Следует также упомянуть о любых крупных предметах в рамках cxf и ссылаться на связанные темы. Поскольку документация для cxf является новой, вам может потребоваться создать начальные версии этих связанных тем.

Основной веб-клиент с провайдером

Для начала нам нужна фабрика, которая производит WebClients.

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 ()». Здесь мы можем добавить несколько других вещей, таких как Basic Auth creds и безопасность потоков. Теперь просто используйте этот.
  • Затем мы вытаскиваем HTTPConduit и устанавливаем таймауты. CXF поставляется с базовыми клиентами Java, но вы можете использовать Glassfish или другие.
  • Наконец, мы кэшируем WebClient. Это важно, поскольку код до сих пор дорог для создания. Следующая строка показывает, как сделать это потокобезопасным. Обратите внимание, что мы также извлекаем код из кеша. По существу, мы создаем модель вызова REST, а затем клонируем его каждый раз, когда нам это нужно.
  • Обратите внимание на то, что здесь нет: мы не добавили никаких параметров или URLS. Они могут быть добавлены здесь, но это сделает конкретную конечную точку, и мы хотим получить общий. Также в запрос не добавляются заголовки. Они не пробивают «клон», поэтому их нужно добавить позже.

Теперь у нас есть 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. Обратите внимание, что у нас есть пользовательский заголовок принятия.
  • Далее добавляется строка пути и запроса. Имейте в виду, что на эти шаги нет порядка.
  • Наконец мы делаем «get». Конечно, есть несколько способов сделать это. Здесь мы имеем CXF для отображения JSON для нас. Когда это делается, нам приходится иметь дело с WebApplicationExceptions. Поэтому, если мы получим 404, CXF выдает исключение. Обратите внимание, что я ем эти исключения, потому что я просто регистрирую ответ в конце. Однако я хочу получить любое ДРУГОЕ исключение, поскольку они могут быть важными.
  • Если вам не нравится эта обработка обработки исключений, вы можете вернуть объект Response из «get». Этот объект содержит объект и код состояния HTTP. Он НИКОГДА не будет бросать исключение WebApplicationException. Единственный недостаток - это не отображение JSON для вас.
  • Наконец, в разделе «finally» у нас есть «wc.close ()». Если вы получаете объект из get clause, вам действительно не нужно это делать. Что-то может пойти не так, как надо, так что это хорошо.

Итак, как насчет заголовка «accept»: 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 показывает это оттуда. Один из вариантов - вернуть «true» в методах «isReadable ()» «isWriteable ()». Это гарантирует, что CXF использует этот класс вместо всех встроенных. Сначала будут проверены добавленные поставщики.

Клиентские фильтры

Одной из веских причин использования фильтров является регистрация. Используя этот метод, вызов 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"}
 

Настройка CXF для JAX-RS

Банки для 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