JAX-RS基本概念理解

JAX-RS,全称为Java API for RESTful Web Services.的核心概念是resource,即面向资源。

JAX-RS的JavaDoc可以在这里找到。
JAX-RS的标准可以在这里找到。

1. Root Resource Classes

满足下列2个条件的POJO类被称为Root Resource Class:

  • 使用@Path注解
  • 至少有一个方法使用@Path或者资源方法注解(如@GET,@DELETE)

下面的HelloworldResource就是这样一样例子:

import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("helloworld") // 条件1
public class HelloWorldResource {
    public static final String CLICHED_MESSAGE = "Hello World!";

    @GET // 条件2
    @Produces("text/plain")
    public String getHello() {
        return CLICHED_MESSAGE;
    }
}

1.1. @Path

@Path注解的值是一个相对的URI路径。@Path的有没有/开头是一样的,同理,结尾有没有包含/也是一样的。上面例子中的/helloworld是最简单的一个例子,但是JAX-RS允许我们在路径中嵌入各种变量。

路径模板在路径中嵌入了以{}包含的变量,这个变量在运行时(资源被请求时)替换成实际的值。例如:

@Path("/users/{username}")

参数的实际值在资源方法中使用@PathParam提取:

@Path("/users/{username}")
public class UserResource{
    @GET
    @Produces("text/xml")
    public String getUser(@PathParam("username") String username){
        ...
    }
}

我们还可以对模板参数的格式做约束,例如我们只允许大小写字符以及数字,则可以使用下面的正则表达式来限制模板参数:

@Path("users/{username: [a-zA-z_0-9]*}")

如果请求路径不符合要求,将会返回404.

1.2 @GET, @PUT, @POST, @DELETE, … (HTTP Methods)

@GET, @PUT, @POST, @DELETE, @HEAD这些注解称为resource method designator,与HTTP规范中定义的方法一致。这些方法决定资源的行为。

1.3 @Produce

@Produce注解指定返回给客户端的MIME媒体类型。可以用于注解类或者注解方法。

@Path("/myResource")
@Produces("text/plain")
public class SomeResource {
    @GET
    public String doGetAsPlainText() {
        ...
    }

    @GET
    @Produces("text/html")
    public String doGetAsHtml() {
        ...
    }
}

如果类中的方法没有指定,则默认使用类级别的@Produce值。@Produce注解可以指定多个值,同时可以指定quality factor:

@GET
@Produces({"application/xml; qs=0.9", "application/json"})
public String doGetAsXmlOrJson() {
    ...
}

@Consumes

该注解用于指定可以接受的客户端请求的MIME媒体类型:

@POST
@Consumes("text/plain")
public void postClichedMessage(String message) {
    // Store the message
}

注意上述方法返回void,表示没有内容,此时给客户端返回204(No Content)

2. 参数注解(@*Param)

参数注解用于从请求中提取参数,例如上面的@PathParam用于提取路径中的参数。

2.1 @QueryParam

@QueryParam注解用于提取查询参数:

@Path("smooth")
@GET
public Response smooth(
    @DefaultValue("2") @QueryParam("step") int step,
    @DefaultValue("true") @QueryParam("min-m") boolean hasMin,
    @DefaultValue("true") @QueryParam("max-m") boolean hasMax,
    @DefaultValue("true") @QueryParam("last-m") boolean hasLast,
    @DefaultValue("blue") @QueryParam("min-color") ColorParam minColor,
    @DefaultValue("green") @QueryParam("max-color") ColorParam maxColor,
    @DefaultValue("red") @QueryParam("last-color") ColorParam lastColor) {
    ...
}

如果请求参数无法被正确地转化为相应的类型,返回404. 参数类型可以自定义:

public class ColorParam extends Color {

    public ColorParam(String s) {
        super(getRGB(s));
    }

    private static int getRGB(String s) {
        if (s.charAt(0) == '#') {
            try {
                Color c = Color.decode("0x" + s.substring(1));
                return c.getRGB();
            } catch (NumberFormatException e) {
                throw new WebApplicationException(400);
            }
        } else {
            try {
                Field f = Color.class.getField(s);
                return ((Color)f.get(null)).getRGB();
            } catch (Exception e) {
                throw new WebApplicationException(400);
            }
        }
    }
}

对这个类的约束就是要有一个接收字符串的构造函数。如果没有@DefaultValue注解,并且请求中未包含该参数,则方法的参数为各类型的“空值”。

2.2 @MatrixParam

从url片段中提取参数,即url中冒号后面的参数。

2.3 @HeaderParam

从请求的头部提取Header。

2.4 @CookieParam

提取cookie。

2.5 @FormParam

用于提取请求中媒体类型为”application/x-www-form-urlencoded” 的参数,根据相应的表单类型提取其中的参数。

@POST
@Consumes("application/x-www-form-urlencoded")
public void post(@FormParam("name") String name) {
    // Store the message
}

2.6 @BeanParam

该注解用于从请求的各部分中提取参数,并注入到对应的Bean中,例如我们有如下定义的Bean:

public class MyBeanParam {
    @PathParam("p")
    private String pathParam;

    @MatrixParam("m")
    @Encoded
    @DefaultValue("default")
    private String matrixParam;

    @HeaderParam("header")
    private String headerParam;

    private String queryParam;

    public MyBeanParam(@QueryParam("q") String queryParam) {
        this.queryParam = queryParam;
    }

    public String getPathParam() {
        return pathParam;
    }
    ...
}

这个Bean的各个属性都是使用前面的参数注解注解的,然后我们可以在resource class类的方法中使用这个类:

@POST
public void post(@BeanParam MyBeanParam beanParam, String entity) {
    final String pathParam = beanParam.getPathParam(); // contains injected path parameter "p"
    ...
}

当接到请求时,RS实现将从查询参数、头部、Cookie等各处提取参数,并注入到beanParam的各个属性中。

可以组合使用多种参数注解:

@POST
public void post(@BeanParam MyBeanParam beanParam, @BeanParam AnotherBean anotherBean, @PathParam("p") pathParam,
String entity) {
    // beanParam.getPathParam() == pathParam
    ...
}

3. 子资源(Sub-resources)

sub-resources的概念类似于Spring MVC框架中Controller的二级映射,及类级别上有一个@RequestMapping,方法上也有一个二级的@RequestMapping。 @Path注解可以用在类上,表示根资源(root resource),也可以用在类的方法上,这里的方法就叫sub-resource method,对应的资源叫sub-resource。

@Singleton
@Path("/printers")
public class PrintersResource {

    @GET
    @Produces({"application/json", "application/xml"})
    public WebResourceList getMyResources() { ... }

    @GET @Path("/list")
    @Produces({"application/json", "application/xml"})
    public WebResourceList getListOfPrinters() { ... }

    @GET @Path("/jMakiTable")
    @Produces("application/json")
    public PrinterTableModel getTable() { ... }

    @GET @Path("/jMakiTree")
    @Produces("application/json")
    public TreeModel getTree() { ... }

    @GET @Path("/ids/{printerid}")
    @Produces({"application/json", "application/xml"})
    public Printer getPrinter(@PathParam("printerid") String printerId) { ... }

    @PUT @Path("/ids/{printerid}")
    @Consumes({"application/json", "application/xml"})
    public void putPrinter(@PathParam("printerid") String printerId, Printer printer) { ... }

    @DELETE @Path("/ids/{printerid}")
    public void deletePrinter(@PathParam("printerid") String printerId) { ... }
}

此例中,如果url为printers,则对应到getMyResources方法,其他二级url分别对应各方法。

@Path的另一种使用场景是用来表示资源嵌套,此时@Path不与@GET之类的HTTP方法注解一起使用,例如:

@Path("/item")
public class ItemResource {
    @Context UriInfo uriInfo;

    @Path("content")
    public ItemContentResource getItemContentResource() {
        return new ItemContentResource();
    }

    @GET
    @Produces("application/xml")
        public Item get() { ... }
    }
}

public class ItemContentResource {

    @GET
    public Response get() { ... }

    @PUT
    @Path("{version}")
    public void put(@PathParam("version") int version,
                    @Context HttpHeaders headers,
                    byte[] in) {
        ...
    }
}

这里的@Path(“content”)没有指定方法注解,因此如果URL为/item/content,此时会继续往上递归,找到ItemContentResource这个资源类,然后针对该类使用URL匹配规则,所以/item/content将映射ItemContentResource的get方法,’/item/content/1.0’将映射到相应的put方法。
这里的getItemContentResource()方法因此也叫子资源定位器(sub-resource locator)

单例资源

正常情况下,资源的scope都是针对每个请求的,也就是说每个请求都会创建不同的资源实例,如果想要每个请求都返回一样的资源,此时需要使用单例注解@Singleton:

@Path("/item")
public class ItemResource {
    @Path("content")
    public Class<ItemContentSingletonResource> getItemContentResource() {
        return ItemContentSingletonResource.class;
    }
}

@Singleton
public class ItemContentSingletonResource {
    // this class is managed in the singleton life cycle
}

4. Root Resource Classes的生命周期

root resource默认的生命周期是请求范围的,也就是器生命周期在一个请求内有效。另外Jersey支持两种不同生命周期:

Scope 注解 类全称 说明
Request @RequestScoped或者空 org.glassfish.jersey.process.internal.RequestScoped 默认的生命周期
Per-lookup @PerLookup org.glassfish.hk2.api.PerLookup
Singleton @Singleton javax.inject.Singleton 一个JAX-RS应用只有一个实例,可以在类上使用@Singleton注解或者使用Application注册

5. 注入规则(Rules of Injection)

正常情况下,可以将请求的各种值注入到参数注解注解的对象,例如属性,方法参数,构造函数等。但是有一些特别的注入规则,会根据注入资源的生命周期有所不同,例如有些参数无法注入单例资源:

@Path("resource")
@Singleton
public static class MySingletonResource {

    @QueryParam("query")
    String param; // WRONG: initialization of application will fail as you cannot
                  // inject request specific parameters into a singleton resource.

    @GET
    public String get() {
        return "query param: " + param;
    }
}

事实上,所以跟某个特定请求相关的参数,都不能被注入到单例资源中,这些规则的验证会在应用启动的时候进行。

一些特殊的对象可以被注入到单例的构造函数或者属性中,此时RS运行时会注入对应的代理类,要使用这些特殊的注入,需要使用@Context注解:

@Path("resource")
@Singleton
public static class MySingletonResource {
    @Context
    Request request; // this is ok: the proxy of Request will be injected into this singleton

    public MySingletonResource(@Context SecurityContext securityContext) {
        // this is ok too: the proxy of SecurityContext will be injected
    }

    @GET
    public String get() {
        return "query param: " + param;
    }
}

总结一下注入类型,有:

  • Class fields: 注入类的域中
  • 构造函数:注入的值将用于调用构造函数
  • Resource Method:资源的各方法中注入
  • sub resource locator:不带方法注解的@Path
  • setter方法:只能使用@Context注解

下面是一个综合的例子:

@Path("resource")
public static class SummaryOfInjectionsResource {
    @QueryParam("query")
    String param; // injection into a class field


    @GET
    public String get(@QueryParam("query") String methodQueryParam) {
        // injection into a resource method parameter
        return "query param: " + param;
    }

    @Path("sub-resource-locator")
    public Class<SubResource> subResourceLocator(@QueryParam("query") String subResourceQueryParam) {
        // injection into a sub resource locator parameter
        return SubResource.class;
    }

    public SummaryOfInjectionsResource(@QueryParam("query") String constructorQueryParam) {
        // injection into a constructor parameter
    }


    @Context
    public void setRequest(Request request) {
        // injection into a setter method
        System.out.println(request != null);
    }
}

public static class SubResource {
    @GET
    public String get() {
        return "sub resource";
    }
}

6. @Context的使用

Context注解一般用于获取request或者response相关的上下文,例如UriInfo。对于那些Java类型可以使用@Context注解,请看参考JAX-RS规范第5章。

7. 编程式的资源模型

资源可以通过类或者实例构建,但是也可以从编程式的资源模型构建。所有使用resource class创建的资源都可以通过编程式的资源构建api来完成。通过代码编程式创建资源的方法,请参考这里。

参考

https://jersey.java.net/documentation/latest/jaxrs-resources.html

你可能感兴趣的:(JAX-RS基本概念理解)