文档章节

[译]用模版技术转换Web服务输出结果

半包咖啡豆
 半包咖啡豆
发布于 2016/09/06 23:02
字数 2061
阅读 132
收藏 1

        要实现通过HTTP远程访问应用程序,Web服务是一种途径。它们允许各种异构的分布式客户端访问服务器端的功能,更是经常被用于为移动应用提供后端API支持。不过它们一般基于独立的技术栈实现,与web前端是分开的,所以需要额外的开发和维护成本。

        模版技术实现了数据和呈现的分离,允许数据结构最终的输出格式独立于数据本身,划清了各自的职责界限。在MVC模式里,模版对应视图,数据结构对应模型。Web服务在这里是作为控制器,它接收来自调用者的输入,产生模型数据,然后应用模版生成最终的结果。

        这篇文章将描述如何使用模版把Web服务输出结果转换成HTML,其中模版使用CTemplate(或者Mustache)编写,Web服务使用HTTP-RPC实现。这样可以只用一个代码库就可以为web和移动客户端提供服务,极大降低了开发成本。

 

HTTP-RPC

        HTTP-RPC是一个开源框架,目的是为了简化REST应用的开发。它允许开发者在保留REST无状态和统一资源访问基本准则的前提下,以一种便利的RPC方式访问基于HTTP的Web服务。更多关于HTTP-RPC的信息,可以在我之前的那篇文章看到。

        通过把HTTP动词GET或POST应用到目标资源上,完成对HTTP-RPC服务的访问。目标资源用能够代表它们名字的路径来表示,而且一般是名词,比如/calendar或/contacts。

        参数跟HTML表单一样,出现在查询字符串或请求体里。服务可能产生任意类型的结果内容,不过一般以JSON格式返回。有些不返回结果的操作也是被允许的。

        举个例子,下面的请求将获得两数的相加之和,这两个数分别是在查询字符串里指定的a和b:

GET /math/sum?a=2&b=4

        这个服务将在响应里返回6。

 

服务示例

        HTTP-RPC服务有一个抽象基类WebService,通过往具体实现类里添加公共方法来实现服务行为。@RPC注解用来标识一个方法是可以被远程访问的,它把一个HTTP动词和一个资源关联到一个方法上。当服务发布的时候,所有被注解过的公共方法都可以被远程调用。

        举个例子,下面的这个类可以用来实现之前例子里提到的服务:

public class MathService extends WebService { 
    @RPC(method="GET", path="sum") 
    public double getSum(double a, double b) { 
        return a + b; 
    } 
}

        再举个例子,下面这个方法会计算一组统计数字的和:

@RPC(method="GET", path="statistics") 
public Map<String, ?> getStatistics(List<Double> values) { 
    int count = values.size(); 
    double sum = 0; 
    for (double value : values) { 
        sum += value; 
    } 
    double average = sum / count; 
    return mapOf( entry("count", count), entry("sum", sum), entry("average", average) ); 
}

    通过GET方法访问下面的URL,上面的方法就会被调用,参数是一个包含1,3,5的列表:

/math/statistics?values=1&values=3&values=5

        返回结果如下:

{ "count": 3, "sum": 9.0, "average": 3.0 }

 

CTemplate

        HTTP-RPC服务生成的数据一般以JSON格式返回,不过如果能以其它不同的格式返回给调用端,对他们来说会更方便。比如,如果客户端是基于浏览器的应用,那么就返回HTML。HTTP-RPC使用CTemplate来支持模板,允许调用结果被转换成任意格式。

        CTemplate定义了一组“占位符”,当处理模板的时候,这些“占位符”会被“数据字典”里的值代替。

  • {{_variable_}} - 注入一个来自数据字典的变量并输出
  • {{#_section_}}...{{/_section_}} - 定义一个循环体
  • {{>include}} - 导入另一个模板定义的内容
  • {{!_comment_}} - 对模板内容的注释

        服务返回的值相当于数据字典。它们一般是java.util.Map类的实例,值跟键相关联。

        下面的代码演示了如何使用一个简单的模板把getStatistics()方法返回的结果显示成一个web页面:

<html> 
    <head> 
        <title>Statistics</title> 
    </head> 
    <body> 
        <p>Count: {{count}}</p> 
        <p>Sum: {{sum}}</p> 
        <p>Average: {{average}}</p> 
    </body> 
</html>

        注意这里我们是如何使用“count”,“sum”,“average”这几个值的占位符的。在程序运行期间,这些占位符会分别被数据字典里的值替换,生成最终的结果。

 

模板注解

        @Template注解用于把模版文档关联到方法上,注解的值表示要应用到结果上的模版的名字和类型。例如:

@RPC(method="GET", path="statistics") 
@Template(name="statistics.html", mimeType="text/html") 
public Map<String, ?> getStatistics(List<Double> values) { ... }

        “name”指向包含模版定义的文件,它一般是相对于服务类的资源路径。

        “contentType”指明模版生成内容的类型,它被HTTP-RPC用来标识模版。客户端发出请求的URL是服务名称加上与“contentType”相对应的文件扩展名。

        有了这个注解,当用GET方法访问statistics.html时getStatistics()方法会被调用,模版也会被应用,生成最终结果:

<html> 
    <head> 
        <title>Statistics</title> 
    </head> 
    <body> 
        <p>Count: 3.0</p> 
        <p>Sum: 9.0</p> 
        <p>Average: 3.0</p> 
    </body> 
</html>

        这个结果跟使用下面的命令得到的结果是一样的。先用curl下载JSON格式的响应结果,然后使用mustache命令应用模版:

curl -s "http://localhost/math/statistics?values=1&values=3&values=5" | mustache - statistics.html

        因为HTTP-RPC是在服务器端应用模版,所以在客户端不需要做任何处理。不过,在开发的时候使用curl来测试模版还是很管用的。

 

实际的例子

        举个实际的例子,我们从BIRT示例里拿出一个表,对这个表做了一个SQL查询,然后web服务把结果返回。

CREATE TABLE Products ( 
    productCode VARCHAR(50) NOT NULL, 
    productName VARCHAR(70) NOT NULL, 
    productLine VARCHAR(50) NOT NULL, 
    productScale VARCHAR(10) NOT NULL, 
    productVendor VARCHAR(50) NOT NULL, 
    productDescription TEXT NOT NULL, 
    quantityInStock SMALLINT NOT NULL, 
    buyPrice DOUBLE NOT NULL, 
    MSRP DOUBLE NOT NULL, 
    PRIMARY KEY (productCode) 
);

        返回查询结果的服务方法可能是下面定义的那样。ResultSetAdapter类让SQL查询结果直接从服务方法返回。它实现了List接口,并把JDBC里的每一行数据转换成一个Map实例,Map很容易被序列化成JSON。HTTP-RPC可以保证当所有数据被写到输出流,结果集就会被关闭。

@RPC(method="GET") 
@Template(name="products.html", contentType="text/html") 
public ResultSetAdapter getProducts() throws SQLException { 
    Statement statement = getConnection().createStatement(); 
    String sql = "SELECT * FROM Products"; 
    return new ResultSetAdapter(statement.executeQuery(sql)); 
}

        服务方法返回的原始JSON可能看起来像下面这个样子,很容易被移动应用客户端处理:

[ 
    { "productCode": "S10_1678", 
    "productName": "1969 Harley Davidson Ultimate Chopper", 
    "productLine": "Motorcycles", 
    "productScale": "1:10", 
    "productVendor": "Min Lin Diecast", 
    "productDescription": "This replica features working kickstand...", 
    "quantityInStock": 7932, 
    "buyPrice": 48.81, "MSRP": 95.7 }, ... 
] 

        下面的模版可以把JSON转换成HTML,适合在浏览器中显示。注意到模版里的^html修饰符,它们可以保证输出结果是经过HTML编码的。在项目文档里有更多关于修饰符的内容。

<html> 
    <head> 
        <title>Product List</title> 
    </head> 
    <body> 
        <table> 
        {{#.}}<tr> 
            <td>{{productCode:^html}}</td> 
            <td>{{productName:^html}}</td> 
            <td>{{productLine:^html}}</td> 
            <td>{{productScale:^html}}</td> 
            <td>{{productVendor:^html}}</td> 
            <td>{{productDescription:^html}}</td> 
            <td>{{quantityInStock}}</td> 
            <td>{{buyPrice:format=currency}}</td> 
            <td>{{MSRP:format=currency}}</td> 
        </tr>{{/.}} 
        </table> 
    </body> 
</html>

        结果可能会是这样的:

<html> 
    <head> 
        <title>Product List</title> 
    </head> 
    <body> 
        <table> 
        <tr> 
            <td>S10_1678</td> 
            <td>1969 Harley Davidson Ultimate Chopper</td> 
            <td>Motorcycles</td> 
            <td>1:10</td> 
            <td>Min Lin Diecast</td> 
            <td>This replica features working kickstand...</td> 
            <td>7932</td> 
            <td>$48.81</td> 
            <td>$95.70</td> 
        </tr> ... 
        </table> 
    </body> 
</html>

 

总结

        这篇文章概述了如何使用模版把REST服务调用结果转换成HTML,特别是因此极大减少为了同时支持web和移动客户端所付出的开发成本。要注意,模版不仅仅局限于HTML,它也可以被用来生成其它格式,如XML或者CSV。

© 著作权归作者所有

半包咖啡豆
粉丝 5
博文 14
码字总数 44502
作品 0
浦东
架构师
私信 提问
MVC框架显示层——Velocity技术

Velocity,名称字面翻译为:速度、速率、迅速,用在Web开发里,用过的人可能不多,大都基本知道和在使用Struts,到底Velocity和Struts(Taglib和Tiles)是如何联系?在技术上Velocity要比Strut...

飞翼
2016/12/09
36
0
FreeMarker:浅尝辄止

一:FreeMarker简介: FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写 FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式的应用程序 FreeMarker允许...

晨曦之光
2012/08/22
439
0
借助C++ Substitution failure is nor error(SFINAE)实现完美的Pretty Printer框架

本文通过5种方案实现了Pretty Printer框架。借助SFINAE非常完美的满足了Pretty Printer提出的的四点需求。即做到了全类型制霸,做到了无侵入,不需要改变用户对象的内存布局,不会触发隐式转...

mongolguier
2018/04/18
0
0
企业应用架构模式学习(十四):web表现形式

模型-视图-控制器(Model View Controller) MVC 模式是我们目前最常用的模式,它可是起源于20世纪70年代后期,比80后都早。 运行机制 模型:一个表示领域信息的对象,是一个不可见的对象,包...

大风起兮
2013/06/05
190
0
面向工程的移动Web前端模版--Qing

什么是Qing? Qing是一套基础开发模版,来源于我们在手机与PC端上的大量工程实践。Qing所提供不是冷冰冰的文件, 而是一套Web前端解决方案,所以Qing不只是关注项目的初始状态,而是整体的工...

叶秀兰
2014/09/17
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

Java 8 Optional:优雅地避免 NPE

本篇文章将详细介绍 Optional 类,以及如何用它消除代码中的 null 检查。在开始之前首先来看下什么是 NPE,以及在 Java 8 之前是如何处理 NPE 问题的。 空指针异常(NullPointException,简称...

武培轩
11分钟前
0
0
CountDownLatch实现的并发框架

目录结构 package com.**.**.base.support.executor;import lombok.NoArgsConstructor;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;imp......

MR_TE
12分钟前
2
0
学习记录(day06-事件、按键修饰符、计算属性)

[TOC] 1.1 事件修饰符/按键修饰符 vue 通过事件修饰符对dom事件细节进行控制 <标签 @事件.修饰符="函数"></标签>.prevent ---阻止浏览器默认行为.stop ---阻止浏览器事件冒泡.e...

庭前云落
31分钟前
0
0
006-Sigle-基于blockstack去中心化博客

本篇文章主要讲解有关基于Blockstack的Sigle是一个去中心化的博客项目; 官网地址:https://www.sigle.io/ Github地址:https://github.com/pradel/sigle 页面展示: 介绍: A beautiful de...

Riverzhou
39分钟前
15
0
驰骋工作流引擎开发平台属性功能的隐藏显示介绍

关键字: 工作流程管理系统 工作流引擎 asp.net工作流引擎 java工作流引擎. 表单引擎 工作流功能说明 工作流设计 工作流快速开发平台 业务流程管理 bpm工作流系统 java工作流主流框架 自定义...

孟娟
40分钟前
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部