利用 WebSphere Application Server 构建 REST 服务,第 1 部分: 简单的 RESTful 实现

本文介绍了 IBM® WebSphere® Application Server V8 及其后续版本中内置的 REST 特性,展示了如何创建简单的 REST 服务,以及如何利用 WebSphere Application Server 内置的 RESTful 功能,例如对象反序列化、定制序列化和异常处理。 本文来自于 IBM WebSphere Developer Technical Journal 中文版

Rick Gunderson, IT 专家, IBM

Rick Gunderson 是 IBM 软件集团的一位 IT 专家,专攻应用程序集成和中间件。他在 IBM 任职已有 15 年之久,在此期间,Rick 曾经参与过多个行业中客户的基于 Web 的项目,这些行业包括医疗保健业、电信业、农业和能源业。



2013 年 6 月 18 日

简介

JAX-RS 是一种 Java™ API for RESTful Web 服务。JAX-RS API 是一组接口和 Java 注释集合,用于简化服务器端 REST 应用程序的开发。JAX-RS 使开发人员能够通过注释类来指定 RESTful Web 服务行为,从而响应特定 HTTP 方法(例如 GET 或 POST)和 URI 模式。JAX-RS 是 Java EE 6 的正式特性。

最新版本的 IBM WebSphere Application Server 提供了 JAX-RS 支持。WebSphere Application Server V8.5 内置了 JAX-RS 支持,无需进行额外安装。对于 WebSphere Application Server V8.0,JAX-RS 特性是在须单独安装的可选产品扩展 Feature Pack for Web 2.0 and Mobile(版本 1.1.0)中提供的。

无论您使用上述哪种配置,WebSphere Application Server 都提供了这些针对 JAX-RS 的特性:

  • 基于 Apache Wink 1.1 的 JAX-RS 1.1 服务器运行时
  • 由 Jackson JSON 处理程序提供的内置 POJO 实体提供程序支持
  • 内置的 IBM JSON4J 实体提供程序支持。

本文描述了一个示例应用程序场景的设计与实现,帮助您了解如何将 JAX-RS 应用于 WebSphere Application Server 应用程序。本文包含该设计中表现为一组 Eclipse 项目的部分实现,包括一个 Web 模块、EJB 模块和一个 Derby 数据库。阅读本文并研究示例应用程序之后,您应能够创建带有响应提供程序的 JAX-RS,允许您量身定制应用程序提供的响应。


简单的 RESTful 应用程序

本文中将使用的示例是一个简单的学生登记应用程序 (SRA)。图 1 展示了该应用程序的组件及其高级交互情况。

图 1. 学生登记应用程序示例
学生登记应用程序示例

本文附带的下载材料 包含从 Eclipse IDE 中导出的三个项目:EAR、EJB 和 Web 项目。如果您希望在阅读本文的同时使用这些文件进行实验,则需要编辑 EJB 项目中的 persistence.xml 文件,并将 openjpa.ConnectionURL 属性的值调整为指向您的环境中初次启动应用程序时可以创建 Student 数据库的位置。

这个示例 SRA 应用程序中提供的学生登记管理功能允许您完成以下任务:

  • 登记新学生
  • 编辑已经登记的学生信息
  • 列出已经登记的学生
  • 删除学生登记。

SRA 中包含提供 RESTful 服务的 Web 模块、提供基本创建-读取-更新-删除 (CRUD) 数据库逻辑的 EJB 模块和存储学生记录的数据库。

JAX-RS 运行时使用图 1 展示的应用程序配置类确定应用程序中的类是资源类还是提供程序类。注册资源类和提供程序类的 StudentRegistrationApplication 类如清单 1 所示。

清单 1. StudentRegistrationApplication 类
public class StudentRegistrationApplication extends Application {
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();

        // Resources
        classes.add( com.dw.demo.rest.resources.StudentResource.class );

        // Providers

        return classes;
    }
}

StudentRegistrationApplication 类是 JAX-RS 提供的 Application 类的子类,提供了 SRA 中的 REST 资源和提供程序的列表。

清单 2 给出了该应用程序的 web.xml 文件。

清单 2. 学生登记应用程序的 web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app
    xmlns="http://java.sun.com/xml/ns/javaee"
   
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <display-name>demo.war</display-name>
    <servlet>
        <servlet-name>com.dw.demo.rest.StudentRegistrationApplication</servlet-name>
    </servlet>
    <servlet-mapping>
        <servlet-name>com.dw.demo.rest.StudentRegistrationApplication</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
</web-app>

WebSphere Application Server 提供了一个支持 JAX-RS 的 servlet 容器,因此可将 StudentRegistrationApplication 类作为 servlet 名称 提供。web.xml 文件中的配置将 “/rest/*” URL 模式映射到 StudentRegistrationApplication 类,WebSphere Application Server servlet 容器自动连接圆点,保证对 “/rest/*” URL 模式的引用调用恰当的资源类。

StudentRegistrationApplication 类声明了一个资源,即 StudentResource 类。StudentResource 类表示已经注册的学生的完整集合。清单 3 给出了该类的初始实现。

清单 3. StudentResource 类
@Path( "/students" )
@Stateless
public class StudentResource {

    @EJB
    IStudent studentService;

    @POST
    @Consumes( MediaType.APPLICATION_JSON )
    public Response create( @Context UriInfo uriInfo, Student student ) {

        Student createdStudent = studentService.createStudent( student );
        URI uri = uriInfo.getBaseUriBuilder().path( StudentResource.class )
            .path( createdStudent.getId() ).build();

        return Response.created( uri ).build();
    }

    @DELETE
    @Path( "/{id}")
    public Response delete( @PathParam( "id" ) String id ) {
        studentService.delete( id );
        return Response.status( Status.NO_CONTENT ).build();
    }

    @GET
    @Produces( MediaType.APPLICATION_JSON )
    public Response getAll() {
        List<Student> students = studentService.getAll();
        return Response.ok( students ).build();
    }

    @GET
    @Path( "/{id}" )
    @Produces( MediaType.APPLICATION_JSON )
    public Response getById( @PathParam( "id" ) String id ) {
        Student student = studentService.findById( id );
        if ( student != null ) {
            return Response.ok( student ).build();
        } else {
            return Response.status( Status.NOT_FOUND ).build();
        }
    }

    @PUT
    @Consumes( MediaType.APPLICATION_JSON )
    public Response update( @Context UriInfo uriInfo, Student student ) {
        Student updatedStudent = studentService.update( student );
        return Response.ok( updatedStudent ).build();
    }
}

@Path( "/students" )” 注释表示对 /demo/rest/students 的请求将由 Student 资源类处理。

该资源类定义了五种方法:

  • create:使用 HTTP POST 创建一条新学生记录。
  • delete:使用 HTTP DELETE 删除特定的一条学生记录。
  • getAll:使用 HTTP GET 获取所有学生。
  • getById:使用 HTTP GET 按照学生 ID 获取特定一名学生
  • update:使用 HTTP PUT 更新一条学生记录。

您必然不希望意外地删除所有已经注册的学生,因此我们特意没有为学生集合实现集合级的删除方法。

StudentResource 类的实现也注释为根据方法使用或生成 JSON 内容;就本文的目的而言,支持的方法类型仅限于 “application/json”。

StudentResource 类使用 EJB 执行必要的数据库操作;类级别的 @Stateless 注释告诉 WebSphere Application Server 将 StudentResource 类作为 EJB 处理,因此该类所依赖的 EJB(本例中为 StudentService EJB)将自动注入。


在 REST 服务中支持 Java 对象

清单 3 展示的方法可接受或返回 Student 类的实例(或者实例列表)。Student 类由 POJO 声明,在 EJB 模块中实现。Student 类注释为 JPA 实体,如清单 4 所示。

清单 4. Student Entity 类
@Entity
@Table( name = "STUDENT" )
public class Student implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;
    private String firstName;
    private String lastName;
    private String email;
    private String studentNumber;
    private Calendar registeredOn;
    private Gender gender;

    // Getters and setters omitted for brevity
}

由于 Student 类属于 POJO 类,带有定义明确的 getter 和 setter 方法,因此 WebSphere Application Server JAX-RS 运行时将使用 Jackson JSON 处理程序,自动序列化和反序列化 Student 类,这个类可以是 Student 类的单一实例,也可以是 Student 类的一组实例集合。无需额外编码。Jackson JSON 处理程序,甚至能正确处理 Student 类使用的 Gender 枚举。

如果您对 /demo/rest/students 执行 GET 命令来测试应用程序,您将看到应用程序返回如清单 5 所示的 JSON 代码。

清单 5. 应用程序返回的 JSON 代码
[ {
    "id" :"0b031e9b-6933-4a10-9ffc-c7fd2c604331",
    "firstName" :"Aaron",
    "lastName" :"Aaronson",
    "gender" :"MALE",
    "email" :"aaaronson@domain.net",
    "studentNumber" :"823-934",
    "registeredOn" :1356480000000
}, …
{
    "id" :"864eb556-053a-42d7-a757-e24928fb19a3",
    "firstName" :"Pamela",
    "lastName" :"Peterson",
    "gender" :"FEMALE",
    "email" :"ppeterson@domain.net",
    "studentNumber" :"826-660",
    "registeredOn" :1364256000000
} ]

提供增强的序列化

默认情况下,Jackson JSON 处理程序会将日期对象(java.util.Datejava.util.Calendar 等)序列化为数字时间戳(从格林威治标准时间 1970 年 1 月 1 日 00:00:00 起的毫秒数)。对于这个示例应用程序,您要将 Student 实体的 registeredOn 值序列化为 ISO8601 字符串。

为了控制序列化,您要创建一个名为 JsonStudentProvider 的提供程序,如清单 6 所示。

清单 6. JsonStudentProvider
@Provider
@Produces( MediaType.APPLICATION_JSON )
public class JsonStudentProvider implements MessageBodyWriter<Object> {

    public long getSize( Object object, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType ) {
        return -1;
    }

    public boolean isWriteable( Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType ) {
        boolean isWritable = false;
        if ( List.class.isAssignableFrom( type )
            && genericType instanceof ParameterizedType ) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type[] actualTypeArgs = ( parameterizedType.getActualTypeArguments() );
            isWritable = (
                actualTypeArgs.length == 1
                && actualTypeArgs[0].equals( Student.class ) );
        } else if ( type == Student.class ) {
            isWritable = true;
        }
        return isWritable;
    }

    public void writeTo( Object object, Class<?> type, Type genericType,
        Annotation[] annotations, MediaType mediaType,
        MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream )
        throws IOException {

        // Explicitly use the Jackson ObjectMapper to write dates in ISO8601 format
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure( SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false );
        mapper.writeValue( entityStream, object );
    }
}

JsonStudentProvider 类必须使用 @Provider 注释,必须实现 JAX-RS MessageBodyWriter 接口声明的这些方法:

  • getSize:获取序列化对象的大小,如果大小未知则返回 -1。
  • isWritable:如果此方法返回 true,则 JAX-RS 框架将使用此类来序列化对象。
  • writeTo:调用此方法将所提供的对象序列化为所提供的 OutputStream 实例。

isWritable 方法测试所提供的对象是属于一个 Student 实例还是属于 Student 实例列表。如果属于 Student 实例列表,则使用 JsonStudentProvider 序列化对象。

writeTo 方法创建一个 Jackson ObjectMapper 实例,将其配置为将日期写为时间戳,而是将日期写为字符串,最终使用 ObjectMapper 实例将所提供的对象序列化为所提供的 OutputStream

创建 JsonStudentProvider 类之后,必须在 Student Registration Application 类中注册该类,如清单 7 所示。

清单 7. 更新后的 StudentRegistrationApplication 类
public class StudentRegistrationApplication extends Application {
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();

        // Resources
        classes.add( dw.demo.rest.resources.StudentResource.class );

        // Providers
        classes.add( dw.demo.rest.providers.JsonStudentProvider.class );

        return classes;
    }
}

您需要对 Student Resource 类中的 getAll 方法略作修改,确保在用户请求所有学生的列表时,正确地将类型信息传递给 JsonStudentProvider(清单 8)。

清单 8. 更新后的 Student Resource 类的 “getAll” 方法
    @GET
    @Produces( MediaType.APPLICATION_JSON )
    public Response getAll() {
        List<Student> students = studentService.get();
        // This preserves type information for the provider
        GenericEntity<List<Student>> entity =
            new GenericEntity<List<Student>>( students ) {};
        return Response.ok( entity ).build();
    }

JAX-RS GenericEntity 类允许在运行时将通用 List 接口的类型信息提供给 JsonStudentProvider。如果您对 /demo/rest/students 执行 GET 命令来再次测试应用程序,您将看到应用程序返回如清单 9 所示的 JSON 代码。

清单 9. 应用程序返回的 JSON 代码
[ {
    "id" :"0b031e9b-6933-4a10-9ffc-c7fd2c604331",
    "firstName" :"Aaron",
    "lastName" :"Aaronson",
    "gender" :"MALE",
    "email" :"aaaronson@domain.net",
    "studentNumber" :"823-934",
    "registeredOn" :"2012-12-26T00:00:00.000+0000"
}, …
{
    "id" :"864eb556-053a-42d7-a757-e24928fb19a3",
    "firstName" :"Pamela",
    "lastName" :"Peterson",
    "gender" :"FEMALE",
    "email" :"ppeterson@domain.net",
    "studentNumber" :"826-660",
    "registeredOn" :"2013-03-26T00:00:00.000+0000"
} ]

registeredOn 属性中的值现在表示为 ISO8601 格式。


更好的异常处理

如果向应用程序提供了无效的 JSON,将得到服务器错误 (HTTP 500)。无效的 JSON 可能包括:

  • 格式错误的 JSON,例如缺少属性名称/值分隔符、括号错误等。
  • JSON 包含的属性在 Student 类中无对应属性。

例如,如果针对 /demo/rest/students URL 的 POST 的请求主体为:

{ "bad":"property" }

服务器将返回如清单 10 所示的响应。

清单 10. 服务器响应
HTTP/1.1 500 Internal Server Error
X-Powered-By:Servlet/3.0
Content-Type: text/html;charset=ISO-8859-1
$WSEP:
Content-Language: en-CA
Content-Length:377
Connection:Close
Date:Tue, 02 Apr 2013 07:40:06 GMT
Server:WebSphere Application Server/8.0

Error 500: javax.servlet.ServletException: 
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: 
Unrecognized field &quot;bad&quot; &#40;Class com.dw.demo.entities.Student&#41;, 
not marked as ignorable at 
[Source: com.ibm.ws.webcontainer.srt.http.HttpInputStream@5370a0&#59; 
line:1, column:10] &#40;through reference chain: com.dw.demo.entities.Student
[&quot;bad&quot;]&#41;

之所以出现异常,是因为 Student 类没有名为 “bad” 的属性。

如果向应用程序提供了无效的 JSON,您可能更希望服务器返回带有一些诊断文字的 HTTP 400(请求错误)响应。为此,可创建一个新的提供程序类,如清单 11 所示。

清单 11. JsonProcessingExceptionMapper 类
@Provider
public class JsonProcessingExceptionMapper implements
    ExceptionMapper<JsonProcessingException> {

    @Override
    public Response toResponse( JsonProcessingException e ) {
        String message = e.getMessage();
        return Response
            .status( Status.BAD_REQUEST )
            .type( MediaType.TEXT_PLAIN )
            .entity( "The supplied JSON was not well formed:" + message )
            .build();
    }
}

JsonProcessingExceptionMapper 类实现了 JAX-RS ExceptionMapper 接口,该接口有一个方法:toResponsetoResponse 方法接受 JsonProcessingException,并创建带有纯文本内容的 HTTP 400 响应。

创建 JsonProcessingExceptionMapper 类之后,必须在 StudentRegistrationApplication 类中注册该类,如清单 12 所示。

清单 12. 更新后的 StudentRegistrationApplication 类
public class StudentRegistrationApplication extends Application {
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();

        // Resources
        classes.add( dw.demo.rest.resources.StudentResource.class );

        // Providers
        classes.add( dw.demo.rest.providers.JsonStudentProvider.class );
        classes.add( dw.demo.rest.providers.JsonProcessingExceptionMapper.class );

        return classes;
    }
}

现在,如果针对 /demo/rest/students URL 的 POST 的请求主体为: { "bad":"property" } ,那么服务器将返回如清单 13 所示的响应。

清单 13. 服务器响应
HTTP/1.1 400 Bad Request
X-Powered-By:Servlet/3.0
Content-Type: text/plain
Content-Language: en-CA
Transfer-Encoding: chunked
Connection:Close
Date:Tue, 02 Apr 2013 7:35:30 GMT
Server:WebSphere Application Server/8.0

10f
The supplied JSON was not well formed:Unrecognized field "bad" 
(Class com.dw.demo.entities.Student), not marked as ignorable
 at [Source: com.ibm.ws.webcontainer.srt.http.HttpInputStream@5370a0; 
line:1, column:10] (through reference chain: com.dw.demo.entities.Student["bad"])
0

结束语

本文介绍了如何创建 RESTful 服务来管理学生登记。该服务使用定制的序列化程序来确保 JSON 容器日期表示的学生记录采用了正确的格式;使用定制的异常映射程序,通过 HTTP 400 错误代码(而非 HTTP 500 错误代码)报告无效的输入 JSON。

第 2 部分将介绍如何将 RESTful 服务于客户端 UI 框架 Dojo 进行集成。


下载

描述名字大小
代码样例demo.zip41 KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=934385
ArticleTitle=利用 WebSphere Application Server 构建 REST 服务,第 1 部分: 简单的 RESTful 实现
publish-date=06182013