cxfAan de slag met cxf


Opmerkingen

Deze sectie geeft een overzicht van wat cxf is en waarom een ontwikkelaar het misschien wil gebruiken.

Het moet ook alle grote onderwerpen binnen cxf vermelden en een link naar de gerelateerde onderwerpen bevatten. Aangezien de documentatie voor cxf nieuw is, moet u mogelijk eerste versies van die gerelateerde onderwerpen maken.

Basic Webclient met Provider

Om te beginnen hebben we een fabriek nodig die WebClients produceert.

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
        }
    }
}
 
  • Eerst maken we een lijst met providers (komen daar later op terug)
  • Vervolgens maken we een nieuwe webclient met behulp van de statische fabriek "create ()". Hier kunnen we een paar andere dingen toevoegen, zoals Basic Auth creds en thread safety. Gebruik deze nu gewoon.
  • Vervolgens trekken we de HTTPConduit eruit en stellen de time-outs in. CXF wordt geleverd met Java-basisklasse clients, maar u kunt Glassfish of andere gebruiken.
  • Eindelijk slaan we de WebClient in de cache op. Dit is belangrijk omdat de code tot nu toe duur is om te maken. De volgende regel laat zien hoe het threadsafe te maken. Merk op dat dit ook is hoe we de code uit de cache halen. In wezen maken we een model van de REST-aanroep en klonen deze vervolgens elke keer dat we deze nodig hebben.
  • Let op wat er NIET is: we hebben geen parameters of URL's toegevoegd. Deze kunnen hier worden toegevoegd, maar dat zou een specifiek eindpunt vormen en we willen een generiek eindpunt. Ook zijn er geen headers toegevoegd aan het verzoek. Deze komen niet voorbij de "kloon", dus ze moeten later worden toegevoegd.

Nu hebben we een WebClient die klaar is voor gebruik. Hiermee kan de rest van het gesprek worden ingesteld.

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;
}
 
  • In de "try" pakken we een WebClient van de fabriek. Dit is een nieuwe "ijzig".
  • Vervolgens stellen we enkele headers in. Hier voegen we een soort Auth-header toe en vervolgens een accept-header. Merk op dat we een aangepaste accept-header hebben.
  • Het toevoegen van het pad en de zoekreeksen komt daarna. Houd er rekening mee dat er geen volgorde is voor deze stappen.
  • Eindelijk doen we de "get". Er zijn natuurlijk verschillende manieren om dit te doen. Hier laten we CXF de JSON-mapping voor ons doen. Wanneer we dit op deze manier doen, hebben we te maken met de WebApplicationExceptions. Dus als we een 404 krijgen, geeft CXF een uitzondering. Merk op dat ik die uitzonderingen opeet, omdat ik de reactie gewoon log in de eindelijk. Ik wil echter een ANDERE uitzondering krijgen, omdat deze mogelijk belangrijk is.
  • Als u dit niet prettig vindt bij het schakelen tussen uitzonderingen, kunt u het Response-object terughalen van de "get". Dit object bevat de entiteit en de HTTP-statuscode. Het zal NOOIT een WebApplicationException gooien. Het enige nadeel is dat het de JSON-toewijzing niet voor u doet.
  • Ten slotte hebben we in de clausule "eindelijk" een "wc.close ()". Als u het object van de clausule Get krijgt, hoeft u dit niet echt te doen. Er kan echter iets misgaan, dus het is een goede failsafe.

Dus, hoe zit het met die "accepteren" header: application / person-v1 + json Hoe weet CXF hoe het te parseren? CXF wordt geleverd met enkele ingebouwde JSON-parsers, maar ik wilde Gson gebruiken. Veel van de andere implementaties van Json-parsers hebben een soort annotatie nodig, maar geen Gson. Het is dom gemakkelijk te gebruiken.

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);
        }
    }
}
 

De code is eenvoudig. U implementeert twee interfaces: MessageBodyReader en MessageBodyWriter (of slechts één) en voegt deze vervolgens toe aan de "providers" bij het maken van de WebClient. CXF komt daarachter. Een optie is om "true" terug te geven in de methoden "isReadable ()" "isWriteable ()". Dit zorgt ervoor dat CXF deze klasse gebruikt in plaats van alle ingebouwde. De toegevoegde providers worden eerst gecontroleerd.

Clientfilters

Een goede reden om Filters te gebruiken, is om te loggen. Met behulp van deze techniek kan een REST-oproep eenvoudig worden vastgelegd en getimed.

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();
    }
}
 

Hierboven ziet u dat het verzoek wordt onderschept voordat het antwoord wordt verzonden en een ThreadLocal Long wordt ingesteld. Wanneer het antwoord is teruggestuurd, kunnen we het verzoek en het antwoord en allerlei relevante gegevens registreren. Dit werkt natuurlijk alleen voor Gson-reacties en dergelijke, maar kan eenvoudig worden aangepast. Dit is als volgt ingesteld:

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);
 

Het verstrekte logboek moet er ongeveer zo uitzien:

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 instellen voor JAX-RS

De potten voor CXF JAX-RS zijn te vinden in 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>
 

Deze potten zijn alles wat je nodig hebt om het te laten werken:

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