java-ee
Java RESTful Web Services (JAX-RS)
Sök…
Anmärkningar
Till skillnad från SOAP och WS-stacken, som specificeras som W3C-standarder, är REST verkligen en uppsättning principer för design och användning av webbaserat gränssnitt. REST / RESTful applikationer förlitar sig starkt på andra standarder:
HTTP
URI, URL
XML, JSON, HTML, GIF, JPEG, and so forth (resource representations)
Rollen för JAX-RS (Java API för RESTful Web Services) är att tillhandahålla API: er som stöder byggandet av RESTful-tjänster. Men JAX-RS är bara ett sätt att göra detta . RESTful tjänster kan implementeras på andra sätt i Java och (verkligen) på många andra programmeringsspråk.
Enkel resurs
Först för en JAX-RS-applikation måste en bas-URI ställas in från vilken alla resurser kommer att finnas tillgängliga. För detta ändamål javax.ws.rs.core.Application
klassen javax.ws.rs.core.Application
utvidgas och kommenteras med annotationen javax.ws.rs.ApplicationPath
. Anteckningen accepterar ett strängargument som definierar bas-URI.
@ApplicationPath(JaxRsActivator.ROOT_PATH)
public class JaxRsActivator extends Application {
/**
* JAX-RS root path.
*/
public static final String ROOT_PATH = "/api";
}
Resurser är enkla POJO- klasser som kommenteras med @Path
kommentaren.
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;
}
}
När en HTTP GET
förfrågan skickas till /hello
svarar resursen med Hello StackOverflow!
meddelande.
GET-metodtyper
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
utan en parameter ger allt innehåll ("Hej världen!") Och GET
med sökvägsparameter ger den specifika bokstaven från den strängen.
Några exempel:
$ curl http://localhost/hello Hello World!
$ curl http://localhost/hello/0 H
$ curl http://localhost/hello/4 o
Obs: om du lämnar annotationen av @GET
(t.ex. @GET
ovan), är en begärningsmetod som standard en GET-förfrågningshanterare.
DELETE-metod
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();
}
}
Konsumera det med lock:
$ curl http://localhost/hello
Hello StackOverflow!
$ curl -X "DELETE" http://localhost/hello
$ curl http://localhost/hello
null
POST-metod
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();
}
}
Den första metoden kan åberopas via HTML-formulärinsändning genom att skicka fångade inmatningsparametrar. Åtgärd från formuläret ska peka på -
/hello/receiveParams
Den andra metoden kräver meddelande POJO med getters / seters. Alla REST-klienter kan kalla denna metod med JSON-inmatning som -
{"sender":"someone","message":"Hello SO!"}
POJO bör ha samma egenskap som JSON för att göra serienummering fungerar.
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;
}
}
Exception Mapper
@Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException exception) {
return Response.serverError().entity("Invalid input: " + exception.getMessage()).build();
}
}
Denna undantagsmapper kommer att fånga alla IllegalArgumentExceptions som kastas i applikationen och visa användaren ett tydligt meddelande istället för en stacktrace.
UriInfo
För att få information om URI som användaragenten använde för att få åtkomst till din resurs kan du använda @Context
parameteranteckningen med en UriInfo
parameter. UriInfo
objektet har några metoder som kan användas för att få olika delar av 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();
}
}
utgång från en GET till 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
Ibland är det av organisatoriska eller andra skäl vettigt att din resurs på toppnivå returnerar en underresurs som skulle se ut så här. (Din underresurs behöver inte vara en inre klass)
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;
}
}
}
Anpassade parameterkonverterare
Detta är ett exempel på hur man implementerar anpassade parameterkonverterare för JAX-RS-slutpunkter. Exemplet visar två klasser från Java 8: s java.time
bibliotek.
@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;
}
}
Namnets bindande
Namnbindning är ett koncept som gör det möjligt att säga till en JAX-RS-runtime att ett specifikt filter eller interceptor kommer att köras endast för en specifik resursmetod. När ett filter eller en interceptor endast är begränsad till en specifik resursmetod säger vi att det är namnbundet . Filter och fångar som inte har en sådan begränsning kallas globala .
Definiera en namnbindande kommentar
Filter eller avlyssnare kan tilldelas en @NameBinding
hjälp av anteckningen @NameBinding
. Den här kommentaren används som metaanotering för andra användarimplementerade kommentarer som tillämpas på en leverantör och resursmetoder. Se följande exempel:
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {}
Exemplet ovan definierar en ny @Compress
annotation som är en namnbindande annotation eftersom den är kommenterad med @NameBinding
. @Compress
anteckningen kan användas för att binda filter och interceptor till slutpunkter.
Bindning av ett filter eller avlyssnar till en slutpunkt
Tänk på att du har en interceptor som utför GZIP-komprimering och att du vill binda en sådan interceptor till en resursmetod. För att göra det, anteckna både resursmetoden och interceptorn enligt följande:
@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
tillämpas på getVeryLongString()
och på interceptorn GZIPWriterInterceptor
. Interceptorn kommer att köras endast om någon resursmetod med en sådan kommentar kommer att utföras.
I exemplet ovan kommer interceptorn att köras endast för getVeryLongString()
. Interceptorn körs inte för metoden getHello()
. I detta exempel är orsaken tydlig. Vi vill bara komprimera långa data och vi behöver inte komprimera det korta svaret "Hello World!"
.
Namnbindning kan tillämpas på en resursklass. I exemplet skulle HelloWorldResource
kommenteras med @Compress
. Detta skulle innebära att alla resursmetoder använder komprimering i detta fall.
Observera att globala filter alltid körs, så även för resursmetoder som har några namnbindande kommentarer.