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

dubbo暴露爲http服務.jpg

前言

一般來講一個dubbo服務都是對內給內部調用的,但也有可能一個服務就是須要提供給外部使用,而且還不能有使用語言的侷限性。javascript

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

這裏所講到的沒有那麼複雜,就只是把一個不須要各類權限檢驗的dubbo服務對外提供爲HTTP服務。git

調用示例:
github

dubbo-http封面.jpg

準備工做

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

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

其實思路很簡單,就是利用SpringMVC提供一個HTTP接口。
在該接口中經過入參進行反射找到具體的dubbo服務實現進行調用。json

HttpProviderConf配置類

首先須要定義一個HttpProviderConf類用於保存聲明須要對外提供服務的包名,畢竟咱們反射時須要用到一個類的全限定名:api

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的包名。app

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…

weixin
相關文章
相關標籤/搜索