论坛首页 Java企业应用论坛

Rop开发手册(2):最简单的服务开放平台框架

浏览 6502 次
精华帖 (5) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-10   最后修改:2012-08-22
引用

Rop,即Rapid Open Platform,是一个参考淘宝开放平台(TOP Taobao Open Platform)的平台设计思路,充分借鉴Spring MVC的技术架构原理开发的一个快速服务开放平台开源框架项目,可以让您迅速构建成熟的SOA服务平台。它不同于传统的SOAP Web Service和Rest Web Service这些Web Service 技术型框架,Rop是一个应用型的Web Service平台框架,它不但可以方便快捷地开发一个个Web Service服务,还提供了服务平台领域问题的整体解决方案。因此,...

传统的Web Service框架帮助你建造房子,而Rop框架帮助您建造城市。

Rop项目文档前后写了一个多月,在写的过程中发现很多功能不完善的,又回过头了改项目,此种反复多次,现在Rop开源项目的文档已经差不多了,将陆续在iteye此发出,希望对大家有帮助。在Rop项目的开发过程中得到了众多iteye网友的有力帮助,收到了很多很好的整改的意见,在此对他们的奉献和帮助表示诚挚的感谢,这些网友包括但不限于:
melin
kellokitty
風一樣的男子
lioliang
木木的爱情童话
devotionalist
ericliang
 

Rop相关资源:
1.github:https://github.com/itstamen/rop
2.群组:http://rop.group.iteye.com/


目录
1.快速了解Rop
2.请求服务模型
3.应用授权及验证
4.服务会话管理
5.错误处理模型
6.响应报文控制
7.文件上传
8.服务安全控制
9.拦截器及事件体系
10.性能调优
11.开发客户端SDK
12.参考资料

传统Web Service请求模型

请求模型设计的好坏将直接影响服务调用的难易程度,设计良好的请求模型可以让服务调用成为随时随地、信手拈来的事。此外,还能使服务接口清晰化,降低开发者理解服务的难度。我们先来了解一下传统Web Service的请求模型。

SOAP请求模型


Web Service基本上都是使用HTTP传输协议进行交互的,服务的响应报文一般支持XML和JSON两种格式,但Web Service服务请求模型却各有千秋。
传统的Web Service采用SOAP请求报文,任何服务都对应一组SOAP请求/响应报文,服务的调用及报文解析都比较麻烦。举例来说,即使是调用一个诸如查看当天天气的简单服务,该服务仅有一个city的参数,在SOAP的世界里,您也必须将其封装成一个复杂的SOAP请求报文才行。一般情况下,不借助CXF、Axis这类框架你很难访问SOAP。

REST请求模型


但是,很多情况下,开发者往往希望自由地随时随地访问服务,比如,通过一个形如
引用
http://www.xxx.com/weather/{city}

的URL就可以访问服务获取响应。把服务看成一个类似于文档、图片式的普通资源,通过一个唯一的URL进行定位和调用――这就是现在方兴未艾的REST Web Service的中心思想。
REST Web Service充分挖掘了HTTP通讯协议的内涵,借助HTTP方法(如GET、POST、PUT、DELETE等)及合理设计的服务URL,让Web Service达到不言自明的效果。

豆瓣网的API就是采用标准的REST Web Service开发的,来看一个获取图书信息的API:

引用
http://api.douban.com/book/subject/isbn/{isbnID}


该服务使用HTTP的GET方式调用,说白了就是您可以简单地在浏览器地址栏中敲入以下URL,就可以发起服务调用:

引用
http://api.douban.com/book/subject/isbn/9787508630069


以上请求将获得《史蒂夫•乔布斯传》这本书的服务响应报文,它是一个XML报文,您既可以在浏览器中预览,也可以写一个程序消费这个响应报文,完成您要干的事情。这种服务调用方式,对于服务调用者非常亲切,因为它和访问一个网页并无二致。如果要学习REST Web Service的设计,豆瓣网的API就是不错的学习案例,我们来欣赏一下豆瓣网其它几个API:

  • GET http://api.douban.com/movie/subject/{subjectID}:获取某个专题的信息,GET表示使用HTTP请求方法,下同;
  • GET http://api.douban.com/people/{userID}:获取某个用户的信息;
  • GET http://api.douban.com/people?q=douban&start-index=10&max-results=5:搜索用户,用户名通过q参数传递,其它两个参数是分页控制参数;
  • DELETE http://api.douban.com/review/{reviewID}:删除某篇评论。


采用REST请求模型发布的服务接口很清晰化、调用也很简单,REST服务已经模糊了服务和网页资源的界限。简单就是最好的,从这个意义上说REST确实优于SOAP,开发者也纷纷用脚做出了投票,弃SOAP之暗而投REST之明。
REST在扛起挑战SOAP大旗时,对SOAP的战斗檄文是:复杂,笨重,EJB死灰复燃。但是,当REST得天下后,我们发现REST本身也存在一些刻板的东西。

首先,经典的REST对HTTP请求方法的使用过于教条化:新增、更改、删除、获取资源的服务分别对应POST、PUT、DELETE和GET的HTTP请求方法。一般的Web服务器和浏览器都只支持GET和POST这两种HTTP请求方法,所以在实际应用中,REST希望充分挖掘HTTP请求方法能力的倡议遭遇了困难。

其次,REST提倡为每个服务设计一个“达意”的URL,让服务的URL望文生义。从可读性,清晰化的角度上看,REST的这个建议是非常值得称赞的。但是,服务的消费者主体是程序,让每个服务对应不同的URL,反而让客户端程序不好写。

综上所述,当前如日中天的REST Web Service自身也存在一些待改进的地方。淘宝的TOP的请求模型可以看成是REST的变体,首先,TOP提供的所有服务的URL都是一样的:即为http://gw.api.taobao.com/router/rest,使用method参数指定服务API名称,再通过其它参数指定服务的入参。由于平台所有服务的URL都相同,不同的服务方法通过method参数区分,反而让服务的调度变得简单了。

Rop请求模型

Rop请求模型的设计直接借鉴了TOP的思想,服务开放平台的所有服务URL是相同的,请求参数分为系统级参数和业务级参数两部分,系统级参数是所有服务API都拥有的参数,而业务级参数由具体服务API定义。

统一服务URL

采用Rop的服务开放平台,其所有的服务都使用统一的URL,Rop通过method系统级参数将请求路由到指定的服务方法中完成服务受理。如何设置这个统一的服务URL呢?答案很简单,即是通过RopServlet的<servlet-mapping>进行定义。

服务平台最终的URL为:<开放平台根URL>/<RopServlet的映射URI>。举例来说,服务器URL为api.xxx.com,而RopServlet的映射URI为/router,则服务统一URL为:

引用
http://api.xxx.com/router。


系统级参数

系统级参数是由开放平台定义的一组参数,每个服务都拥有这些参数,用以传送框架级的参数信息。如我们前面提到的method就是一个系统级参数,使用该参数指定服务的名称。Rop共有7个系统级参数,在下表中说明:

参数名称 是否必须 参数说明
appKey 应用键,开放平台用以确定客户端应用的身份,如000001,000002等。应用键对应一个密钥secret。要基于服务平台开发应用,必须事先通过申请获取appKey/secret后,才能进行应用的开发。
sessionId 会话ID,一般是一个36位的UUID,在登录服务平台后获取;
method 服务方法名,一般采用“名词+动词”的结构定义。如user.get、user.create等;
v 服务方法的版本号,如1.0、2.0等。一个具体的服务方法上method+v两者唯一确定。因此服务平台必须保证所有服务的method+v的唯一性。
format 通信报文格式,可选值为xml和json,默认为xml。
locale 本地化类型,默认为zh_CN。
sign 签名串,请求参数的签名,服务平台通过它验证请求数据的合法性。


locale、format这两个系统级参数的功用是不言自明的,而其它的系统级参数由于涉及到服务开放平台很多的领域性问题,需要一些背景知识的铺垫,因此我们将在后续内容中进行专门的介绍。

默认情况下,系统级参数名是固定的,一般情况下,并不需要对调整它。如果希望使用自行定义的参数名称,可以使用<rop:sysparams/>进行定义,如下所示:

sampleRopApplicationContext.xml:定义系统级参数名
<rop:sysparams 
            format-param-name="messageFormat"
            appkey-param-name="app_key"/>


业务级参数

业务级参数,顾名思义是由业务逻辑需要自行定义的,每个服务API都可以定义若干个自己的业务级参数。Rop根据参数名和RopRequest类属性名相等的契约,将业务级参数绑定到RopRequest中。

如LogonRequest定义了两个userName和password两个属性,Rop就会将HTTP请求参数值绑定到LogonRequest对象的同名属性中。

参数数据绑定与验证

参数数据绑定


当客户端调用服务平台某个服务时,其实质是向服务平台的URL发送若干个请求参数(包括系统级和业务级的参数)。Rop框架在接收到这些请求参数后,就会将其绑定到RopRequest请求对象中,服务方法可通过这个RopRequest对象获取请求参数信息,进而执行相应的服务API并返回响应结果。下图描述了请求参数的转换过程:


图1



首先,客户端的服务请求通过HTTP报文发送给服务端的Servlet服务器(即HTTP服务器),Servlet服务器将HTTP报文转换成一个HttpServletRequest对象。然后通过RopServlet转交给Rop框架,Rop框架将HttpServletRequest转换成一个RopRequestContext对象。接着,ServiceRouter将RopRequestContext传给ServiceMethodAdapter,ServiceMethodAdapter在内部将RopRequestContext转换成RopRequest对象,输送给最终的服务方法。
从上面的数据转换过程中,我们知道每当客户端发起一个服务调用时,Rop都会在内部创建一个RopRequestContext实例,它包含了所有的请求数据信息。

下面,我们来了解一下RopRequestContext接口的方法:
  • String getAppKey():获取appKey系统级参数的值。RopRequestContext为每个系统级参数都分配了一个对应的接口方法,如String getMethod()、String getSessionId()等;
  • HttpAction getHttpAction():获取HTTP请求方法,HttpAction是一个枚举,仅有两个枚举值,即GET和POST。这也说明,Rop仅支持GET和POST两个HTTP请求方法;
  • String getIp():获取请求来源的IP地址。由于在集群环境下,请求通过前端的负载均衡器再传给后端集群的某个具体服务节点。因此,直接使用ServletRequest#getRemoteAddr()返回的值将是前端负载均衡服务器的IP,在此Rop使用了一些技巧,以保证后端服务获取的IP是客户端的IP。具体实现可以参见com.rop.ServletRequestContextBuilder#getRemoteAddr(HttpServletRequest request)的实现;
  • Object getRawRequestObject():获取原请求对象,即服务请求对应的HttpServletRequest对象;
  • Map<String, String> getAllParams():获取服务请求所对应的所有请求参数。可以通过String getParamValue(String paramName)获取某个具体参数的值;
  • RopContext getRopContext():获取Rop框架上下文的信息。RopContext之于Rop框架相当于ServletContext之于Servlet容器,它包含了很多Rop框架的运行期信息,所有Rop的服务方法都注册在RopContext 中。


概括来说,RopRequestContext为每个系统级参数都提供了一个方法,如getAppKey()、getMethod()等。对于业务级参数,则可以使用RopRequestContext的getParamValue("<参数名>")获取。此外,RopRequestContext还提供了获取原始请求对象、客户端IP等方法。

所有服务方法的入参都是RopRequest接口或其实现类,RopRequest接口仅有一个方法:

RopRequestContext getRopRequestContext();

RopRequest的实现类负责定义业务级参数对应的属性,这样,在服务方法内部,就可以通过RopRequest#getRopRequestContext()获取RopRequestContext,再通过RopRequestContext访问到系统级参数了。而业务级参数是可通过RopRequest实现类的属性获取。

在下面的getSession()服务方法中,我们定义了一个LogonRequest的请求对象,它就是一个实现了RopRequest接口的对象,在服务方法内部,可以通过LogonRequest访问到系统级参数、业务级参数及其它相关的信息,如下所示:

UserService.java
@ServiceMethod(method = "user.getSession",needInSession = NeedInSessionType.NO) 
public RopResponse getSession(LogonRequest request) {

//①访问系统级参数
        String appKey = request.getRopRequestContext().getAppKey();

//②-1 访问业务级参数:通过类属性
        String userName1 = request.getUserName();
//②-2 访问业务级参数:通过RopRequestContext获取
        String userName2 = request.getRopRequestContext().getParamValue("userName");

//③获取其它信息
        String ip = request.getRopRequestContext().getIp();
}


通过上面的实例,我们可以知道通过RopRequest可以很方便地获取系统级参数、业务级参数及客户端的相关信息。

参数数据验证

由于应用客户端和服务平台都是服务报文进行通信的,所有的请求参数都以字符串的形式传送过来。为了保证服务得到正确执行,必须事先对请求参数进行数据合法性验证,只有在服务请求所有参数都符合约定的情况下,服务平台才执行具体的服务操作,否则直接驳回请求,返回错误的报文。

数据校验从责任主体上看,可分为客户端校验和服务端校验两种。对于一个封闭式的应用软件来说,由于服务端和客户端都是一体化开发的,为了减少开发工作量,有时仅需要进行客户端校验就可以了。但是,服务开放平台的潜在调用者是不受限的,应用开发者可基于服务平台开发出众多丰富多彩的应用,在这种场景下,服务端校验是必不可少的。

服务请求参数的校验是开放平台的一项重要的基础功能,Rop和Spring MVC一样使用JSR 303注解定义参数的校验规则,当请求数据违反校验规则时,直接返回对应的错误报文,只有所有请求参数都通过合法性验证后,才调用目标服务方法。

下面的CreateUserRequest使用了JSR 303注解的,来看一下具体的使用方法:

CreateUserRequest.java:使用JSR 303对业务级参数进行校验
public class CreateUserRequest extends AbstractRopRequest {

    @Pattern(regexp = "\\w{4,30}")
    private String userName;

    @IgnoreSign
    @Pattern(regexp = "\\w{6,30}")
    private String password;

    @DecimalMin("1000.00")
    @DecimalMax("100000.00")
    @NumberFormat(pattern = "#,###.##")
    private long salary;

…
}


对于系统级的参数,Rop本身会负责校验,开发者仅需关注业务级参数的校验即可。当请求的参数违反校验规则后,Rop将把这些错误“翻译成”对应的错误报文。假设salary格式不对,其对应的错误报文为:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error code="33">
    <message>非法的参数</message>
    <solution>请查看根据服务接口对参数格式的要求</solution>
    <subErrors>
        <subError code="isv.parameters-mismatch:salary-and-yyy">
            <message>传入的参数salary和aaa不匹配,两者有一定的对应关系</message>
        </subError>
    </subErrors>
</error

关于错误处理模型及报文格式,我们将后续内容中讲解。

XML和JSON参数绑定

如果某个请求参数的值是一个XML或JSON串,能否正确地进行绑定呢?Rop框架支持将XML或JSON格式的参数值透明地绑定到RopRequest的复合属性中。

我们通过rop-sample实例项目的UserService#addUser(CreateUserRequest request)讲解XML/JSON参数值绑定的内容。 CreateUserRequest拥有一个Address的业务级参数,如下所示:

CreateUserRequest.java:复合属性
package com.rop.sample.request;
import javax.validation.Valid;
…
public class CreateUserRequest extends AbstractRopRequest {

    @Pattern(regexp = "\\w{4,30}")
    private String userName;

…
    
    //① 可绑定XML或JSON的复合属性,必须打上@Valid注解进行数据校验  
    @Valid
    private Address address;
}

Address是一个复合对象属性,它的类结构对应XML的结构:

CreateUserRequest.java:复合属性
package com.rop.sample.request;

import javax.validation.constraints.Pattern;
import javax.xml.bind.annotation.*;
import java.util.List;

// ①使用JSR 222注解,定义了基于属性名进行数据绑定的规则。
@XmlAccessorType(XmlAccessType.FIELD) 
@XmlRootElement(name = "address")
public class Address {

    //②使用JSR 222定义数据绑定规则,使用JSR 303注解定义数据校验规则。
    @XmlAttribute                  
    @Pattern(regexp = "\\w{4,30}")
    private String zoneCode;

    @XmlAttribute
    private String doorCode;


    // ③使用JSR 222注解指定列表数据的绑定规则
    @XmlElementWrapper(name = "streets") 
    @XmlElement(name = "street")
    private List<Street> streets;
}


JSR 222标准规范(也即JAXB),已经作为XML数据绑定官方标准添加到JDK 6.0核心库中。因此,我们直接使用JSR 222注解定义XML数据的绑定规则。官方标准的JAXB库只支持XML数据的绑定,很多开源进行了扩展,支持JSON数据的绑定,Rop使用Jackson项目完成JSON数据的绑定。

请求数据绑定时一般都需要进行数据校验,因此您还需要使用JSR 303的注解定义数据校验规则。通过JSR 222和JSR 303注解两者珠联璧合,Rop很完美地解决了请求数据绑定和数据校验的问题。

开发者仅需要在CreateUserRequest中标注上注解,无需做任何其它的开发工作,就可以绑定客户端的XML和JSON数据了。rop-sample项目的UserServiceRawClient有一个testServiceXmlRequestAttr()测试方法,它演示了XML参数数据绑定的场景:

UserServiceRawClient.java:XML请求参数
@Test
public void testServiceXmlRequestAttr() {
    RestTemplate restTemplate = new RestTemplate();
    MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
    form.add("method", "user.add");
    form.add("messageFormat", "xml");//①定消息报文格式为XML格式
    …
    
    //②ML格式的参数数据
    form.add("address",
            "<address zoneCode=\"0001\" doorCode=\"002\">\n" + 
            "  <streets>\n" +
            "    <street no=\"001\" name=\"street1\"/>\n" +
            "    <street no=\"002\" name=\"street2\"/>\n" +
            "  </streets>\n" +
            "</address>");

    //手工对请求参数进行签名
    String sign = RopUtils.sign(form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde");
    form.add("sign", sign);

     //调用服务获取响应报文
    String response = restTemplate.postForObject(SERVER_URL, form, String.class);
}


如果有某个请求参数的内容是XML,必须将报文格式设置成xml,如①所示。在②处,address的参数值即是一个XML格式的字符串,它将正确绑定到CreateUserRequest的address属性中。

相似的,下面的testServiceJsonRequestAttr()测试方法则使用JSON格式为address参数提供数据:

UserServiceRawClient.java:XML请求参数
public void testServiceJsonRequestAttr() {
	RestTemplate restTemplate = new RestTemplate();
	MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
	form.add("method", "user.add");
	form.add("messageFormat", "json");//①指定消息格式为JSON格式

        //②JSON格式的参数数据
	form.add("address",
			"{\"zoneCode\":\"0001\",\n" +                         
			" \"doorCode\":\"002\",\n" +
			" \"streets\":[{\"no\":\"001\",\"name\":\"street1\"},\n" +
			"            {\"no\":\"002\",\"name\":\"street2\"}]}");

	String sign = RopUtils.sign(form.toSingleValueMap(), "abcdeabcdeabcdeabcdeabcde");
	form.add("sign", sign);

	String response = restTemplate.postForObject(SERVER_URL, form, String.class);
}


将报文格式设置为json,即可支持JSON格式参数数据的绑定。在默认情况下,Rop不允许同时使用XML和JSON,仅能两者取一:请求和响应报文要么是XML,要么是JSON。


自定义数据转换器

对于复合结构的参数,我们推荐使用XML或JSON的格式指定参数内容。除此以外,Rop允许您通过注册自定义转换器支持自定义格式的参数。Spring 3.0新增了一个类型转换的核心框架,可以实现任意两个类型对象数据的转换,即org.springframework.core.convert.ConversionService,FormattingConversionService扩展于ConversionService,添加了格式化数据的功能。Spring的数据类型转换体系是高度可扩展的,Rop就是基于Spring的类型转换体系实施参数数据绑定的工作,因此,Rop允许开发者定义自己的类型转换器。

在Spring的类型转换服务体系中,转换器是由Converter<S, T>接口定义,它仅能实现单向转换,即从S到T的转换。但是Rop需要双向转换功能:在服务端将参数绑定到RopRequest时,将S转换成T,而在客户端将RopRequest流化成请求报文时,需要将T转换成S。因此,Rop对Converter<S, T>接口进行了扩展,定义了一个可以实现双向转换的接口,如下所示:

package com.rop.request;
import org.springframework.core.convert.converter.Converter;
public interface RopConverter<S, T> extends Converter<S, T> {

    S unconvert(T target);

    Class<S> getSourceClass();
    Class<T> getTargetClass();
}


Converter<S, T>接口定义了一个T convert(S source)的方法,RopConverter<S, T>新增了一个S unconvert(T target)的方法,这样就可以实现S和T两者的双向转换了。

开发一个类型转换器是件轻松的事情,仅需扩展RopConverter<S, T>接口并实现S和T相互转换的逻辑即可。rop-sample中定义了一个可实现格式化电话号码和Telephone对象的双向转换器:

TelephoneConverter.java:双向类型转换器
package com.rop.sample.request;

import com.rop.request.RopConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

public class TelephoneConverter implements RopConverter<String, Telephone> {

    @Override
    public Telephone convert(String source) {//①将格式化字符串转换为Telephone 
        if (StringUtils.hasText(source)) {
            String zoneCode = source.substring(0, source.indexOf("-"));
            String telephoneCode = source.substring(source.indexOf("-") + 1);
            Telephone telephone = new Telephone();
            telephone.setZoneCode(zoneCode);
            telephone.setTelephoneCode(telephoneCode);
            return telephone;
        } else {
            return null;
        }
    }

    @Override
    public String unconvert(Telephone target) {//②将Telephone转换为格式化字符串
        StringBuilder sb = new StringBuilder();
        sb.append(target.getZoneCode());
        sb.append("-");
        sb.append(target.getTelephoneCode());
        return null;
    }

    @Override
    public Class<String> getSourceClass() {
        return String.class;
    }

    @Override
    public Class<Telephone> getTargetClass() {
        return Telephone.class;
    }
}


接下来的工作是如何将TelephoneConverter注册到Rop中,以便Rop在进行参数数据绑定时利用这个转换器。
Rop的<rop:annotation-driven/>拥有一个formatting-conversion-service属性,可以通过该属性指定一个Spring的FormattingConversionService。在FormattingConversionService中即可注册自定义的Converter,如下所示:

sampleRopApplicationContext.xml:注册自定义类型转换器
<rop:annotation-driven formatting-conversion-service="conversionService"/>
<bean id="conversionService" 
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <!--将xxxx-yyy格式化串转换为Telephone对象-->
                <bean class="com.rop.sample.request.TelephoneConverter"/>
            </set>
        </property>
 </bean>


CreateUserRequest中拥有一个Telephone的属性:

CreateUserRequest.java
public class CreateUserRequest extends AbstractRopRequest {

    @Pattern(regexp = "\\w{4,30}")
    private String userName;

    @IgnoreSign
    @Pattern(regexp = "\\w{6,30}")
    private String password;

    private Telephone telephone;

…
}


Telephone类拥有zoneCode和telephoneCode两个属性,Rop在处理Telephone类型的数据绑定时,将自动调用TelephoneConverter进行数据转换。

UserServiceClient#testCustomConverter()演示了客户端使用TelephoneConverter的方法:

UserServiceClient.java:测试自定义类型转换器
@Test
public void testCustomConverter() {
    
ropClient.addRopConvertor(new TelephoneConverter());// ①
    
CreateUserRequest request = new CreateUserRequest();
    request.setUserName("tomson");
    request.setSalary(2500L);
    Telephone telephone = new Telephone();
    telephone.setZoneCode("0592");
    telephone.setTelephoneCode("12345678");

    CompositeResponse response = ropClient.buildClientRequest()
.post(request, CreateUserResponse.class, "user.add", "1.0");

    assertNotNull(response);
    assertTrue(response.isSuccessful());
    assertTrue(response.getSuccessResponse() instanceof CreateUserResponse);
}


在①处,RopClient注册了一个TelephoneConverter实现,当调用post()发送服务请求时,TelephoneConverter就会自动将Telephone对象转换成一个xxx-yyy的格式化串,并以请求报文的方式发送给服务端。而服务端则会利用注册在ConversionService中的TelephoneConverter,将xxx-yyy格式的电话号转为Telephone对象。

从上面的分析可知,RopConverter#unconvert()是服务于客户端,而RopConverter#convert()则服务于服务端。由于客户端和服务端位于不同的JVM中,因此必须各自独立注册RopConverter,关于RopClient的更多内容,将在后续内容中介绍。


请求服务映射

Spring MVC通过@RequestMapping注解实现HTTP请求到处理方法的映射。类似的,Rop使用@ServiceMethod注解实现HTTP请求到服务处理方法的映射。@ServiceMethod只能对Bean的方法进行标注,且该方法的签名是受限的:拥有一个RopRequest的入参和一个返回对象。

@ServiceMethod的method和version属性值是必须的,method代码服务方法名,而version表示版本号。如代码清单10-1的getSession()服务方法对应的注解是@ServiceMethod(method = "user.getSession", version = "1.0"),它对应如下的服务请求: 

引用
http://<serverUrl>/<ropServletUri>?method=user.getSession&v=1.0&...


来看一个具体的例子:

UserService.java
@Service //①服务类必须是一个Spring的Bean
public class UserService {

    //②服务方法对应如下的HTTP请求:?method=user.add&v=1.0&…
    @ServiceMethod(method = "user.add", version = "1.0") 
    public RopResponse addUser(CreateUserRequest request) {
       ...
    }
}


服务开放平台一旦将服务发布出去后,其内部实现可以不断优化和调整,但是服务接口必须保证不变,否则基于服务开发的第三方应用的运行稳定性就得不到保障。如果要调整服务接口定义,必须升级版本,这也是Rop为什么要求方法名一定要和版本同时提供的原因。

一个服务方法可以同时存在多个版本,客户端可以调用指定版本的服务。来看几个不同版本的服务及对应的客户端调用参数:

  • @ServiceMethod(method = "user.add", version = "2.0"):对应method=user.add&v=2.0;
  • @ServiceMethod(method = "user.add", version = "3.0"):对应method=user.add&v=3.0;
  • @ServiceMethod(method = "user.get", version = "1.5"):对应method=user.get&v=1.5;


@ServiceMethod除了method和version属性外,还拥有多个其它的属性,分别说明如下:
  • group:服务分组名。服务的分组没有特殊的意义,您可以为服务定义一个分组,以便在事件监听器、服务拦截器中利用分组信息进行特殊的控制。默认的分组为ServiceMethodDefinition.DEFAULT_GROUP;
  • groupTitle:服务分组标识;
  • tags:tags的类型是一个String[],您可以给服务打上一个或多个TAG,以便在事件处理监听器、服务拦截器利用该信息进行特殊的处理;
  • title:服务的标识;
  • httpAction:服务允许的HTTP请求方法,可选值在HttpAction枚举中定义,即GET或POST,如果不指定则不限制;
  • needInSession:表示该服务方法是否需要工作在会话环境中,默认所有的服务方法必须工作于会话环境中,也即请求的sessionId不能为空。如果某个方法不需要工作于会话环境中(如登录的服务方法、获取应用最新版本的服务方法),则必须显式设置:needInSession = NeedInSessionType.NO;
  • ignoreSign:表示该服务方法是否要进行请求数据签名验证,默认为需要。如果不需要,可以设置:ignoreSign=IgnoreSignType.NO。正式环境务必开启请求签名验证的功能,这样才能对客户端请求的合法性进行校验;
  • timeout:服务超时时间,单位为秒。如果服务方法执行时间超过timeout后,Rop将直接中断服务并返回错误的报文。



@ServiceMethod拥有众多的可设置属性,它们都和Rop具体的领域性问题相关联,因此,在这里只要知道method和version的属性就可以了,后面会对其它的属性进行深入的讲解。

如果一个服务类中拥有多个服务方法,而它们拥有一些共同的属性,如group、version等,能否在某个地方统一定义呢?答案是肯定的,Rop为复用服务方法元数据信息提供了一个类级别的@ServiceMethodBean。
@ServiceMethodBean拥有一套和@ServiceMethod类似的属性,其属性值会被同一服务类中所有的@ServiceMethod继承。

@ServiceMethodBean类本身已经标注了Spring的@Service,所以标注了@ServiceMethodBean的服务类就相当于打上的@Service,可以被Spring的Bean扫描器扫描到。

下面的例子拥有两个服务方法,它们的version都是1.0:

UserService.java:使用@ServiceMethodBean
@ServiceMethodBean(version = "1.0") ①
public class UserService {

    @ServiceMethod(method = "user.add") ②
    public RopResponse addUser(CreateUserRequest request) {
       ...
    }

   @ServiceMethod(method = "user.get", httpAction = HttpAction.GET)③
    public RopResponse getUser(CreateUserRequest request) {
...
}
}

②和③处的服务方法的version都自动设置为1.0,如果UserService 业务类方法显式指定了version属性,将会覆盖@ServiceMethodBean的设置。
Rop框架在启动时,将创建代表Rop框架上下文的RopContext实例,同时扫描Spring容器中所有的Bean,将标注了@ServiceMethod的Bean方法注册到RopContext的服务方法注册表中。这样,ServiceRouter就可根据RopContext中的服务方法注册表进行请求服务的路由了。
  • 大小: 38.6 KB
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics