Java RESTful Web Service实战(第2版) 1.6 快速了解Java REST服务

    xiaoxiao2024-07-26  21

    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将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。

     

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

    上面提到的四个示例在下面的“阅读指南”中给出了源代码目录和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服务类型三所对应的示例,即例3的源代码地址如下。

    https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.4.myrest-servlet3-application。

    https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.5.myrest-servlet2-rc。

    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服务类型四所对应的示例,即例4的源代码地址如下。

    https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.6.myrest-servlet2-application。

    https://github.com/feuyeux/jax-rs2-guide-II/tree/master/1.6.7.myrest-servlet3-application。

    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.3 2013-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自动生成的功能。

    最新回复(0)