在本节中,我们会针对前面两篇 Jersey 文章的资源,请求参数绑定等做一些补充说明。

@Context 注入特殊资源

在 SpringMVC 中,让我们影响深刻的有一个标签是 @Autowire。能够注入一些非常特殊的对象,比如 ApplicationEventPublisher,在 Web 环境下能注入 ServletContext 等等,在 SpringMVC 中,还能在每一个 Controller 方法参数中注入 HttpServletRequest,HttpSession 等特殊对象,其实在 Jersey 中也可以实现,需要用到 @Context 注解。

获取 URI 信息(UriInfo)

  1. /**
  2. * 使用@Context获取请求上下文内容
  3. *
  4. * @param ui
  5. * @return
  6. */
  7. @GET
  8. @Path("/formui")
  9. public String formPojoParam(@Context UriInfo ui) {
  10. MultivaluedMap<String, String> qps = ui.getQueryParameters();
  11. MultivaluedMap<String, String> pps = ui.getPathParameters();
  12. System.out.println(qps);
  13. System.out.println(pps);
  14. return "success";
  15. }

在 JAX-RS 中,一个 UriInfo 对象封装了应用相关信息和本次请求相关信息。并在 UriInfo 中提供了一些有用的方法:
比如针对请求 GET localhost:8082/webapi/param/formui?name=haha

  1. //获取资源路径:param/formui
  2. System.out.println(ui.getPath());
  3. //获取完整请求路径:http://localhost:8082/webapi/param/formui
  4. System.out.println(ui.getAbsolutePath());
  5. //获取请求根路径:http://localhost:8082/webapi/
  6. System.out.println(ui.getBaseUri());
  7. //获取匹配请求的资源类[cn.wolfcode.jersey._04parameters.ParameterRest@73ea3756]
  8. System.out.println(ui.getMatchedResources());
  9. //获取尝试匹配的资源路径[param/formui, param]
  10. System.out.println(ui.getMatchedURIs());
  11. //获取完整请求URIhttp://localhost:8082/webapi/param/formui?name=haha
  12. System.out.println(ui.getRequestUri());
  13. //获取请求参数列表{name=[haha]},类型为MultivaluedMap<String, String>
  14. System.out.println(ui.getQueryParameters());
  15. //获取路径参数列表{}
  16. System.out.println(ui.getPathParameters());

获取请求头信息

可以通过 @Context 直接获取请求头相关信息:

  1. /**
  2. * 通过@Context获取请求头信息
  3. *
  4. * @param headers
  5. * @return
  6. */
  7. @GET
  8. @Path("/headps")
  9. public String headps(@Context HttpHeaders headers) {
  10. System.out.println(headers.getRequestHeaders());
  11. return "success";
  12. }

在 JAX-RS 中,提供了 HttpHeaders 接口来描述一个请求头信息,在该接口中也提供了很多请求头相关的有用的方法:

  1. //获取请求头中的指定值dfse-34fd-1ggd-34pe
  2. System.out.println(headers.getHeaderString("token"));
  3. //获取接受的语言[*]
  4. System.out.println(headers.getAcceptableLanguages());
  5. //获取接受的MIME类型[application/xml]
  6. System.out.println(headers.getAcceptableMediaTypes());
  7. //获取cookie信息
  8. System.out.println(headers.getCookies());
  9. //获取请求头中的指定值[dfse-34fd-1ggd-34pe]
  10. System.out.println(headers.getRequestHeader("token"));
  11. //获取请求头所有信息
  12. //{content-type=[application/x-www-form-urlencoded], accept=[application/xml], token=[dfse-34fd-1ggd-34pe], cache-control=[no-cache],
  13. System.out.println(headers.getRequestHeaders());

获取请求处理相关信息

@Context 还可以获取一个 Request 对象:

  1. /**
  2. * 通过@Context获取请求信息
  3. *
  4. * @param headers
  5. * @return
  6. */
  7. @GET
  8. @Path("/request")
  9. public String request(@Context Request request) {
  10. System.out.println(request);
  11. return "success";
  12. }

但是注意,在 JAX-RS 中,这个 Request 对象并不代表的是请求对象本身,他是一个有关请求处理的辅助类,这个类主要的作用是用来做请求的预处理相关内容。那如果要真正获取请求,继续向下看。

获取 Servlet 相关对象

如果上面几个案例通过 @Context 获取的都是请求相关的抽象,那是因为我们之前说过,Jersey 可以脱离 Servlet 环境运行。那么,如果我们的应用确实运行在 Servlet 环境下,获取 Servlet 相关对象,比如 ServletContext,ServletRequest,ServletResponse 就显得很有必要。@Context 注解给我们提供了这个能力。

  1. /**
  2. * 在Servlet环境下使用@Context注入Servlet对象
  3. * @param ctx
  4. * @param req
  5. * @param resp
  6. * @return
  7. */
  8. @GET
  9. @Path("/servlet")
  10. public String servlet(@Context ServletContext ctx,
  11. @Context HttpServletRequest req,@Context HttpServletResponse resp){
  12. System.out.println(ctx);
  13. System.out.println(req);
  14. System.out.println(resp);
  15. return "success";
  16. }

但是再次注意,要获取 Servlet 相关对象,必须运行在 Servlet 环境下。

子资源定位器

在特殊情况下,我们可能会对某个重复使用的子资源进行抽象。在这种情况下,我们就可以把某个子资源单独的放到一个类中,由主资源类在特定的资源方法中返回即可。以下示例作为一个演示:

创建一个主资源类:

  1. @Path("parent")
  2. public class ParentResource {
  3. @GET
  4. @Produces(MediaType.APPLICATION_JSON)
  5. public Employee get(@Context ServletContext ctx) {
  6. System.out.println(ctx);
  7. return new Employee(1L, "哈哈", 2);
  8. }
  9. @Path("child")
  10. public ChildResource childResource(){
  11. return new ChildResource();
  12. }
  13. }

注意,第一个资源 parent,就是一个正常的资源方法,那么在请求 / parent 这个资源的时候,get 方法就会被正常执行。但是第二个资源 child,这个方法返回的是一个 ChildResource 对象,那么在请求 / parent/child 资源的时候,就不是直接在这个方法中处理了,而是继续交给 ChildResource 进行处理。
我们来看看 ChildResource 类:

  1. public class ChildResource {
  2. @GET
  3. @Path("{version}")
  4. public String child(@PathParam("version") @DefaultValue("2.0") String version) {
  5. return version;
  6. }
  7. }

在这个子资源类中,我们有一个资源路径 {version},那么真正在处理 / parent/child / 或者 / parent/child/{version} 资源的时候,就是由这个方法处理的。

当我们执行请求 GET /parent/child 的时候,得到 2.0 这个值;
当执行请求 GET /parent/child/3.0 的时候,得到 3.0 这个值;

资源类的生命周期

我们一直忽略没有讨论的一个问题就是线程安全问题。我们知道,在 Struts2 中,每一个请求创建一个全新的 Action 实例,所以我们能够在 Action 的成员变量中绑定请求相关的参数;而在 SpringMVC 中,因为参数是绑定在方法列表中的,所以 Controller 可以是单例的,所以我们不在 Controller 中使用线程不安全的成员变量。

那么在 Jersey 中是怎么样的呢?Jersey 的资源类是怎么处理请求的呢?

  1. @GET
  2. @Path("{id}")
  3. public String pathParam(@PathParam("id") Long id) {
  4. System.out.println(this);
  5. return "success";
  6. }

我们写了一个简单的测试方法,在方法中打印 this,连续两次请求该方法,
cn.wolfcode.jersey._04parameters.ParameterRest@c065304
cn.wolfcode.jersey._04parameters.ParameterRest@e5a25a9
可以看到,每次请求都会创建一个全新的资源类对象。从这点上,我们可以理解为,Jersey 默认的资源类的使用方式类似于 Struts2。但我们知道,这种方式在性能上面表现的并不是很好,比如一百万请求过来,就需要创建一百万个对象来处理,而调用的仅仅是对象中的某几个方法。所以我们可以这样设置:

  1. @Path("param")
  2. @Singleton
  3. public class ParameterRest {

我们在资源类上添加 javax.inject.Singleton 注解,再次请求,结果变为:
cn.wolfcode.jersey._04parameters.ParameterRest@2f831231
cn.wolfcode.jersey._04parameters.ParameterRest@2f831231

按照这种使用方式,我们就和使用 SpringMVC 的 controller 类似了。同样的,在子资源类中,也可以使用 @Singleton 进行注解。

注入的位置

前面介绍的所有参数绑定注解,包括 @PathParam,@QueryParam,@FormParam,@BeanParam,@HeaderParam,@Context 等都可以在主资源类,或者子资源类的属性,构造方法参数,资源方法参数,Setter,子资源方法参数中。
比如:

  1. public class ChildResource {
  2. @Context
  3. private ServletContext ctx;
  4. @GET
  5. @Path("{version}")
  6. public String child(@PathParam("version") @DefaultValue("2.0") String version) {
  7. return version;
  8. }
  9. }

但是需要注意一点,如果资源类被标记为 @Singleton 的,不要在类属性中标记和请求相关的线程不安全的内容。虽然官方文档上介绍,可以这样使用:

  1. @Path("resource")
  2. @Singleton
  3. public class MySingletonClass{
  4. @Context
  5. Request request;
  6. }

就算 Request 是线程相关的,但是官方文档中解释这种方式是允许的,因为注入的是 Request 的代理,在执行阶段才是真正使用的是当前的请求。不过我的建议还是按照 SpringMVC 的注入方式,线程相关的内容都通过方法参数注入,ServletContext 这种全局的对象,才在类级别注入。

小结

在本节中,我们讨论了 @Context 标签的使用,也了解了 Jersey 中资源类的生命周期和建议使用方式。当然,后面在把 Jersey 和 Spring 或者使用 Springboot 集成 Jersey 之后,这些资源类还是会交回给容器来管理。