Jersey 开发RESTful(九)Jersey中的注入
【原创文章,转载请注明原文章地址,谢谢!】
在本节中,我们会针对前面两篇Jersey文章的资源,请求参数绑定等做一些补充说明。
@Context注入特殊资源
在SpringMVC中,让我们影响深刻的有一个标签是@Autowire。能够注入一些非常特殊的对象,比如ApplicationEventPublisher,在Web环境下能注入ServletContext等等,在SpringMVC中,还能在每一个Controller方法参数中注入HttpServletRequest,HttpSession等特殊对象,其实在Jersey中也可以实现,需要用到@Context注解。
获取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之后,这些资源类还是会交回给容器来管理。
![](https://img.haomeiwen.com/i807144/9f794c4e6bdfc908.jpeg)