天天看點

SSM(十三) 将dubbo暴露出HTTP服務前言準備工作總結

SSM(十三) 将dubbo暴露出HTTP服務前言準備工作總結

前言

通常來說一個

dubbo

服務都是對内給内部調用的,但也有可能一個服務就是需要提供給外部使用,并且還不能有使用語言的局限性。

比較标準的做法是對外的服務我們統一提供一個

openAPI

,這樣的調用方需要按照标準提供相應的

appID

以及密鑰來進行驗簽才能使用。這樣固然是比較規範和安全,但複雜度也不亞于開發一個單獨的系統了。

這裡所講到的沒有那麼複雜,就隻是把一個不需要各種權限檢驗的

dubbo

服務對外提供為

HTTP

服務。

調用示例:

SSM(十三) 将dubbo暴露出HTTP服務前言準備工作總結

準備工作

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

  • 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;
    }
}

           

複制

(手機上看代碼體驗不好,建議到PC上檢視。)

先一步一步的看:

首先是定義了一個

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.UserInfoApi

getUserInfo

指派到

httpRequest

入參中。

判斷傳入的包是否是對外提供的。如下配置:

其中的

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"/>           

複制

插件位址:https://github.com/crossoverJie/SSM-DUBBO-HTTP

項目位址:https://github.com/crossoverJie/SSM.git

個人部落格位址:http://crossoverjie.top。

GitHub位址:https://github.com/crossoverJie。