SSM(十三) 將dubbo暴露出HTTP服務

crossoverJie發表於2017-05-02

SSM(十三) 將dubbo暴露出HTTP服務
dubbo暴露為http服務.jpg

前言

通常來說一個dubbo服務都是對內給內部呼叫的,但也有可能一個服務就是需要提供給外部使用,並且還不能有使用語言的侷限性。

比較標準的做法是對外的服務我們統一提供一個openAPI,這樣的呼叫方需要按照標準提供相應的appID以及金鑰來進行驗籤才能使用。這樣固然是比較規範和安全,但複雜度也不亞於開發一個單獨的系統了。

這裡所講到的沒有那麼複雜,就只是把一個不需要各種許可權檢驗的dubbo服務對外提供為HTTP服務。

呼叫示例:

SSM(十三) 將dubbo暴露出HTTP服務
dubbo-http封面.jpg

準備工作

以下是本文所涉及到的一些知識點:

  • Spring相關知識。
  • Java反射相關知識。
  • SpringMVC相關知識。

其實思路很簡單,就是利用SpringMVC提供一個HTTP介面。
在該介面中通過入參進行反射找到具體的dubbo服務實現進行呼叫。

HttpProviderConf配置類

首先需要定義一個HttpProviderConf類用於儲存宣告需要對外提供服務的包名,畢竟我們反射時需要用到一個類的全限定名:

public class HttpProviderConf {

    /**
     * 提供http訪問的包
     */
    private List<String> usePackage ;
    //省略getter setter方法
}複製程式碼

就只有一個usePackage成員變數,用於存放需要包名。
至於用List的原因是允許有多個。

請求響應入參、出參

HttpRequest入參

public class HttpRequest {
    private String param ;//入參
    private String service ;//請求service
    private String method ;//請求方法
    //省略getter setter方法
}複製程式碼

其中param是用於存放真正呼叫dubbo服務時的入參,傳入json在呼叫的時候解析成具體的引數物件。

service存放dubbo服務宣告的interface API的包名。

method則是真正呼叫的方法名稱。

HttpResponse 響應

public class HttpResponse implements Serializable{

    private static final long serialVersionUID = -552828440320737814L;

    private boolean success;//成功標誌

    private String code;//資訊碼

    private String description;//描述
    //省略getter setter方法
}複製程式碼

這裡只是封裝了常用的HTTP服務的響應資料。

暴露服務controller

最重要的則是controller裡的實現程式碼了。

先貼程式碼:

@Controller
@RequestMapping("/dubboAPI")
public class DubboController implements ApplicationContextAware{

    private final static Logger logger = LoggerFactory.getLogger(DubboController.class);

    @Autowired
    private HttpProviderConf httpProviderConf;

    //快取作用的map
    private final Map<String, Class<?>> cacheMap = new HashMap<String, Class<?>>();

    protected ApplicationContext applicationContext;


    @ResponseBody
    @RequestMapping(value = "/{service}/{method}",method = RequestMethod.POST)
    public String api(HttpRequest httpRequest, HttpServletRequest request,
                      @PathVariable String service,
                      @PathVariable String method) {
        logger.debug("ip:{}-httpRequest:{}",getIP(request), JSON.toJSONString(httpRequest));

        String invoke = invoke(httpRequest, service, method);
        logger.debug("callback :"+invoke) ;
        return invoke ;

    }


    private String invoke(HttpRequest httpRequest,String service,String method){
        httpRequest.setService(service);
        httpRequest.setMethod(method);

        HttpResponse response = new HttpResponse() ;

        logger.debug("input param:"+JSON.toJSONString(httpRequest));

        if (!CollectionUtils.isEmpty(httpProviderConf.getUsePackage())){
            boolean isPac = false ;
            for (String pac : httpProviderConf.getUsePackage()) {
                if (service.startsWith(pac)){
                    isPac = true ;
                    break ;
                }
            }
            if (!isPac){
                //呼叫的是未經配置的包
                logger.error("service is not correct,service="+service);
                response.setCode("2");
                response.setSuccess(false);
                response.setDescription("service is not correct,service="+service);
            }

        }
        try {
            Class<?> serviceCla = cacheMap.get(service);
            if (serviceCla == null){
                serviceCla = Class.forName(service) ;
                logger.debug("serviceCla:"+JSON.toJSONString(serviceCla));

                //設定快取
                cacheMap.put(service,serviceCla) ;
            }
            Method[] methods = serviceCla.getMethods();
            Method targetMethod = null ;
            for (Method m : methods) {
                if (m.getName().equals(method)){
                    targetMethod = m ;
                    break ;
                }
            }

            if (method == null){
                logger.error("method is not correct,method="+method);
                response.setCode("2");
                response.setSuccess(false);
                response.setDescription("method is not correct,method="+method);
            }

            Object bean = this.applicationContext.getBean(serviceCla);
            Object result = null ;
            Class<?>[] parameterTypes = targetMethod.getParameterTypes();
            if (parameterTypes.length == 0){
                //沒有引數
                result = targetMethod.invoke(bean);
            }else if (parameterTypes.length == 1){
                Object json = JSON.parseObject(httpRequest.getParam(), parameterTypes[0]);
                result = targetMethod.invoke(bean,json) ;
            }else {
                logger.error("Can only have one parameter");
                response.setSuccess(false);
                response.setCode("2");
                response.setDescription("Can only have one parameter");
            }
            return JSON.toJSONString(result) ;

        }catch (ClassNotFoundException e){
            logger.error("class not found",e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("class not found");
        } catch (InvocationTargetException e) {
            logger.error("InvocationTargetException",e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("InvocationTargetException");
        } catch (IllegalAccessException e) {
            logger.error("IllegalAccessException",e);
            response.setSuccess(false);
            response.setCode("2");
            response.setDescription("IllegalAccessException");
        }
        return JSON.toJSONString(response) ;
    }

    /**
     * 獲取IP
     * @param request
     * @return
     */
    private String getIP(HttpServletRequest request) {
        if (request == null)
            return null;
        String s = request.getHeader("X-Forwarded-For");
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("Proxy-Client-IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("WL-Proxy-Client-IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {
            s = request.getHeader("HTTP_CLIENT_IP");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (s == null || s.length() == 0 || "unknown".equalsIgnoreCase(s)) {

            s = request.getRemoteAddr();
        }
        if ("127.0.0.1".equals(s) || "0:0:0:0:0:0:0:1".equals(s))
            try {
                s = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException unknownhostexception) {
                return "";
            }
        return s;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }複製程式碼

先一步一步的看:

  • 首先是定義了一個DubboController,並使用了SpringMVC的註解對外暴露HTTP服務。

  • 實現了org.springframework.context.ApplicationContextAware類,
    實現了setApplicationContext()方法用於初始化Spring上下文物件,在之後可以獲取到容器裡的相應物件。

  • 核心的invoke()方法。

  • 呼叫時:http://127.0.0.1:8080/SSM-SERVICE/dubboAPI/com.crossoverJie.api.UserInfoApi/getUserInfo
  • 具體如上文的呼叫例項。先將com.crossoverJie.api.UserInfoApigetUserInfo賦值到httpRequest入參中。
  • 判斷傳入的包是否是對外提供的。如下配置:

      <!--dubbo服務暴露為http服務-->
      <bean class="com.crossoverJie.dubbo.http.conf.HttpProviderConf">
          <property name="usePackage">
              <list>
                     <!--需要暴露服務的介面包名,可多個-->
                  <value>com.crossoverJie.api</value>
              </list>
          </property>
      </bean>
      <!--掃描暴露包-->
      <context:component-scan base-package="com.crossoverJie.dubbo.http"/>複製程式碼

    其中的com.crossoverJie.api就是自己需要暴露的包名,可以多個。

  • 接著在快取map中取出反射獲取到的介面類型別,如果獲取不到則通過反射獲取,並將值設定到快取map中,這樣不用每次都反射獲取,可以節省系統開銷(反射很耗系統資源)。

  • 接著也是判斷該介面中是否有傳入的getUserInfo方法。
  • 取出該方法的引數列表,如果沒有引數則直接呼叫。
  • 如果有引數,判斷個數。這裡最多隻執行一個引數。也就是說在真正的dubbo呼叫的時候只能傳遞一個BO型別,具體的引數列表可以寫到BO中。因為如果有多個在進行json解析的時候是無法賦值到兩個引數物件中去的。
  • 之後進行呼叫,將呼叫返回的資料進行返回即可。

總結

通常來說這樣提供的HTTP介面再實際中用的不多,但是很方便除錯。

比如寫了一個dubbo的查詢介面,在測試環境或者是預釋出環境中就可以直接通過HTTP請求的方式進行簡單的測試,或者就是查詢資料。比在Java中寫單測來測試或查詢快的很多。

安裝

git clone https://github.com/crossoverJie/SSM-DUBBO-HTTP.git複製程式碼
cd SSM-DUBBO-HTTP複製程式碼
mvn clean複製程式碼
mvn install複製程式碼

使用

<dependency>
    <groupId>com.crossoverJie</groupId>
    <artifactId>SSM-HTTP-PROVIDER</artifactId>
    <version>1.0.0</version>
</dependency>複製程式碼

spring配置

    <!--dubbo服務暴露為http服務-->
    <bean class="com.crossoverJie.dubbo.http.conf.HttpProviderConf">
        <property name="usePackage">
            <list>
                   <!--需要暴露服務的介面包名,可多個-->
                <value>com.crossoverJie.api</value>
            </list>
        </property>
    </bean>
    <!--掃描暴露包-->
    <context:component-scan base-package="com.crossoverJie.dubbo.http"/>複製程式碼

外掛地址:github.com/crossoverJi…

專案地址:github.com/crossoverJi…

個人部落格地址:crossoverjie.top

GitHub地址:github.com/crossoverJi…

SSM(十三) 將dubbo暴露出HTTP服務
weixin

相關文章