JAX-RS: developing a simple service

JAX-RS uses annotations for configuration, so developing REST-style services with it is very simple. The author uses a small example in this article to illustrate the basic usage of JAX-RS.

 

Assume that the author is developing a small movie service, and the client can perform CRUD operations on movies by requesting URI. For simplicity, no database is used here, only in-memory simulation. First use a very simple Movie class, and gradually expand it according to the situation in subsequent articles:

 

 

1

2

3

4

5

publicclassMovie {

    private individuals;

    privateString title;

    // Omit some lines here

}

Well, it is just a very ordinary JavaBean. In actual projects, you can add @Entity and other annotations as needed. Next, let's look at how to write a JAX-RS service.

 

A JAX-RS service is a Java class that uses JAX-RS annotations to bind HTTP requests to methods. It supports two types: single request object or singleton object. A single request object means that for every request, a service object is created and destroyed when the request ends. A singleton object means that only one service object handles all requests, so that the service state can be maintained across multiple requests. JAX-RS services can be defined by inheriting javax.ws.rs.core.Application, where the getClasses method returns the type of a single request object, and the getSingletons method returns the type of a singleton object. These two methods are optional. In a Java EE 6 environment, if both methods return null or an empty collection, all JAX-RS in the application will be deployed. At this time, you can use CDI's @javax.inject.Singleton or EJB's @javax.ejb.Singleton annotation to specify a singleton object.

If the context root path of the movie service is http://localhost/ms, and the author wants to deploy the service under http://localhost/ms/rest, he only needs to write a class:

 

 

1

2

3

@ApplicationPath("rest")

publicclassRestApplication extendsApplication {

}

The @ApplicationPath annotation specifies the relative base address of all services. If it is an empty string, the context root path is used directly. Another configuration method is to declare it in the web.xml file, which is to enable JAX-RS to run in a Servlet container (such as Tomcat), which is skipped here. This configuration is essential, otherwise the service cannot be deployed.

 

It's very good and powerful. Now let's start writing the movie service class MovieService. Let's take a look at the declaration and initialization first:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

@Singleton

@Path("movie")

publicclassMovieService {

    privateAtomicInteger ai;

    privateConcurrentMap<Integer, Movie> 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 是可以的。


推荐一篇文章:

一个草根程序员创业之路的所感所悟-2016 


Guess you like

Origin blog.csdn.net/gridlayout/article/details/23884481