一个基于Java Spi机制实现的远程服务调用组件

介绍

远程服务调用组件
1.远程服务调用组件是基于Java SPI(Service Provider Interface)机制实现,具有插件式,高度可扩展,集成使用简单等特点。
2.独创的XML配置格式及配套解析方法,让远程接口配置更为简单,处理更为灵活,
3.组件提供统一的远程服务调用API,解决了远程服务调用代码分散,实现各异,配置硬编码等问题。
4.同时统一抽象的调用器接口使得每个服务的具体通信方式和格式对使用者都是透明的,可通过配置不同的调用器类来支持相应的调用策略。
5.远程服务调用组件,屏蔽了具体的通信方式和数据格式转换细节,程序员只需要传入参数对象和服务ID即可。

软件架构

类关系模型

image

基于XML配置文件,配置远程接口,提供统一远程调用API,解决了远程服务调用代码分散,配置硬编码在代码中,开发效率低下的问题。与现在使用的大多数方法相比,本方法简化了远程服务调用流程,配置灵活,开发效率大大提高,具体内容如下:

  1. 独创XML配置格式:

<services>

     <http-endpoint id="http_credits" service-addr="/v1/integral/"type="https">
            <host address="www.test.com" port="" appKey="30a10e21" secretKey="43351af641be41e08d398f6952d4b8c1" livemode="0" scope="INTEGRAL" />
     </http-endpoint>

   <import service-id="add" api="add" endpoint-ref="http_credits">
     <url-param name="accesstoken">#accessToken</url-param>
     <data-param name="appid">#appKey</data-param>
     <data-param name="livemode">#livemode</data-param>
   </import></services>
  1. XML解析模块:系统启动时,读取XML配置文件,经过XML文件格式校验,节点完整性校验,节点数据格式合法性校验,解析等过程,最终将XML信息转换为系统内部接口服务元数据信息,保存在内存中,供下一步使用。

  2. 服务注册模块:完成XML解析之后,服务注册模块,读取接口服务元数据信息,将原数据信息转换为服务调用模块需要的接口服务信息格式,保存在内存中,供服务调用模块使用。

  3. 接口服务调用模块:提供了统一的远程服务调用接口,程序根据服务ID定位到已经注册的远程服务接口,传入请求参数,即可完成远程服务的调用,屏蔽了不同服务接口,请求方式,参数格式,协议类型等差异,让使用更为简单,开发效率更高。

  4. 服务响应处理模块:接口服务调用模块完成服务调用后,返回信息将由此模块进行统一处理,将不同接口服务响应数据格式转换为统一的接口响应格式,也可根据不同接口配置不同的响应数据处理(扩展开发),屏蔽了响应数据格式差异。

安装教程

  1. Maven工程导入方式:pom.xml 文件添加如下依赖

<dependency>
   <groupId>com.remoting</groupId>
   <artifactId>remoting-core</artifactId>
   <version>1.0.0</version></dependency>

  • jar包引入方式:导入以下包到工程

  • remoting-api-1.0.0.jar
    remoting-core-1.0.0.jar
    remoting-tools-1.0.0.jar

    使用说明

    1. 配置:以jar包引入方式,需要配置remoting.xml

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    
     <!-- 远程服务调用器 -->
     <bean id="remoteServiceExecutor"
         class="com.remoting.core.http.HttpRemoteServiceExecutor">
         <!-- 构造函数:远程服务配置  -->
         <constructor-arg ref="remoteSetting" />
     </bean>
     <!-- 远程服务配置 -->
     <bean id="remoteSetting"
         class="com.remoting.api.RemoteSetting">
         <!-- 远程服务配置文件路径 -->
         <property name="configLocation" value="service-import*.xml" />
         <!-- 连接池最大并发连接数 -->
         <property name="maxTotal" value="300" />
         <!-- 单路由最大并发数 -->
         <property name="defaultMaxPerRoute" value="50" />
         <!-- 数据传输处理时间(毫秒) -->
         <property name="socketTimeout" value="4000" />
         <!-- 建立连接的timeout时间(毫秒) -->
         <property name="connectionTimeout" value="3000" />
         <!-- 从连接池中获取连接的timeout时间(毫秒) -->
         <property name="connectionRequestTimeout" value="1000" />
     </bean></beans>

    1. 远程服务注册配置

    <?xml version="1.0" encoding="UTF-8"?><services>
        <!-- 注意:accessToken,livemode,appKey,secretKey 为系统变量,以#开头使用,使用后节点的值将会被替换为系统生成的值 -->
     <http-endpoint id="http_personalizedRecommend" service-addr="/CHiQ3/launcher/" type="http">
         <!-- 服务器地址
         <host address="chiq3.chiq-cloud.com" />-->
         <!-- 测试服务器地址 -->
         <host address="chiq3.test-cloud.com" port="" />
     </http-endpoint>
     <!-- 获取子类目个性化推荐 -->
     <import service-id="together" api="together" endpoint-ref="http_personalizedRecommend" request-type="post" ></import>
     </services>

  • 配置参数说明

    注意:accessToken,livemode,appKey 为系统变量,以#开头使用,使用后节点的值将会被替换为系统生成的值

    对应配置文件说明:

    http-endpoint 节点
    属性
    id:服务地址节点ID,全局唯一。
    Service-addr:路径。
    Type:类型。
    Host子点:
    属性
    address:地址。
    port:端口。
    appKey:应用的唯一标示
    secretKey:安全码
    livemode:模式
    scope:作用域
    http-header 节点
    属性
    id:服务地址节点ID,全局唯一。
    charset:编码类型。
    Header子点:
    属性
    name:header键。
    Import 节点:要导入的接口
    属性
    Service-id:服务ID,全局唯一。
    Api: 即积分系统发布的接口名称。
    Endpoint-ref:服务地址配置引用ID。
    Header-ref:http请求头信息配置引用ID。
    request-type:请求方式(post,get),默认post。
    子节点
    url-param:需要添加到URL中的参数配置。
    data-param:需要添加到请求参数中的参数配置。
    注意:取值支持SpingEL表达式,需在请求参数中配置该值,如

  •  <!-- 积分查询 -->
     <import service-id="query" api="" endpoint-ref="http_credits" request-type="get">
         <url-param name="accesstoken">#accessToken</url-param>
         <url-param name="appid">#appKey</url-param>
         <url-param name="livemode">#livemode</url-param>
     </import>

    Key 也支持SpingEL表达式:根据配置的EL表达式找到对应的节点并添加对应的字段(要添加的节点必须存在)如

      <!-- 推送消息 -->
     <import service-id="pushMsg" api="pushMsg" endpoint-ref="http_mag">
         <data-param name="apikey">#appKey</data-param>
         <data-param name="secretkey">#secretKey</data-param>
         <data-param name="mType">THIRD</data-param>
         <data-param name="apiversion">1.0</data-param>
         <data-param name="['tag']['online']">0</data-param>
         <data-param name="['tag']['userflag']">2</data-param>
         <data-param name="['tag']['target']['condition']">AND</data-param>
     </import>
    特别说明:
    1. url-param:需要添加到URL中的参数配置。
      如上例中的:

       ```
        <url-param name="accesstoken">#accessToken</url-param>
       ```
       
             对应积分系统发布的接口规范中,url部分:   
       ```
        https://www.xxx.com?accesstoken=xxx
       ```
       
        其中url-name 节点中的 name属性对应的值为url中的accesstoken(注意大小写与url中保持一致)。
       
       accessToken 则表示引用系统变量accessToken,系统自动识别,根据endpoint-ref引用配置中host配置(appKey, secretKey, scope),以及当前import节点属性api的值,生成accessToken。
    1. data-param:需要添加到请求参数中的参数配置。
      如上例中的:

    <url-param name="appid">#appKey</url-param><url-param name="livemode">#livemode</url-param>
    1. 对应积分系统发布的接口规范中,正文部分相应的字段。

    像这种会根据服务器环境变化而变化,且在请求参数中的参数,可以用此节点配置,配置后,调用程序中就不需要再设置。
    其中url-name 节点中的 name属性对应的值为接口规范请求参数中的key。

    1. url-param ,data-param:
      取值支持SpingEL表达式,需在请求参数中配置该值。
      如:

    <url-param name="token]">['token']</data-param>

    Key EL表达式

    <data-param name="['tag']['online']">0</data-param>

    1. Header-ref:
      http请求头信息引用ID,如果不设置则使用组件默认值:
      即:Content-Type:application/json,编码为:UTF-8

    1. 程序调用

    package com.remoting;import com.remoting.api.RemoteServiceExecutor;import com.remoting.api.RemoteSetting;import com.remoting.api.spi.executor.RemoteServiceExecutorFactory;import junit.framework.Test;import junit.framework.TestCase;import junit.framework.TestSuite;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.HashMap;import java.util.Map;/**
     * Unit test for simple App.
     */public class RemotingTest extends TestCase {
     private final Logger logger = LoggerFactory.getLogger(RemotingTest.class);
     private RemoteServiceExecutor remoteExecutor;
     protected void setUp() throws Exception {
         remoteExecutor = remoteExecutor();
     }
     private RemoteServiceExecutor remoteExecutor(){
         return RemoteServiceExecutorFactory.create();
     }
     /**
      * 构建远程服务配置
      * <p>说明:</p>
      * <li></li>
      * @author DuanYong
      * @return
      * @since 2017年7月31日下午4:45:39
      */
     private RemoteSetting createRemoteSetting() {
         RemoteSetting remoteSetting = new RemoteSetting();
         remoteSetting.setConfigLocation("remoting/service-import*.xml");
         remoteSetting.setConnectionRequestTimeout(5 * 1000);
         remoteSetting.setConnectionTimeout(3 * 1000);
         remoteSetting.setDefaultMaxPerRoute(50);
         remoteSetting.setSocketTimeout(4 * 1000);
         remoteSetting.setMaxTotal(300);
         return remoteSetting;
     }
     /**
      * Create the test case
      *
      * @param testName
      *            name of the test case
      */
     public RemotingTest(String testName) {
         super(testName);
     }
    
     /**
      * @return the suite of tests being tested
      */
     public static Test suite() {
         return new TestSuite(RemotingTest.class);
     }
    
     /**
      * Rigourous Test :-)
      */
     public void testApp() {//       Map<String, Object> param = new HashMap<String, Object>();//       param.put("mac", "18-99-f5-5d-71-c7");//       param.put("sversion", "ZLM65HiS2_0.00094");//       param.put("img_size", "300,564");//       param.put("keyword", "tv");//       Object resultObj = remoteExecutor.execute("together", param);//       assertNotNull(resultObj);
         
         Map<String, Object> param = new HashMap<String, Object>();
         param.put("billUserId", "2088102971016535");
         param.put("startTime", "2019-02-18");
         param.put("endTime", "2019-02-19");
         param.put("ctoken", "u6vanf_DNIG55OWM");
         Object resultObj = remoteExecutor.execute("tradeListQuery", param);
         logger.info(resultObj.toString());
         System.out.println(resultObj.toString());
         assertNotNull(resultObj);
     }
     public void testTogether() {
         Map<String, Object> param = new HashMap<String, Object>();
         param.put("mac", "18-99-f5-5d-71-c7");
         param.put("sversion", "ZLM65HiS2_0.00094");
         param.put("img_size", "300,564");
         param.put("keyword", "tv");
         Object resultObj = remoteExecutor.execute("together", param);
         assertNotNull(resultObj);
     }}

    下载地址

    https://gitee.com/javacoo/remoting