1.6 快速了解Java REST服务

1.6.1 REST工程类型

在REST服务中,资源类是接收REST请求并完成响应的核心类,而资源类是由REST服务的“提供者”来调度的。这一概念类似其他框架中自定义的Servlet类,该类会将请求分派给指定的Controller/Action类来处理。本节将讲述REST中的这个提供者,即JAX-RS2中定义的Application以及Servlet。

Application类在JAX-RS2(JSR339,详见参考资料)标准中定义为javax.ws.rs.core.Application,相当于JAX-RS2服务的入口。如果REST服务没有自定义Application的子类,容器将默认生成一个javax.ws.rs.core.Application类。

本节根据JAX-RS2规范第2章中对REST服务场景的定义,将REST服务分为四种类型,如图1-1所示。

图1-1 REST工程类型示意图

图1-1将JAX-RS2标准中对REST服务的类型图形化,依据不同的条件分为了四种类型。

❏类型一:当服务中没有Application子类时,容器会查找Servlet的子类来做入口,如果Servlet的子类也不存在,则REST服务类型为类型一,对应图1-1中的例1。

❏类型二:当服务中没有Application子类时,存在Servlet的子类,则REST服务类型为类型二,对应图1-1中的例2。

❏类型三:服务中定义了Application的子类,而且这个Application的子类使用了@ApplicationPath注解,则REST服务类型为类型三,对应图1-1中的例3。

❏类型四:如果服务中定义了Application的子类,但是这个Application的子类没有使用@ApplicationPath注解,则REST服务类型为类型四,对应图1-1中的例4。

上面提到的四个示例在下面的“阅读指南”中给出了源代码目录和Github下载地址,需要读者仔细体会示例之间的差异,以更好地理解和使用不同类型的REST服务。

1.REST服务类型一

类型一对应的是图1-1中的例1,相应的逻辑是服务中同时不存在Application的子类和Servlet子类。在JAX-RS2(JSR339)中定义这种情况下应作如下处理:为REST服务动态生成一个名称为javax.ws.rs.core.Application的Servlet实例,并自动探测匹配资源。与此同时,需要根据Servlet的不同版本,在web.xml定义REST请求处理的Servlet为这个动态生成的Servlet,并定义该Servlet对资源路径的匹配。在没有Application的子类存在的情况下,在web.xml中定义Servlet是必不可少的配置。

阅读指南

REST服务类型一所对应的示例,即例1的源代码地址如下。

https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.1.myrest-servlet2-webxml

https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.2.myrest-servlet3-webxml

请使用mvn jetty:run启动服务,使用curl http://localhost:8080/webapi/myresource测试服务。

REST服务类型一的示例包含两个小项目,分别对应Servlet2和Servlet3两种容器依赖场景。我们只须关注Maven配置文件(pom.xml)和Web服务配置文件(web.xml)的区别即可理解无Application子类情况下,如何实现基于Servlet2和Servlet3容器内的服务。

Servlet3的最简配置示例代码如下。

    <? xml version="1.0" encoding="UTF-8"? >
    <web-app  version="3.0"  xmlns="http://java.sun.com/xml/ns/Java  EE"
    xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance  xsi:schemaLocation="http://
    java.sun.com/xml/ns/Java EE http://java.sun.com/xml/ns/Java EE/web-app_3_0.xsd">
        <servlet>
            <servlet-name>javax.ws.rs.core.Application</servlet-name>
        </servlet>
        <servlet-mapping>
            <servlet-name>javax.ws.rs.core.Application</servlet-name>
            <url-pattern>/webapi/*</url-pattern>
        </servlet-mapping>
    </web-app>

相对于Servlet2而言,在Servlet3中,servlet的定义可以只包含servlet-name。再次强调,Jersey的Servlet3的容器支持包是jersey-container-servlet。Servlet2的最简配置示例代码如下。

    <? xml version="1.0" encoding="UTF-8"? >
    <web-app  version="2.5"  xmlns="http://java.sun.com/xml/ns/Java  EE"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://
    java.sun.com/xml/ns/Java EE http://java.sun.com/xml/ns/Java EE/web-app_2_5.xsd">
        <servlet>
            <servlet-name>Jersey Web Application</servlet-name>
            <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
            <init-param>
                <param-name>jersey.config.server.provider.packages</param-name>
                <param-value>com.example</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>Jersey Web Application</servlet-name>
            <url-pattern>/webapi/*</url-pattern>
        </servlet-mapping>
    </web-app>

servlet的定义包含servlet-name和servlet-class,其初始化参数需要显示给出要加载的资源类所在的包名,可以看出Servlet2的支持包jersey-container-servlet-core不具备自动扫描资源类的功能。

2.REST服务类型二

类型二对应的是图1-1中的例2,相应的逻辑是不存在Application的子类但存在Servlet的子类。

阅读指南

REST服务类型二所对应的示例,即例2的源代码地址如下。

https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.3.myrest-subservlet

本例定义了Servlet子类AirServlet,该类继承自org.glassfish.jersey.servlet.ServletContainer类,这是Jersey2中Servlet的基类,继承自HttpServlet。AirServlet类的代码示例如下。

    @WebServlet(
    initParams = @WebInitParam(
    name = "jersey.config.server.provider.packages", value = "com.example"),
    urlPatterns = "/webapi/*",
    loadOnStartup = 1)
    public class AirServlet extends ServletContainer {

AirServlet使用了WebServlet注解来配置Servlet参数。包括初始化参数initParams中定义扫描的资源类所在的包名:com.example, Servlet匹配的资源路径:urlPatterns="/webapi/*"和启动时的加载标识:loadOnStartup=1。

例2是基于Servlet3容器的REST服务,使用了WebServlet注解和无web.xml等Servlet3引入而Servlet2没有的功能。在自定义Servlet3.x子类的场景下,web.xml可以省略,但需要修改Maven的maven-war-plugin插件的配置,添加failOnMissingWebXml为false,这样编译时才不会报错。Maven配置文件中相关信息如下所示。

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.3</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
    </plugin>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

3.REST服务类型三

类型三对应的是图1-1中的例3,相应的逻辑是存在Application的子类并且定义了@ApplicationPath注解。

REST服务类型三的示例包含两个小项目。其中,servlet2-rc项目基于Servlet2, AirResourceConfig类继承自Application的子类ResourceConfig类;servlet3-application项目基于Servlet3, AirApplication类继承自Application类。基于Servlet2的REST服务需要定义web.xml(但内容可以是“空的”,即只有web-app的基本定义),基于Servlet3的REST服务可以省略此文件。AirApplication类代码示例如下。

    @ApplicationPath("/webapi/*")
    public class AirApplication extends Application {
        @Override
        public Set<Class<? >> getClasses() {
            final Set<Class<? >> classes = new HashSet<Class<? >>();
            classes.add(MyResource.class);
            return classes;
        }
    }

AirApplication类覆盖了getClasses()方法,注册了资源类MyResource,这样在服务启动后,MyResource类提供的资源路径将被映射到内存,以便请求处理时匹配相关的资源类和方法。AirResourceConfig类代码示例如下。

    @ApplicationPath("/webapi/\*")
    public class AirResourceConfig extends ResourceConfig {
        public AirResourceConfig() {
            packages("com.example");
        }
    }

AirResourceConfig类在构造子中提供了扫描包的全名,这样在服务启动后,com.example包内资源类所提供的资源路径将被映射到内存。

4.REST服务类型四

类型四对应的是图1-1中的例4,相应的逻辑是一有二无:一有是存在Application的子类;二无是不存在Servlet子类、不存在或者不允许使用注解@ApplicationPath。

REST服务类型四的示例包含两个小项目,演示了基于Servlet2和Servlet3两个版本的REST服务,其差异仅此而已,关于差异性配置前面的例子已经讲过,不再冗述。如下以servlet3-application为例说明。AirApplication类是Application的子类,代码示例如下。

    public class AirApplication extends Application {
        @Override
        public Set<Class<? >> getClasses() {
            final Set<Class<? >> classes = new HashSet<Class<? >>();
            classes.add(MyResource.class);
            return classes;
        }
    }

代码和类型三的示例相仿,但是该类没有定义@ApplicationPath注解,因此我们需要在web.xml中配置Servlet和映射资源路径,代码示例如下。

    <? xml version="1.0" encoding="UTF-8"? >
    <web-app  xmlns="http://java.sun.com/xml/ns/Java  EE"  xmlns:xsi="http://www.
    w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/
    Java EE http://java.sun.com/xml/ns/Java EE/web-app_3_0.xsd"version="3.0">
        <servlet>
            <servlet-name>com.example.AirApplication</servlet-name>
        </servlet>
        <servlet-mapping>
            <servlet-name>com.example.AirApplication</servlet-name>
            <url-pattern>/webapi/\*</url-pattern>
        </servlet-mapping>
    </web-app>

在servlet-name中使用自定义的Application子类com.example.AirApplication的全名作为Servlet名称,并在url-pattern中映射资源路径。

1.6.2 REST应用描述

在明白如何创建和部署各种类型的REST服务后,我们来了解一下部署好的REST服务中一个特殊的成员,REST应用的描述:以XML格式展示当前REST环境中所提供的REST服务接口。这种XML格式的描述就是WADL(Web Application Description Language, Web应用描述语言)。

WADL是用来描述基于HTTP协议的REST式Web服务部署情况的。它采用XML格式,支持多种数据类型的描述。WADL由Sun公司提出,尚未成为W3C或者OASIS的标准,JAX-RS标准中并没有关于WADL的定义和说明。Jersey作为JAX-RS2的参考实现默认支持服务的WADL。通过浏览器访问“服务根路径/application.wadl”即可打开该服务的WADL内容。相对于REST服务,WSDL更为人们所熟知,WSDL是RPC风格的基于SOAP的Web服务的描述语言。两者缩写类似而且都使用XML格式,此外共性不多。

1.应用的描述

以REST服务类型四的示例项目1.6.7.myrest-servlet3-application为例,该应用的WADL路径如下:http://localhost:8080/myrest-servlet3-application/webapi/application.wadl

通过浏览器访问该路径,可以一览WADL的schema结构。WADL的最外层标签是application,代表应用。然后自上而下分别是doc、grammars和resources。resources是应用提供的资源集合,里面至少包含application.wadl,以及应用中包含的资源描述,比如本例的资源信息描述在资源路径myresource之内,如下所示。

    <? xml version="1.0" encoding="UTF-8" standalone="yes"? >
    <application>
        <doc jersey:generatedBy="Jersey: 2.32013-09-20 13:59:07"/>
        <grammars/>
    <resources
    base="http://localhost:8080/myrest-servlet3-application/webapi/">
            <resource path="myresource">...</resource>
            <resource path="application.wadl">...</resource>
        </resources>
    </application>

2.资源的描述

可以展开myresource来查看具体某个方法的WADL,也可以通过发送一条请求并定义请求头信息来获取。以cURL(详见1.8节)为例,命令如下。

    curl -X OPTIONS -H "Allow: application/vnd.sun.wadl+xml" -v   http://localhost:
    8080/myrest-servlet3-application/webapi/myresource

myrest-servlet3-application提供的资源接口,对照服务器返回的XML,可以更清晰地理解WADL的内容。其WADL内容如下。

    <resource path="myresource">
        <method id="getIt" name="GET">
            <response>
                <representation mediaType="text/plain"/>
            </response>
        </method>
        <method id="apply" name="OPTIONS">
            <request>
                <representation mediaType="*/*"/>
            </request>
            <response>
                <representation mediaType="application/vnd.sun.wadl+xml"/>
            </response>
        </method>
        <method id="apply" name="OPTIONS">
            <request>
                <representation mediaType="*/*"/>
            </request>
            <response>
                <representation mediaType="text/plain"/>
            </response>
        </method>
        <method id="apply" name="OPTIONS">
            <request>
                <representation mediaType="*/*"/>
            </request>
            <response>
                <representation mediaType="*/*"/>
            </response>
        </method>
    </resource>

在这段代码中,公布了四个方法。其中,getIt方法代码如下。其他三个OPTIONS请求方法是Jersey默认实现的,用以描述getiIt方法,分别返回text/plain类型,*/*类型和application/vnd.sun.wadl+xml类型。

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getIt() {
    return "Got it! ";
    }

getIt方法定义为GET请求方法,@Produces中定义的媒体类型是MediaType.TEXT_PLAIN,即响应过程中生产的数据,其表述性状态以text/plain媒体类型转移。

3.WADL的配置

上述OPTIONS请求方法的实现是Jersey默认支持的,如果读者不希望在REST服务中让Jersey自动生成,可以通过配置jersey.config.server.wadl.disableWadl=true来实现。代码示例如下。

    public class AirApplication extends ResourceConfig {
        public AirApplication() {
            property(ServerProperties.WADL_FEATURE_DISABLE, true);
            packages("com.example.resource");
        }
    }

在构造函数中,我们通过定义ServerProperties.WADL_FEATURE_DISABLE属性为true以实现去除WADL自动生成的功能。或者,可以通过修改Web配置文件中servlet启动参数来实现,代码示例如下。

    <servlet>
        <servlet-name>com.example.AirApplication</servlet-name>
        <init-param>
            <param-name>jersey.config.server.wadl.disableWadl</param-name>
            <param-value>true</param-value>
        </init-param>
    </servlet>

配置文件中定义了启动参数jersey.config.server.wadl.disableWadl,其值定义为true,以实现去除WADL自动生成的功能。