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.

Documentazione



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow