RESTful和JAXRS

REST(全称Representational State Transfer)是Roy Thomas Fielding在2000年提出的一种基于HTTP的接口服务设计风格,而非一种新的传输协议,由于这种设计简洁优雅,近些年来逐渐流行起来。在JavaEE中,JAX-RS是一组用于构建RESTful风格的API,它使得在JavaEE平台上开发和部署RESTful服务变得更加简单。JAX-RS提供了基于注解的简洁的方式来定义资源以及如何与之交互,使得开发者可以轻松地将Java类和方法映射到HTTP请求方法和URI上。

这篇笔记我们学习什么是RESTful风格,以及JavaEE中JAX-RS的使用。本章节基于当前最新的JDK8、JavaEE8版本和Wildfly14应用服务器,Wildfly14中JAX-RS的实现框架和JAX-WS相同,都是CXF。

RESTful简介

REST基于HTTP现有的功能,定义了资源定位和资源操作(其实就是资源增删改查)两种类型的使用方式,包括利用HTTP协议的GET,POST,PUT,DELETE四种动作实现以下4种类型的资源操作:

  • GET 查询 对应数据库SELECT
  • POST 插入 对应数据库INSERT
  • PUT 更新 对应数据库UPDATE
  • DELETE 删除 对应数据库DELETE

REST风格要求URL参数中尽量不要有动词,因为“动作”已经在HTTP协议的动作字段中指定了。举例来说,获取id1的用户所有订单,如果使用传统的HTTP接口定义风格,我们可能会如下设计:

GET /getOrdersByUserId?userId=1

但RESTful要求我们按如下设计:

GET /v1/users/1/orders?_type=xml
  • /v1:我们设计的API可以携带一个版本号,以免API升级时给原来用户造成不便。
  • /users/1/orders/users代表所有用户集合,/1表示在所有用户集合中取id1的用户,/orders表示这个用户的所有订单集合,REST接口的资源定位描述大致就是按照这种形式构造的。
  • ?_type=xml:指定返回的数据类型是xml。REST接口可以带有参数,但是参数的作用类似于形容词,而不应该是动词。

更多的例子如下。

# 新建一个用户
POST /v1/users
# 修改id为1的用户信息
PUT /v1/users/1
# 删除id为1的用户
DELETE /v1/users/1

当客户端或服务端不支持PUT和DELETE时

REST相对HTTP使用的历史来说算是个新技术。由于一些历史原因,某些HTTP客户端或服务器不支持PUT和DELETE请求,此时一种替代方案是用POST代替,但在POST中加一个特殊参数例如method=put,用来表示这个POST请求是否按照REST形式解析,这种方式用的也十分广泛。

RESTful的适用场景

RESTful风格接口是对HTTP协议很好的诠释。使用REST接口,对网络资源的定位更加简单,RESTful风格结构清晰便于理解。但是当URL的层级过多时,REST接口的可读性就不好了。

当然上面说的这些都是理论上的,我个人认为REST接口用于参数较少,比较简单的资源定位还是十分方便的。比如就像前面举的例子,查询用户、订单等,此时RESTful风格的接口可读性非常好,层次十分清晰。但是一旦复杂起来就不行了,复杂的查询条件会让REST接口编写十分困难(换句话说就是RESTful风格下没法写出太复杂的查询条件),强行按RESTful设计的结果就是接口冗长而且可读性几乎没有,给接口调用带来困难。好在大多数情况下接口都是属于前者,因此RESTful风格一定程度上得以流行。

开发JAX-RS服务端

引入Maven依赖

这里我们基于Wildfly14应用服务器开发WebService,由于应用服务器已经内置了CXF,我们只需要引入JavaEE SDK即可。

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0.1</version>
    <scope>provided</scope>
</dependency>

工程搭建

下面例子和之前JAX-WS相同,我们仍然编写天气查询接口,它接收一个城市名称作为输入,并返回该城市的天气信息,工程目录结构如下。

|_ src/main
    |_ java
        |_ model
            |_ Weather.java
        |_ service
            |_ CityService.java
            |_ impl
                |_ CityServiceImpl.java
    |_ webapp
        |_ WEB-INF
            |_ web.xml

配置web.xml

wildfly14中,使用内置的CXF实现JAX-RS需要一些额外配置,我们需要在web.xml中配置javax.ws.rs.core.Application的映射。

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="false">

    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/ws/*</url-pattern>
    </servlet-mapping>

</web-app>

配置中,<url-pattern>/ws/*</url-pattern>表示所有以/ws开头的请求都会被映射到javax.ws.rs.core.Application,交给CXF的JAX-RS模块执行。

代码实现

对于实体类Weather,由于这里我们需要使用XML格式输出,因此额外标注了一个JAXB注解@XmlRootElement,这个注解是必须的(对于XML输出方式来说),如果不指定CXF会因为找不到该如何序列化这个类的对象而报错,对于其它字段我们也可以使用JAXB注解进行修饰。

package com.gacfox.demo.demows.model;

import lombok.Data;

import javax.xml.bind.annotation.XmlRootElement;
import java.util.Date;

@Data
@XmlRootElement(name = "weather")
public class Weather {
    private String city;
    private String weather;
    private int temperature;
    private Date date;
}

下面代码我们定义了天气查询接口的接口类和实现类。

package com.gacfox.demo.demows.service;

import com.gacfox.demo.demows.model.Weather;

import javax.jws.WebService;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@WebService
@Path("/api/cities")
public interface CityService {
    @GET
    @Path("/{city}/weather")
    @Produces(MediaType.APPLICATION_XML)
    Weather queryCityWeather(@PathParam("city") String city);
}

接口类中,同时使用@WebService@Path会被识别为定义了JAX-RS服务,类级别的@Path中我们指定了基础路径/api/cities,而具体的方法中则对应于一个具体的Restful风格的接口,此时访问/api/cities/{city}/weather就会调用queryCityWeather方法。

除此之外,我们还在方法上指定了@GET标识该接口使用GET请求,@Produces标识了该接口的输出格式为XML。

package com.gacfox.demo.demows.service.impl;

import com.gacfox.demo.demows.model.Weather;
import com.gacfox.demo.demows.service.CityService;

import java.util.Date;


public class CityServiceImpl implements CityService {
    @Override
    public Weather queryCityWeather(String city) {
        // ... 具体业务逻辑
        return weather;
    }
}

实现类没有什么特殊之处,上述代码都编写完成后,此时我们将工程部署到Wildfly即可看到效果。如果我们的ContextPath的demows,我们访问例如:http://localhost:8080/demows/ws/api/cities/DemoCity/weather,即可看到输出的XML报文。

至于为什么我们这里定义了CityService,而不是起名为WeatherService呢?因为从嵌套逻辑上说,“天气”是一个“城市”的子级资源,按照RESTful设计它的资源定位符应该是类似/cities/{城市ID}/weather,因此将其编写在CityService是更加合适的。

处理POST请求

HTTP中GET和DELETE没要请求体,但POST和PUT需要请求体来映射到Java实体类的,这里演示下如何实现POST接口。下面例子我们实现了一个接口,用于添加城市。

package com.gacfox.demo.demows.service;

import com.gacfox.demo.demows.model.City;

import javax.jws.WebService;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@WebService
@Path("/api/cities")
public interface CityService {

    @POST
    @Path("")
    @Consumes(MediaType.APPLICATION_XML)
    void addCity(City city);
}

和之前一样,实体类需要使用JAXB注解来标识,此外这里由于我们传入了一个XML格式数据,因此需要标注@Consumes(MediaType.APPLICATION_XML)。我们可以用HTTP调试工具发起POST请求进行测试。

请求路径(假设工程的ContextPath为demows):

POST /demows/ws/api/cities

请求体:

<city>
    <cityCode>001</cityCode>
    <cityName>DemoCity</cityName>
</city>

使用JSON传递数据

JSON(JavaScript Object Notation)是一种轻量级的新型数据交换格式,它易于人们阅读和编写,并易于机器解析和生成。JSON格式的灵感来源于JavaScript对象字面量语法,但它也能够被很多其他编程语言支持,JSON因其轻量级、简洁性、易用性和广泛的支持而成为了一种近年来逐渐流行的数据交换格式,在部分场景可以作为XML的替代。JAX-RS标准和CXF框架也对JSON进行了支持。

对于JSON数据,我们将之前的@Produces@Consumes中的类型改为MediaType.APPLICATION_JSON即可。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap