文档章节

SSM(十三) 将dubbo暴露出HTTP服务

crossoverJie
 crossoverJie
发布于 2017/05/03 22:40
字数 1574
阅读 882
收藏 48

dubbo暴露为http服务.jpg

前言

通常来说一个dubbo服务都是对内给内部调用的,但也有可能一个服务就是需要提供给外部使用,并且还不能有使用语言的局限性。

比较标准的做法是对外的服务我们统一提供一个openAPI,这样的调用方需要按照标准提供相应的appID以及密钥来进行验签才能使用。这样固然是比较规范和安全,但复杂度也不亚于开发一个单独的系统了。

这里所讲到的没有那么复杂,就只是把一个不需要各种权限检验的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"/>

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

项目地址:https://github.com/crossoverJie/SSM.git

个人博客地址:http://crossoverjie.top

GitHub地址:https://github.com/crossoverJie

weixin

© 著作权归作者所有

crossoverJie

crossoverJie

粉丝 714
博文 97
码字总数 187484
作品 0
江北
后端工程师
私信 提问
加载中

评论(7)

everythingbest
everythingbest
推荐一个dubbo-postman工具,很方便~
https://www.jianshu.com/p/11048224be1a
霸气不外露的木木
霸气不外露的木木
谢谢哈
crossoverJie
crossoverJie 博主

引用来自“霸气不外露的木木”的评论

你好,你上传的那个图片,调用apiapi的工具是什么?自己做的吗?

@霸气不外露的木木 postman 调试神奇
霸气不外露的木木
霸气不外露的木木
你好,你上传的那个图片,调用apiapi的工具是什么?自己做的吗?
随风散忆
随风散忆
可以考虑基于Dubbo的泛化改进,无需引入dubbo接口类
crossoverJie
crossoverJie 博主

引用来自“魔幻之翼”的评论

没看懂这样写的好处.... 是让Controller 成为请求瓶颈?
好处文末有提到啊 可以方便在测试 预发布之类的环境调试
魔幻之翼
魔幻之翼
没看懂这样写的好处.... 是让Controller 成为请求瓶颈?
crossoverJie/SSM

使用Idea搭建的Maven项目,会不定期更新一些在实际开发中使用的技巧,没有复杂的业务流程,更不是XXXX系统,只有一些技术的分享 目录结构 ├── SSM-API // 通用API │ ├── src/main│ ├...

crossoverJie
2018/05/09
0
0
SSM(十三) 将dubbo暴露出HTTP服务

dubbo暴露为http服务.jpg 前言 通常来说一个服务都是对内给内部调用的,但也有可能一个服务就是需要提供给外部使用,并且还不能有使用语言的局限性。 比较标准的做法是对外的服务我们统一提供...

crossoverJie
2017/05/03
0
0
推荐几个自己写的Java后端相关的范例项目(转载)

http://wosyingjun.iteye.com/blog/2312553 这里推荐几个自己写的范例项目,主要采用SSM(Spring+SpringMVC+Mybatis)框架,分布式架构采用的是(dubbo+zookeeper)。范例项目的好处是简单易...

指尖的舞者
2016/09/27
415
0
基于 dubbo 的分布式架构

前言 现在越来越多的互联网公司还是将自己公司的项目进行服务化,这确实是今后项目开发的一个趋势,就这个点再凭借之前的 SSM 项目来让第一次接触的同学能快速上手。 浅谈分布式架构 分布式架...

Java小铺
2018/07/06
54
0
微服务架构下,解决数据一致性问题的实践

随着业务的快速发展,应用单体架构暴露出代码可维护性差、容错率低、测试难度大和敏捷交付能力差等诸多问题,微服务应运而生。微服务的诞生一方面解决了上述问题,但是另一方面却引入新的问题...

中间件小哥
02/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
687
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
39
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
25
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
74
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
昨天
72
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部