cxf Tutorial
Iniziare con cxf
Ricerca…
Osservazioni
Questa sezione fornisce una panoramica di cosa cxf è, e perché uno sviluppatore potrebbe voler usarlo.
Dovrebbe anche menzionare qualsiasi argomento di grandi dimensioni all'interno di cxf e collegarsi agli argomenti correlati. Poiché la Documentazione per cxf è nuova, potrebbe essere necessario creare versioni iniziali di tali argomenti correlati.
Webclient di base con provider
Per iniziare abbiamo bisogno di una fabbrica che produce 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
}
}
}
- Per prima cosa creiamo un elenco di fornitori (ne parleremo più avanti)
- Quindi creiamo un nuovo webclient usando la factory statica "create ()". Qui possiamo aggiungere alcune altre cose come i credenziali di autenticazione di base e la sicurezza dei thread. Per ora basta usare questo.
- Quindi estraiamo HTTPConduit e impostiamo i timeout. CXF viene fornito con client Java di classe base, ma è possibile utilizzare Glassfish o altri.
- Finalmente mettiamo in cache il WebClient. Questo è importante in quanto il codice finora è costoso da creare. La riga successiva mostra come renderlo a prova di thread. Si noti che questo è anche il modo in cui si estrae il codice dalla cache. Essenzialmente stiamo facendo un modello della chiamata REST e poi la cloniamo ogni volta che ne abbiamo bisogno.
- Nota cosa NON è qui: non abbiamo aggiunto alcun parametro o URL. Questi possono essere aggiunti qui ma ciò renderebbe un endpoint specifico e vogliamo uno generico. Inoltre non ci sono intestazioni aggiunte alla richiesta. Questi non superano il "clone" quindi devono essere aggiunti in seguito.
Ora abbiamo un client Web pronto per l'uso. Consente di impostare il resto della chiamata.
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;
}
- All'interno del "try" prendiamo un WebClient dalla fabbrica. Questo è un nuovo "gelido".
- Quindi impostiamo alcune intestazioni. Qui aggiungiamo una sorta di intestazione Auth e quindi un'intestazione di accettazione. Si noti che abbiamo un'intestazione di accettazione personalizzata.
- L'aggiunta del percorso e delle stringhe di interrogazione viene dopo. Tieni presente che non esiste un ordine per questi passaggi.
- Finalmente facciamo il "get". Ci sono diversi modi per farlo, ovviamente. Qui abbiamo CXF per la mappatura JSON per noi. Quando fatto in questo modo dobbiamo occuparci di WebApplicationExceptions. Quindi se otteniamo un 404, CXF genererà un'eccezione. Notate qui che mangio quelle eccezioni perché registro la risposta alla fine. Tuttavia desidero ottenere qualsiasi altra eccezione in quanto potrebbero essere importanti.
- Se non ti piace il passaggio alla gestione delle eccezioni, puoi recuperare l'oggetto risposta dal "get". Questo oggetto contiene l'entità e il codice di stato HTTP. Non genererà MAI una eccezione WebApplicationException. L'unico inconveniente è che non esegue la mappatura JSON per te.
- Infine, nella clausola "finally" abbiamo un "wc.close ()". Se ottieni l'oggetto dalla clausola get, in realtà non devi farlo. Qualcosa potrebbe andare storto, quindi è un buon metodo di sicurezza.
Quindi, che dire di quell'intestazione "accept": application / person-v1 + json Come farà CXF a saperlo analizzare? CXF viene fornito con alcuni parser JSON incorporati ma volevo usare Gson. Molte altre implementazioni di parser Json hanno bisogno di qualche tipo di annotazione ma non di Gson. È stupido facile da usare.
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);
}
}
}
Il codice è semplice. Implementate due interfacce: MessageBodyReader e MessageBodyWriter (o solo una) e quindi aggiungetela ai "provider" durante la creazione di WebClient. CXF capisce da lì. Un'opzione consiste nel restituire "true" nei metodi "isReadable ()" "isWriteable ()". Questo assicurerà che CXF usi questa classe invece di tutte quelle integrate. I fornitori aggiunti verranno controllati per primi.
Impostazione di CXF per JAX-RS
I vasi per CXF JAX-RS si trovano 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>
Questi vasi sono tutto ciò che serve per farlo funzionare:
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
Filtri client
Un buon motivo per utilizzare i filtri è per la registrazione. Usando questa tecnica, una chiamata REST può essere registrata e programmata facilmente.
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();
}
}
Sopra puoi vedere che la richiesta viene intercettata prima che la risposta venga inviata e che sia impostato un ThreadLocal Long. Quando viene restituita la risposta, possiamo registrare la richiesta e la risposta e tutti i tipi di dati pertinenti. Naturalmente questo funziona solo per le risposte Gson e simili, ma può essere facilmente modificato. Questo è impostato in questo modo:
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);
Il registro fornito dovrebbe essere simile a questo:
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"}