JAX-RS 使用注解进行配置,所以用它开发REST 风格的服务非常简单。楼主在本文用一个小例子来说明JAX-RS 的基本用法。
假设楼主要开发一个小电影服务,客户端可以通过请求URI 对电影进行CRUD 操作。为简明起见,这儿不使用数据库,只在内存中模拟。先用一个非常简单的Movie类,在后续的文章中根据情况逐步扩充:
1 2 3 4 5 |
publicclassMovie { privateintid; privateString title; // 此处省略若干行 } |
嗯,就是一个很普通的JavaBean,实际项目中可以根据需要加上@Entity等注解。接下来看看如何编写JAX-RS 服务。
一个JAX-RS 服务就是一个使用了JAX-RS 注解来将HTTP 请求绑定到方法的Java 类,一共支持两种类型:单请求对象或单例对象。单请求对象意味着每来一个请求,就创建一个服务对象,在请求结束时销毁。单例对象则意味着只有一个服务对象处理所有的请求,从而可以在多个请求间维持服务状态。JAX-RS 服务可通过继承javax.ws.rs.core.Application来定义,其中的getClasses方法返回单请求对象的类型,getSingletons方法返回单例对象的类型。这两个方法是可选的。在Java EE 6 环境中,如果这两个方法都返回null或者空集合,那么应用程序中的所有JAX-RS 都将被部署。这时可以用CDI 的@javax.inject.Singleton或者EJB 的@javax.ejb.Singleton注解来指定单例对象。
如果电影服务的上下文根路径为http://localhost/ms,而楼主希望将服务部署到http://localhost/ms/rest 下面,只需要写一个类:
1 2 3 |
@ApplicationPath("rest") publicclassRestApplication extendsApplication { } |
@ApplicationPath注解指定所有服务的相对基址,如果为空字符串,则直接使用上下文根路径。另一种配置方式是在web.xml 文件中进行声明,那是为了使JAX-RS 能在Servlet 容器(例如Tomcat)中运行,此处略过。这项配置必不可少,否则无法部署服务。
很好很强大,现在开始编写电影服务类MovieService,先看看声明和初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Singleton @Path("movie") publicclassMovieService { privateAtomicInteger ai; privateConcurrentMap movieMap; @PostConstruct privatevoidinit() { ai = newAtomicInteger(); movieMap = newConcurrentHashMap<>(); intid = ai.getAndIncrement(); movieMap.put(id, newMovie().setId(id).setTitle("Avatar")); } |
因为楼主只需要一个“内存数据库”,所以用单例对象即可,此处使用CDI 的@javax.inject.Singleton来声明单例。@Path声明了一个服务,它指示MovieService负责处理发送到http://localhost/ms/rest/movie 的请求。路径的拼接方式非常直观。init方法带有@PostConstruct注解,因此将在MovieService构造完成后立即调用,它向movieMap中存入了一个ID 为0 的Movie对象。为简化代码,Movie的设置方法都返回this,有点伪造构建者模式的味道。
接下来看看如何处理HTTP 请求。
GET
GET 请求用于获取一个资源。在本例中用来获取一部电影的信息:
1 2 3 4 5 6 7 8 9 10 11 |
@GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) publicMovie find(@PathParam("id") intid) { Movie movie = movieMap.get(id); if(movie != null) { returnmovie; } else{ thrownewWebApplicationException(Response.Status.NOT_FOUND); } } |
该方法标注了@GET,表示用来处理向http://localhost/ms/rest/movie/{id} 发送的GET 请求。@Path再次用来绑定路径,注意其参数{id},它带有花括号,对应URI 的最后一段,也正好和方法参数id的@PathParam的值相对应。这种参数还有很多高级用法,以后再介绍。@Produces注解指定输出格式为JSON。JAX-RS 内置了很多格式,详见MediaType的文档。如果找到了相应ID 的对象,则直接返回,JAX-RS 会自动加上响应码200 OK;否则抛出异常,错误码为404 Not Found。
例如,通过浏览器访问http://localhost/ms/rest/movie/0,得到的结果为{"@id":"0","@title":"Avatar"}。
POST
POST 请求用于创建一个资源。在本例中用来创建一部电影:
1 2 3 4 5 6 7 |
@POST @Consumes(MediaType.APPLICATION_JSON) publicResponse create(Movie movie) { intid = ai.getAndIncrement(); movieMap.put(id, movie.setId(id)); returnResponse.created(URI.create(String.valueOf(id))).build(); } |
由于没有@Path注解,所以POST 请求的目标就直接是http://localhost/ms/rest/movie。Consumes和@Produces相反,表示接受的数据类型,此处JAX-RS 会自动把JSON 数据转换为Movie对象。返回的响应码为201 Created,并且带有所创建资源的URI。
例如,向http://localhost/ms/rest/movie 发送POST 请求,正文为{"@title": "007"},则可以从FireBug 的网络监控中看到返回的响应码,以及头部中Location 的值为http://localhost:8080/rest/service/movie/1。多次发送该POST 请求,将会创建多个资源,以保证POST 不是幂等的。
PUT
PUT 请求用于创建或更新一个资源。与POST 不同,PUT 请求要指定某个特定资源的地址。在本例中用来更新一部电影的信息:
1 2 3 4 5 6 7 8 9 10 11 |
@PUT @Path("{id}") @Consumes(MediaType.APPLICATION_JSON) publicResponse update(@PathParam("id") intid, Movie movie) { movie.setId(id); if(movieMap.replace(id, movie) != null) { returnResponse.ok().build(); } else{ thrownewWebApplicationException(Response.Status.NOT_FOUND); } } |
更新成功就返回200 OK,否则返回404 Not Found。这儿先把movie对象的ID 强制改为URI 所指定的,以免出现不一致。也可以根据需求,将不一致作为异常处理,给客户端返回一个错误码。
顺便啰嗦一句,反正代码在自己手中,楼主也可以把PUT 搞成非幂等的,例如将PUT 当成POST 来处理,就像以前把GET 和POST 一视同仁那样。不过咱既然在搞JAX-RS,就还是要沾染一点REST 风格,严格遵守HTTP 才是。
DELETE
DELETE 请求用于删除一个资源。在本例中用来删除一部电影:
1 2 3 4 5 6 7 8 9 |
@DELETE @Path("{id}") publicResponse delete(@PathParam("id") intid) { if(movieMap.remove(id) != null) { returnResponse.ok().build(); } else{ thrownewWebApplicationException(Response.Status.NOT_FOUND); } } |
没什么特别的,该说的前面都说了。
HEAD 和OPTIONS 请求就忽略吧,用得不太多,也同样挺简单的。
JAX-RS 服务的部署和部署常规Web 程序一样,打包成war 文件就可以了。最后赞一下NetBeans 可以为REST 风格的服务自动生成测试页面,很好用,虽然在Firefox 下页面显示不正常,但IE 是可以的。