让 Smart WebService 插件支持 REST 服务

作者:无名 - 开源软件 -

本文是《轻量级 Java Web 框架架构设计》的系列博文。

前几天我们已基本实现 Smart WebService 插件,该插件可无缝集成到 Smart Framework 中,可发布基于 SOAP 的 WebService。

  • 关于 Smart WebService 插件是如何实现的,您可以阅读《初步实现 WebService 插件》这篇文章。
  • 如果您想获取 Smart WebService 插件的源码,可以访问 OSC Maven:http://git.oschina.net/huangyong/smart-plugin-ws

目前我们已经自定义了一个 @WebService 注解,直接将其配置在某个接口上,便可将该接口发布为 WebService,无需再做任何的配置。

这一切似乎都那么的简单而优雅,但又似乎缺少了一点什么?

没错!只能发布基于 SOAP 的 WebService,却不能发布基于 REST 的 WebService(以下简称“REST 服务”)。这确实有些遗憾!

本文即将揭晓如何发布并调用 REST 服务,请您继续往下阅读。

第一步:在 Maven 中添加相关依赖包

我们选择了 CXF,看来是明智的,因为它不仅仅可以提供 SOAP 支持,同时还提供了 REST 支持,而且它的功能远远不止这些。

...
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>2.7.7</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.13</version>
        </dependency>
...

注意,要使用 CXF 的 cxf-rt-frontend-jaxrs,而在 SOAP 中,我们使用的是 cxf-rt-frontend-jaxws,一个是 jaxws,另一个是 jaxrs,一个字母只差,差异却千千万。

这还要依赖一个 Jackson 的 jackson-jaxrs 包,它是干嘛的?别着急,马上您就知道了。

第二步:扩展 @WebService 注解

还记得之前我提到过,为什么要自定义一个 @WebService 注解吗?为什么不用 JDK 给我们提供的 javax.jws.WebService 呢?

其实就是为了干今天这件大事 —— 实现 REST 服务。

现将 @WebService 注解做如下扩展:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebService {

    String value() default "";

    Type type() default Type.SOAP;

    public enum Type {
        SOAP, REST
    }
}

增加了一个 type 属性,默认值是 SOAP,这里用到了 Java 枚举,方便我们定义不同类型的 WebService(其实目前主流也就这两种:SOAP 与 REST)。

第三步:封装 CXF API

还是用以前的套路,将 CXF API 做一个封装。还记得上次编写了一个 WebServiceHelper 吗?它可以发布 WebService 并获取 WebService 客户端。如果将 REST 服务的发布与调用也一并加入该类中,或许不是最好的选择,倒不如将该类重命名为 SOAPHelper,然后再提供一个 RESTHelper,这样也许更加符合设计原则中的“单一职责原则”,它们俩的职责更加清晰,也便于维护。

public class RESTHelper {

    private static final JacksonJsonProvider jsonProvider = new JacksonJsonProvider();

    // 发布 REST 服务
    public static void publishService(String wadl, Class<> resourceClass) {
        JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
        factory.setAddress(wadl);
        factory.setResourceClasses(resourceClass);
        factory.setProviders(Arrays.asList(jsonProvider));
        factory.setResourceProvider(resourceClass, new SingletonResourceProvider(BeanHelper.getBean(resourceClass)));
        factory.create();
    }

    // 创建 REST 客户端
    public static <T> T createClient(String wadl, Class< extends T> resourceClass) {
        return JAXRSClientFactory.create(wadl, resourceClass, Arrays.asList(jsonProvider));
    }
}

以上首先定义了一个 jsonProvider(JacksonJsonProvider),它是 Jackson JSON 库给我们提供的基于 JAX-RS 的序列化与反序列化工具。该对象只需加载一次即可,所以将其定义为 static 的了。

随后提供了两个 static 方法:

  • 在 Smart WebService 插件中会调用 publishService 方法来发布 REST 服务。
  • 如果您需要调用 REST 服务,可以调用 createClient 方法来创建 REST 客户端,这同样也是一个 Proxy 对象,当然这只是实现 REST Client 的方法之一。

说明:

  • JAX-RS(JSR-339)规范描述的就是 REST 服务,感兴趣的朋友可以阅读一下它的具体规范:http://download.oracle.com/otndocs/jcp/jaxrs-2_0-fr-spec/index.html
  • 在 CXF 官网上也提供了一份 JAX-RS 的使用手册,也推荐您阅读一下:http://cxf.apache.org/docs/jax-rs.html
  • CXF 还提供了一份 JAX-RS 的基础教程(比官方的要精简一些):http://cxf.apache.org/docs/jax-rs-basics.html

现在工具都准备好了,下面要做的就是调用这个它,来发布 REST 服务。

第四步:发布 REST 服务

我们需要扩展一下 WebServiceServlet,因为只有它才能发布 WebService。需要在里面增加一个逻辑判断:

  • 若 @WebService 的 type 为 Type.SOAP,则发布 SOAP 服务。
  • 若 @WebService 的 type 为 Type.REST,则发布 REST 服务。

只需做以下简单改进即可实现:

@WebServlet(urlPatterns = WebServiceConstant.SERVLET_URL, loadOnStartup = 0)
public class WebServiceServlet extends CXFNonSpringServlet {
...
    private void publishWebService() {
        // 遍历所有标注了 @WebService 注解的接口
        List<Class<>> interfaceClassList = ClassHelper.getClassListByAnnotation(WebService.class);
        if (CollectionUtil.isNotEmpty(interfaceClassList)) {
            for (Class<> interfaceClass : interfaceClassList) {
                // 获取 @WebService 注解及其相关属性
                WebService ws = interfaceClass.getAnnotation(WebService.class);
                String wsValue = ws.value();
                WebService.Type wsType = ws.type();
                // 获取 WebService 地址
                String address = getAddress(wsValue, interfaceClass);
                // 判断 WebService 类型(SOAP 或 REST)
                if (wsType == WebService.Type.SOAP) {
                    doPublishForSOAP(address, interfaceClass);
                } else if (wsType == WebService.Type.REST) {
                    doPublishForREST(address, interfaceClass);
                }
            }
        }
    }

    private void doPublishForSOAP(String wsdl, Class<> interfaceClass) {
        // 获取 WebService 实现类(找到唯一的实现类)
        Class<> implementClass = IOCHelper.findImplementClass(interfaceClass);
        // 获取实现类的实例
        Object implementInstance = BeanHelper.getBean(implementClass);
        // 发布 SOAP Service
        SOAPHelper.publishService(wsdl, interfaceClass, implementInstance);
    }

    private void doPublishForREST(String wadl, Class<> resourceClass) {
        // 发布 REST Service
        RESTHelper.publishService(wadl, resourceClass);
    }
...
}

是不是 so easy?尽管 if else 有很多人反对,但我还是觉得它够简单、够直接,并非所有情况都需要用多态来替换 if else 的,要具体情况具体分析,当然这只是我的个人的编程习惯问题了。

下面,我们不妨配置一个 REST 服务吧,看看 Smart WebService 插件能否将其成功地发布出来。

第五步:配置 REST 服务

REST 推荐我们直接面向类进行发布,而无需面向接口。其实 REST 也可以定义接口的,只不过意义不太大,我个人也是这么觉得的,而 SOAP 似乎必须要有一个接口才行,搞得跟 EJB 有一拼了。

不妨以 Smart Sample 中的 Product 为例,我们为它发布一个 REST 服务吧。

@Bean
@WebService(value = "/rest/ProductService", type = WebService.Type.REST)
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ProductService extends BaseService {

    @GET
    @Path("/products")
    public List<Product> getProductList() {
        return DataSet.selectList(Product.class, "", "id asc");
    }

    @GET
    @Path("/product/{productId}")
    public Product getProduct(@PathParam("productId") long productId) {
        return DataSet.select(Product.class, "id = ", productId);
    }

    @POST
    @Path("/product")
    @Transaction
    public boolean createProduct(Map<String, Object> productFieldMap) {
        return DataSet.insert(Product.class, productFieldMap);
    }

    @PUT
    @Path("/product/{productId}")
    @Transaction
    public boolean updateProduct(@PathParam("productId") long productId, Map<String, Object> productFieldMap) {
        return DataSet.update(Product.class, productFieldMap, "id = ", productId);
    }

    @DELETE
    @Path("/product/{productId}")
    @Transaction
    public boolean deleteProduct(@PathParam("productId") long productId) {
        return DataSet.delete(Product.class, "id = ", productId);
    }
}

首先,在类的头上我们使用了 @WebService 注解,其中定义了两个属性:

  • value 属性用于指定 WebService 的路径,其实是一个后缀而已,叫什么都无所谓,如果不指定,那么就用类的简单名称。
  • type 属性用于指定 WebService 的类型,这里指定为 REST,所以用到了枚举类型 WebService.Type.REST。

随后,需要使用 JAX-RS 规范提供的两个非常重要的注解:@Consumes 与 @Produces,前者用于序列化方法中参数,后者用于序列化方法返回值。

大家一定要明确,不管使用 SOAP 还是 REST,他们都是 WebService,都是需要做序列化与反序列化的,只不过 REST 更加轻量级一些罢了,我们可以使用 JSON 来作为对象序列化工具,还记得 RESTHelper 中的 jsonProvider 的吗?它就是干这个活的。所以我们在这里使用了 JAX-RS 规范的 javax.ws.rs.core.MediaType 常量类来指定 JSON 类型,实际上就是 application/json。

当然,也可以使用 XML 作为对象序列化工具,但是我个人更加倾向于 JSON,因为它更加简洁,更加轻量级,也是现在的主流。不相信的话,您可以看看许多互联网公司(比如:淘宝、百度、新浪等)开放的 Open API,多半都是基于 JSON 的,其实它们本质上就是 REST 服务,只不过加上了一些权限控制机制,比如使用了 OAuth 规范。

这里的 ProductService 其实与普通的 Service 没多大区别,也可以使用事务控制(可以在需要事务控制的方法上使用 @Transaction 注解),只不过可以对外发布 WebService 罢了。初看一下该类中的方法,是不是与 Smart Action 或 Spring MVC Controller 有异曲同工之妙呢?这就是 JAX-RS 规范教我们如何发布 REST 服务的方法。

我们这里展现了 REST 中常用的四种动作:GET、POST、PUT、DELETE,他们可以分别对应 CRUD 操作,而且还可以简化 URL 的表现形式,这似乎太妙了。

有些朋友问我:有 GET 与 POST 不就够了吗?为何还要有 PUT、DELETE 呢?原因如下:

1. 语义更加清晰

这四个动词分别对应我们的 CRUD 操作,可以这样理解:

  • GET -- Read
  • POST -- Create
  • PUT -- Update
  • DELETE -- Delete

看到了 URL 就知道是什么类型的操作,这样不是更清晰了吗?

2. 简化 URL 表达方式

同一个 URL,使用不同的动词,可表达不同的语义,比如:

  • GET /product/1 -- 获取 id 为 1 的 Product
  • PUT /product/1 -- 更新 id 为 1 的 Product(可在 Request Body 中放入具体更新的字段)
  • DELETE /product/1 -- 删除 id 为 1 的 Product

这样的 URL 是否优雅呢?

发布 REST 服务不再是我们同年的梦想了,而且 Smart 还可以同时发布 SOAP 与 REST 这两种 WebService,启动 Tomcat 后将自动发布。

第六步:启动 Tomcat

可通过 CXF 提供的 WebService 控制台查看已发布的 WebService,只需输入以下地址:

http://localhost:8080/smart-sample/ws


这里有一个 WADL,全称是 Web Application Description Language(Web 应用描述语言),REST 就用 WADL 来描述自己的。

以下两个资源方便您了解一下 WADL 究竟是什么?

  • WADL 维基百科:http://en.wikipedia.org/wiki/Web_Application_Description_Language
  • WADL 规范:https://wadl.java.net/
点击 WADL 链接,可以查看 WADL 文档,它与 WSDL 类似,只不过它是用于描述 REST 服务的。

看到了这个 WADL 连接,也就证明 REST 服务发布成功了,我们可以随时通过 REST 客户端进行调用。

最后一步:调用 REST 服务

REST 有一个特性确实比 SOAP 要好很多,那就是便于测试。我们可以使用浏览器,或 REST 客户端软件,或使用 Chrome、Firefox 的相关 REST 客户端插件,这些都可以让我们轻松调用 REST 服务。我们不妨使用浏览器来调用一下 REST 服务吧。

在浏览器地址栏中输入:http://localhost:8080/smart-sample/ws/rest/ProductService/product/1

让 Smart WebService 插件支持 REST 服务

是不是很爽呢?但使用浏览器我们只能发送 GET 请求,其它类型的请求,我们还是通过客户端软件来调用比较好。

那么,如何在 Java 中来调用 REST 服务呢?

调用 REST 服务,必须知道 WADL 地址,这就像调用 SOAP 服务,必须要知道 WSDL 地址一样。我们首先来一个简单的调用吧:

public class ProductServiceRESTTest {

    private String wadl = "http://localhost:8080/smart-sample/ws/rest/ProductService";
    private ProductService productService = RESTHelper.createClient(wadl, ProductService.class);

    @Test
    public void getProductTest() {
        long productId = 1;
        Product product = productService.getProduct(productId);
        Assert.assertNotNull(product);
    }
}

这是一个 JUnit 单元测试类,我们通过 WADL 并使用 RESTHelper 来创建 REST 客户端(代理),直接通过这个代理对象来调用目标方法。其实 CXF 底层也使用了 CGLib 作为动态代理工具,看来这个工具的使用范围还真广,因为它确实好用!

调用结果如我们所愿,一切正常。但是这似乎太简单,我们要不再来一个更复杂一点的调用吧:

...
    @Test
    public void createProductTest() {
        Map<String, Object> productFieldMap = new HashMap<String, Object>();
        productFieldMap.put("productTypeId", 1);
        productFieldMap.put("productName", "1");
        productFieldMap.put("productCode", "1");
        productFieldMap.put("price", 1);
        productFieldMap.put("description", "1");
        boolean result = productService.createProduct(productFieldMap);
        Assert.assertTrue(result);
    }
...

调用 REST 服务并传递一个 Map 对象,其结果也是正确的。看来 JSON 对象序列化起效果了,如果您不使用 jsonProvider 肯定会报错,告诉您无法序列化 Map 对象。这恰恰是 SOAP 服务的硬伤!

想让 SOAP 来序列化 Map 对象,我们恐怕不会这样简单了。那么,如何通过 SOAP 来实现 Map 对象的序列化呢?下回分解,敬请期待!

这篇内容就是由IT人知识库 小编为各位整理 原文链接:http://www.itpeo.net/15310/3488234.html





rfedfre

初步实现 I18N 插件

本文是《轻量级 Java Web 框架架构设计》的系列博文。 在 JSTL、Struts、Sp... ...

loginregister0721

1  <form action="update" th:object="${userBean}" ... ...

05_7_14的小结

1、${userBean.userId}   表示取出userBean的userId2、@Au... ...

rfedfre

清关、转运、运费到付、门到门服务!

YSE-CITYLINK国际快递专线 公司秉承以客户需求为核心,坚持“质量到位、服务一流”的经营理念,在商务服... ...

rfedfre

深度探索Linux操作系统:系统构建和原理解析

深度探索Linux操作系统:系统构建和原理解析       本书是探... ...

rfedfre

Linux软件管理平台设计与实现

Linux软件管理平台设计与实现       本书不仅详细讲解 了 ... ...

三维渲染服务器轻松搞定渲染项目

对渲染项目来说,三维渲染服务器无疑是力求的捷径之一。有时我们花费再多的时间守候也不能节省渲染时间,付出越多时间收... ...

com.yammer.metrics.logback.InstrumentedAppender

Sonatype Nexus 是个MAVEN仓库管理工具。提供2种包,一种是war,一种是.zip或者.gz。 ... ...

APP体验:滴答清单

清单类的APP越来越重要了,成为工作中重要的一部分。用过很多类似产品,不过都不是很理想!不过,最终我还是选择了一款... ...

读书《程序员思维修炼》

今天凌晨,买了两本书,其中一本是《程序员思维修炼》。 为什么要买这本书?其实是感觉思维有短板,而恰好看到这么一... ...

python 二

python 流程控制 if else 语句 (支持嵌套) 语句结构: if 条件: &... ...

python 对图片的处理

下载2.7.3版本的python 和 图像处理工具包 wget http://python.org/ft... ...

zencart模板

我们是谁?      我们是对电子商务,zencart,Oscomme... ...

The working copy "xxx" has uncommitted changes.

    进到它说提示的目录中,使用git reset --hard命... ...

rfedfre

1.变量和常量

1 标识符 标识符就是用于给 Java 程序中变量、类、方法等命名的符号。 1>.  ... ...

rfedfre

高仿QQ空间-进阶篇

更新说明: 一 增加了照片的墙的功能,可以让你的照片可以流动哦!不信,你去试试就知道啦,O(∩_∩)O哈哈哈~... ...

rfedfre

随手写了个android应用

最近比较闲,就边学边写,做了个android下的 dota 攻略型应用。 目前还没发布到市场上,也没有加广告,... ...

Unix crontab命令 添加定时任务

Unix crontab命令 添加定时任务 使用crontab命令要求root身份1. cr... ...

linux(ubuntu)下安装nginx 笔记

这两天突然想在ubuntu下 安装nginx模拟真实环境,第一次安装还真是一个充满奇异的旅程,走了很多歪路但也从中... ...

推荐给每个找工作的IT毕业生--打鸡血书

本文忘了在哪里下载的了,转过来与各位共享 一、 求职历程总结 2007年1月10日,随着在三方协... ...