java-ee
Servizi Web RESTful di Java (JAX-RS)
Ricerca…
Osservazioni
A differenza di SOAP e WS-stack, che sono specificati come standard W3C, REST è davvero un insieme di principi per la progettazione e l'utilizzo dell'interfaccia basata sul web. Le applicazioni REST / RESTful si basano su altri standard:
HTTP
URI, URL
XML, JSON, HTML, GIF, JPEG, and so forth (resource representations)
Il ruolo di JAX-RS (Java API per RESTful Web Services) è quello di fornire API che supportano la creazione di servizi RESTful. Tuttavia, JAX-RS è solo un modo per farlo . I servizi RESTful possono essere implementati in altri modi in Java e (in effetti) in molti altri linguaggi di programmazione.
Risorsa semplice
Prima di tutto per un'applicazione JAX-RS deve essere impostato un URI di base da cui saranno disponibili tutte le risorse. A tale scopo, la classe javax.ws.rs.core.Application
deve essere estesa e annotata con l'annotazione javax.ws.rs.ApplicationPath
. L'annotazione accetta un argomento stringa che definisce l'URI di base.
@ApplicationPath(JaxRsActivator.ROOT_PATH)
public class JaxRsActivator extends Application {
/**
* JAX-RS root path.
*/
public static final String ROOT_PATH = "/api";
}
Le risorse sono semplici classi POJO annotate con l'annotazione @Path
.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("/hello")
public class HelloWorldResource {
public static final String MESSAGE = "Hello StackOverflow!";
@GET
@Produces("text/plain")
public String getHello() {
return MESSAGE;
}
}
Quando una richiesta HTTP GET
viene inviata a /hello
, la risorsa risponde con Hello StackOverflow!
Messaggio.
Ottieni i tipi di metodo
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("/hello")
public class HelloWorldResource {
public static final String MESSAGE = "Hello World!";
@GET
@Produces("text/plain")
public String getHello() {
return MESSAGE;
}
@GET
@Path("/{letter}")
@Produces("text/plain")
public String getHelloLetter(@PathParam("letter") int letter){
if (letter >= 0 && letter < MESSAGE.length()) {
return MESSAGE.substring(letter, letter + 1);
} else {
return "";
}
}
}
GET
senza un parametro fornisce tutto il contenuto ("Hello World!") E GET
con il parametro path fornisce la lettera specifica di quella stringa.
Qualche esempio:
$ curl http://localhost/hello Hello World!
$ curl http://localhost/hello/0 H
$ curl http://localhost/hello/4 o
Nota: se si @GET
l'annotazione del tipo di metodo (ad es. @GET
sopra), un metodo di richiesta è @GET
gestore di richieste GET.
Elimina metodo
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
@Path("hello")
public class HelloWorldResource {
private String message = "Hello StackOverflow!";
@GET
@Produces("text/plain")
public String getHello() {
return message;
}
@DELETE
public Response deleteMessage() {
message = null;
return Response.noContent().build();
}
}
Consumalo con arricciatura:
$ curl http://localhost/hello
Hello StackOverflow!
$ curl -X "DELETE" http://localhost/hello
$ curl http://localhost/hello
null
Metodo POST
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
@Path("hello")
public class HelloWorldResource {
@POST
@Path("/receiveParams")
public Response receiveHello(@FormParam("name") String name, @FormParam("message") String message) {
//process parameters
return Response.status(200).build();
}
@POST
@Path("/saveObject")
@Consumes("application/json")
public Response saveMessage(Message message) {
//process message json
return Response.status(200).entity("OK").build();
}
}
Il primo metodo può essere invocato tramite l'invio di moduli HTML inviando i parametri di input catturati. L'azione di invio del modulo deve puntare a:
/hello/receiveParams
Il secondo metodo richiede Message POJO con getter / setter. Qualsiasi client REST può chiamare questo metodo con input JSON come -
{"sender":"someone","message":"Hello SO!"}
POJO dovrebbe avere la stessa proprietà di JSON per far funzionare la serializzazione.
public class Message {
String sender;
String message;
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Eccezione Mapper
@Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException exception) {
return Response.serverError().entity("Invalid input: " + exception.getMessage()).build();
}
}
Questo programma di eccezioni catturerà tutte le IllegalArgumentException lanciate nell'applicazione e mostrerà all'utente un messaggio chiaro invece di uno stacktrace.
UriInfo
Per ottenere informazioni sull'URI, l'agente utente utilizzato per accedere alla risorsa, è possibile utilizzare l'annotazione del parametro @Context
con un parametro UriInfo
. L'oggetto UriInfo
ha alcuni metodi che possono essere utilizzati per ottenere diverse parti dell'URI.
//server is running on https://localhost:8080,
// webapp is at /webapp, servlet at /webapp/servlet
@Path("class")
class Foo {
@GET
@Path("resource")
@Produces(MediaType.TEXT_PLAIN)
public Response getResource(@Context UriInfo uriInfo) {
StringBuilder sb = new StringBuilder();
sb.append("Path: " + uriInfo.getPath() + "\n");
sb.append("Absolute Path: " + uriInfo.getAbsolutePath() + "\n");
sb.append("Base URI: " + uriInfo.getBaseUri() + "\n");
sb.append("Request URI: " + uriInfo.getRequestUri() + "\n");
return Response.ok(sb.toString()).build();
}
}
output di un GET a https://localhost:8080/webapp/servlet/class/resource
:
Path: class/resource
Absolute Path: https://localhost:8080/webapp/servlet/class/resource#
Base URI: https://localhost:8080/webapp/servlet/
Request URI: https://localhost:8080/webapp/servlet/class/resource
SubResources
A volte per motivi organizzativi o di altro tipo è logico che la risorsa di primo livello restituisca una sotto risorsa che assomiglierebbe a questa. (La tua sotto-risorsa non ha bisogno di essere una classe interiore)
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("items")
public class ItemsResource {
@Path("{id}")
public String item(@PathParam("id") String id) {
return new ItemSubResource(id);
}
public static class ItemSubResource {
private final String id;
public ItemSubResource(String id) {
this.id = id;
}
@GET
@Produces("text/plain")
public Item item() {
return "The item " + id;
}
}
}
Convertitori di parametri personalizzati
Questo è un esempio di come implementare convertitori di parametri personalizzati per gli endpoint JAX-RS. L'esempio mostra due classi dalla libreria java.time
Java 8.
@Provider
public class ParamConverters implements ParamConverterProvider {
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType,
Type genericType,
Annotation[] annotations)
{
if (rawType == LocalDate.class)
return (ParamConverter<T>) new ParamConverter<LocalDate>() {
@Override
public LocalDate fromString(String value) {
return LocalDate.parse(value);
}
@Override
public String toString(LocalDate value) {
return null;
}
};
else if (rawType == MonthDay.class)
return (ParamConverter<T>) new ParamConverter<MonthDay>() {
@Override
public MonthDay fromString(String value) {
int[] ddmm = Arrays.stream(value.split("/"))
.mapToInt(Integer::parseInt)
.toArray();
return MonthDay.of(ddmm[1], ddmm[0]);
}
@Override
public String toString(MonthDay value) {
return null;
}
};
return null;
}
}
Nome vincolante
L'associazione dei nomi è un concetto che consente di dire a un runtime JAX-RS che un filtro specifico o un intercettore verranno eseguiti solo per un metodo di risorse specifico. Quando un filtro o un intercettore è limitato solo a un metodo di risorsa specifico, diciamo che è legato al nome . I filtri e gli intercettori che non hanno tale limite sono chiamati globali .
Definizione di un'annotazione di binding del nome
I filtri o gli intercettori possono essere assegnati a un metodo di risorsa usando l'annotazione @NameBinding
. Questa annotazione viene utilizzata come meta annotazione per altre annotazioni implementate dall'utente che vengono applicate a un provider e a metodi di risorse. Guarda il seguente esempio:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
L'esempio precedente definisce una nuova annotazione @Compress
che è un'annotazione di binding del nome poiché è annotata con @NameBinding
. L'annotazione @Compress
può essere utilizzata per associare filtri e interceptor agli endpoint.
Associazione di un filtro o intercettore a un endpoint
Si consideri che si dispone di un intercettore che esegue la compressione GZIP e si desidera associare tale intercettatore a un metodo di risorsa. Per farlo annota sia il metodo della risorsa che l'intercettatore, come segue:
@Compress
public class GZIPWriterInterceptor implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new GZIPOutputStream(outputStream));
context.proceed();
}
}
@Path("helloworld")
public class HelloWorldResource {
@GET
@Produces("text/plain")
public String getHello() {
return "Hello World!";
}
@GET
@Path("too-much-data")
@Compress
public String getVeryLongString() {
String str = ... // very long string
return str;
}
}
@Compress
viene applicato al metodo di risorsa getVeryLongString()
e all'intercettore GZIPWriterInterceptor
. L'intercettore verrà eseguito solo se verrà eseguito qualsiasi metodo di risorsa con tale annotazione.
Nell'esempio sopra, l'interceptor verrà eseguito solo per il metodo getVeryLongString()
. L'intercettore non verrà eseguito per il metodo getHello()
. In questo esempio la ragione è probabilmente chiara. Vorremmo comprimere solo dati lunghi e non è necessario comprimere la risposta breve di "Hello World!"
.
L'associazione del nome può essere applicata a una classe di risorse. Nell'esempio HelloWorldResource
verrà annotato con @Compress
. Ciò significherebbe che tutti i metodi di risorse useranno la compressione in questo caso.
Si noti che i filtri globali vengono sempre eseguiti, quindi anche per i metodi di risorse con annotazioni di associazione dei nomi.