jersey

jersy是一个Web Service的框架,据说是符合RESTful架构的一种框架,这是一种新的思路,而不是新的技术,是对于前端的各种技术而言,所设计的后台实现方式,力图以一种统一的方便的方式组织为前端提供数据。
jersey的官网.

此次接触这个框架是业务需要了,但是,觉得这个框架比较简单,正好也有时间,所以就做一个学习实验,探索一下在资源短缺的情况下如何学习一个新的东西。事实上资源也确实不是很多,这一次主要依靠官方文档学习使用。

原本打算直接上代码的,但是在官网上看了一会后就发现有好多名词解释的问题。那就先来看几个名词。

首先第一个就是JAX-RS,这是JAVA EE6引进的新技术,全称Java api for RESTful Web Service. 主要是使用了注解的形式来简化Web开发和部署。然后跟Jersey的关系是Jersey实现了JAX-RS的框架接口,并且扩展了更多的东西,提供了自己的API。

然后学习Jersey的第一步就是搞懂他的路由方式,在这里就是注解了。

在讲注解之前还有一个不是很重要的名词:POJO(Plain Old Java Object), 称之为简单一般Java对象,这个概念是与JavaBean做区分的。其实没有什么必要,引用Martin Fowler的一句话:

“我们疑惑为什么人们不喜欢在他们的系统中使用普通的对象,我们得到的结论是——普通的对象缺少一个响亮的名字,因此我们给它们起了一个,并且取得了很好的效果。” ——Martin Fowler

所以事实上也就是个名词,所谓简单Java对象就是不包含业务逻辑的对象,一般用于描述数据实体。具体的区别等到写JavaBean的时候就看出来了,这里不讲了。

在我手中的项目使用了其中的两个包,maven代码如下:

1
2
3
4
5
6
7
8
9
10
11
<!-- jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.25</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.25</version>
</dependency>

可以看到一个是servlet的Container,另外一个是client客户端。具体的用法,等到用到再说。

注解路由

这里用的路由就是JAX-RS的规范。(一次写不完,慢慢补充)。

@Path

这个注解的原文解释非常好,所以有时间的还是去看一下原文是怎么写的吧,下面我就结合自己理解写一下。

这个@Path注解的内容是一个相对的URI路径,由其注解的类会被相应的路径访问到。

在下面这个例子中可以看到,这个类被一个@Path注解为”printers”,当URL路径为/printers的时候就会路由到这个类中,那么这个类有那么多函数,调用那个函数呢?下面还有子路径以及路径的通信方式。

首先HTTP-Methods都是被支持的,常用的有:

  • @POST
  • @GET
  • @PUT
  • @DELETE
    并且对于每个函数都可以进行@Path的进一步注解,有过Web开发经验的人都知道是怎么玩的。如果你的URL路径是/printers/list, 就会定位到getListOfPrinters()函数。同理可知其他的用法。
    需要说的可能是如果一个函数没有注解而其他的函数有注解,在路径为printers的时候就会定位那个没有注解的,因为其他的都是精确匹配的,按照精确匹配无法匹配到其他的函数,这个是不精确匹配的,所以就过来了。

对于/,开始和结尾的位置可以加可以不加,都能够被解析。除此以外,这里还有些内容你可能看不太懂,下面会讲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Path("/printers")
public class PrintersResource {

@GET
@Produces({"application/json", "application/xml"})
public WebResourceList getMyResources() { ... }

@GET @Path("/list")
@Produces({"application/json", "application/xml"})
public WebResourceList getListOfPrinters() { ... }

@GET @Path("/jMakiTable")
@Produces("application/json")
public PrinterTableModel getTable() { ... }

@GET @Path("/jMakiTree")
@Produces("application/json")
public TreeModel getTree() { ... }

@GET @Path("/ids/{printerid}")
@Produces({"application/json", "application/xml"})
public Printer getPrinter(@PathParam("printerid") String printerId) { ... }

@PUT @Path("/ids/{printerid}")
@Consumes({"application/json", "application/xml"})
public void putPrinter(@PathParam("printerid") String printerId, Printer printer) { ... }

@DELETE @Path("/ids/{printerid}")
public void deletePrinter(@PathParam("printerid") String printerId) { ... }
}

注解除了简单的字符串以外,按照JAX-RS的规范,在路径中可以设定变量。也就是上面看到的@Path("{printerid}")的用法,这里的话花括号中的部分就是变量,是用URL的方式传参过来的。对应的在其函数的定义中,参数列表中就对这个参数做了声明:@PathParam("printerid"),就作为参数传进来了。对于参数,还可以进行正则匹配,格式如下:@Path("{name: [a-zA-Z]*}"),则如果参数符合大小写字母的限制就能够正确匹配,否则就是404了。

@Produce

很明显这个注解是描述了这个方法或是类可以生产什么东西,什么类型的东西,也就是返回给Client什么类型的内容,如果是text/html,则返回的就是一个页面。这个东西在HTTP请求的accept字段会有限制,如果accept注明了接受什么类型的数据,那么就会选择对应的生产者去调用,在其他注解方式相同的情况下。

可以选择多个MIME媒体类型。

@Consumes

消费者,就是这个类或方法可以接受什么媒体类型的数据,例如:@Consumes(MediaType.APPLICATION_JSON)就说明该函数可以接受Json类型的数据。具体的用法还没有用过,暂时不写。

@POST

如果我们用jersey来做API的话,一般就是将POST的报文转换成JSON字符串整个传过来。这个时候参数直接就传做函数的参数,不需要做什么说明。事实上不做注解说明的函数就是被默认塞进来的参数。

还有一种是表单参数。这个目前没有做过,不过我猜也是一样的。

Jersey Client

做API最常用的就是Client了,一个以http协议为基础的请求发送端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Client client = ClientBuilder.newClient();
WebTarget target = client.target(url);

// put
Response response = target.request().accept(MediaType.APPLICATION_JSON_TYPE).put(
Entity.entity(obj.toJSONString(), MediaType.APPLICATION_JSON_TYPE));

// post
Response response = target.request().accept(MediaType.APPLICATION_JSON_TYPE).post(
Entity.entity(inputJson, MediaType.APPLICATION_JSON));

// get (参数放在url中)
Response response = target.request().get();

if(response == null || response.getStatus() != 200 )
// 请求失败

String detail = response.readEntity(String.class);
JSONObject object = JSON.parseObject(objString);

client.close();

基本用法就是这样了,可以用来对自己的API进行测试,也可以去调其他的API。

jesey的几种参数注解

在写接口的时候接受的参数有这么几种,上面已经介绍过了@PathParam, 下面就系统的介绍一下几种参数。

@PathParam()

可以认为是定义在路径中的一个变量。程序提取对应路径位置的字符串作为对应的参数。使用方式在上面有所以不详细介绍了。

@QueryParam()

该参数用于获取Get请求中的查询参数,他和上一个的区别是它是通过URI中的?符号来实现的。

1
2
3
4
5
@GET
@Path("/user")
@Produces("text/plain")
public User getUser(@QueryParam("name") String name, @QueryParam("age") int age) {
...}

当请求是:http://localhost:8080/user?name=cesar&age=21时会被这个方法捕获。

@FormParam()

顾名思义就是表单中的数据

1
2
3
4
5
@POST
@Consumes("application/x-www-form-urlencoded")
publicvoid post(@FormParam("name") String name) {
// Store the message
}

默认参数值@DefaultValue

当你希望在函数获取参数时参数有一个默认值,那么就可以使用该注释,它的使用方法如下

1
2
3
4
5
@GET
@Path("/user")
@Produces("text/plain")
public User getUser(@QueryParam("name") String name, @DefaultValue("26") @QueryParam("age") int age) {
...}

使用Map的参数@Context

在一个大型的server中,由于参数的多变,参数结构的调整很容易遇到问题,这时候就可以考虑使用@Context来进行注释了。例子如下:

1
2
3
4
5
@GET
public String get(@Context UriInfo ui) {
MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
MultivaluedMap<String, String> pathParams = ui.getPathParameters();
}

接下来你会用到遍历map和删除map元素的操作来做一些处理。

Talk is not cheap.