浏览 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:
采用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个系统级参数,在下表中说明:
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接口的方法:
概括来说,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和version属性外,还拥有多个其它的属性,分别说明如下:
@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中的服务方法注册表进行请求服务的路由了。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||