在本节中,我们会针对前面两篇 Jersey 文章的资源,请求参数绑定等做一些补充说明。
@Context 注入特殊资源
在 SpringMVC 中,让我们影响深刻的有一个标签是 @Autowire。能够注入一些非常特殊的对象,比如 ApplicationEventPublisher,在 Web 环境下能注入 ServletContext 等等,在 SpringMVC 中,还能在每一个 Controller 方法参数中注入 HttpServletRequest,HttpSession 等特殊对象,其实在 Jersey 中也可以实现,需要用到 @Context 注解。
获取 URI 信息(UriInfo)
/**
* 使用@Context获取请求上下文内容
*
* @param ui
* @return
*/
@GET
@Path("/formui")
public String formPojoParam(@Context UriInfo ui) {
MultivaluedMap<String, String> qps = ui.getQueryParameters();
MultivaluedMap<String, String> pps = ui.getPathParameters();
System.out.println(qps);
System.out.println(pps);
return "success";
}
在 JAX-RS 中,一个 UriInfo 对象封装了应用相关信息和本次请求相关信息。并在 UriInfo 中提供了一些有用的方法:
比如针对请求 GET localhost:8082/webapi/param/formui?name=haha
//获取资源路径:param/formui
System.out.println(ui.getPath());
//获取完整请求路径:http://localhost:8082/webapi/param/formui
System.out.println(ui.getAbsolutePath());
//获取请求根路径:http://localhost:8082/webapi/
System.out.println(ui.getBaseUri());
//获取匹配请求的资源类[cn.wolfcode.jersey._04parameters.ParameterRest@73ea3756]
System.out.println(ui.getMatchedResources());
//获取尝试匹配的资源路径[param/formui, param]
System.out.println(ui.getMatchedURIs());
//获取完整请求URIhttp://localhost:8082/webapi/param/formui?name=haha
System.out.println(ui.getRequestUri());
//获取请求参数列表{name=[haha]},类型为MultivaluedMap<String, String>
System.out.println(ui.getQueryParameters());
//获取路径参数列表{}
System.out.println(ui.getPathParameters());
获取请求头信息
可以通过 @Context 直接获取请求头相关信息:
/**
* 通过@Context获取请求头信息
*
* @param headers
* @return
*/
@GET
@Path("/headps")
public String headps(@Context HttpHeaders headers) {
System.out.println(headers.getRequestHeaders());
return "success";
}
在 JAX-RS 中,提供了 HttpHeaders 接口来描述一个请求头信息,在该接口中也提供了很多请求头相关的有用的方法:
//获取请求头中的指定值dfse-34fd-1ggd-34pe
System.out.println(headers.getHeaderString("token"));
//获取接受的语言[*]
System.out.println(headers.getAcceptableLanguages());
//获取接受的MIME类型[application/xml]
System.out.println(headers.getAcceptableMediaTypes());
//获取cookie信息
System.out.println(headers.getCookies());
//获取请求头中的指定值[dfse-34fd-1ggd-34pe]
System.out.println(headers.getRequestHeader("token"));
//获取请求头所有信息
//{content-type=[application/x-www-form-urlencoded], accept=[application/xml], token=[dfse-34fd-1ggd-34pe], cache-control=[no-cache],
System.out.println(headers.getRequestHeaders());
获取请求处理相关信息
@Context 还可以获取一个 Request 对象:
/**
* 通过@Context获取请求信息
*
* @param headers
* @return
*/
@GET
@Path("/request")
public String request(@Context Request request) {
System.out.println(request);
return "success";
}
但是注意,在 JAX-RS 中,这个 Request 对象并不代表的是请求对象本身,他是一个有关请求处理的辅助类,这个类主要的作用是用来做请求的预处理相关内容。那如果要真正获取请求,继续向下看。
获取 Servlet 相关对象
如果上面几个案例通过 @Context 获取的都是请求相关的抽象,那是因为我们之前说过,Jersey 可以脱离 Servlet 环境运行。那么,如果我们的应用确实运行在 Servlet 环境下,获取 Servlet 相关对象,比如 ServletContext,ServletRequest,ServletResponse 就显得很有必要。@Context 注解给我们提供了这个能力。
/**
* 在Servlet环境下使用@Context注入Servlet对象
* @param ctx
* @param req
* @param resp
* @return
*/
@GET
@Path("/servlet")
public String servlet(@Context ServletContext ctx,
@Context HttpServletRequest req,@Context HttpServletResponse resp){
System.out.println(ctx);
System.out.println(req);
System.out.println(resp);
return "success";
}
但是再次注意,要获取 Servlet 相关对象,必须运行在 Servlet 环境下。
子资源定位器
在特殊情况下,我们可能会对某个重复使用的子资源进行抽象。在这种情况下,我们就可以把某个子资源单独的放到一个类中,由主资源类在特定的资源方法中返回即可。以下示例作为一个演示:
创建一个主资源类:
@Path("parent")
public class ParentResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public Employee get(@Context ServletContext ctx) {
System.out.println(ctx);
return new Employee(1L, "哈哈", 2);
}
@Path("child")
public ChildResource childResource(){
return new ChildResource();
}
}
注意,第一个资源 parent,就是一个正常的资源方法,那么在请求 / parent 这个资源的时候,get 方法就会被正常执行。但是第二个资源 child,这个方法返回的是一个 ChildResource 对象,那么在请求 / parent/child 资源的时候,就不是直接在这个方法中处理了,而是继续交给 ChildResource 进行处理。
我们来看看 ChildResource 类:
public class ChildResource {
@GET
@Path("{version}")
public String child(@PathParam("version") @DefaultValue("2.0") String version) {
return version;
}
}
在这个子资源类中,我们有一个资源路径 {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 的资源类是怎么处理请求的呢?
@GET
@Path("{id}")
public String pathParam(@PathParam("id") Long id) {
System.out.println(this);
return "success";
}
我们写了一个简单的测试方法,在方法中打印 this,连续两次请求该方法,
cn.wolfcode.jersey._04parameters.ParameterRest@c065304
cn.wolfcode.jersey._04parameters.ParameterRest@e5a25a9
可以看到,每次请求都会创建一个全新的资源类对象。从这点上,我们可以理解为,Jersey 默认的资源类的使用方式类似于 Struts2。但我们知道,这种方式在性能上面表现的并不是很好,比如一百万请求过来,就需要创建一百万个对象来处理,而调用的仅仅是对象中的某几个方法。所以我们可以这样设置:
@Path("param")
@Singleton
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,子资源方法参数中。
比如:
public class ChildResource {
@Context
private ServletContext ctx;
@GET
@Path("{version}")
public String child(@PathParam("version") @DefaultValue("2.0") String version) {
return version;
}
}
但是需要注意一点,如果资源类被标记为 @Singleton 的,不要在类属性中标记和请求相关的线程不安全的内容。虽然官方文档上介绍,可以这样使用:
@Path("resource")
@Singleton
public class MySingletonClass{
@Context
Request request;
}
就算 Request 是线程相关的,但是官方文档中解释这种方式是允许的,因为注入的是 Request 的代理,在执行阶段才是真正使用的是当前的请求。不过我的建议还是按照 SpringMVC 的注入方式,线程相关的内容都通过方法参数注入,ServletContext 这种全局的对象,才在类级别注入。
小结
在本节中,我们讨论了 @Context 标签的使用,也了解了 Jersey 中资源类的生命周期和建议使用方式。当然,后面在把 Jersey 和 Spring 或者使用 Springboot 集成 Jersey 之后,这些资源类还是会交回给容器来管理。