我是靠谱客的博主 真实大树,最近开发中收集的这篇文章主要介绍Http服务与Dubbo服务相互转换的Spring Boot代理节点实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1.需求
目前有些项目已经接入了Spring cloud管理,节点间通信(包括老项目)通过eureka(非boot web项目的注eureka注册与发现参照前文)提供http通信,由于我们公司内部项目间交流要求通过dubbo做服务的暴露与消费,考虑新加一个boot节点用于http与dubbo之间的相互转换

2.主要思想,方案与问题

(1)主要思想:

<1>做一个Spring Boot节点用于http与dubbo服务的代理
<2>Http调用Dubbo:
将节点接入Spring Cloud管理,注册到eureka上提供SC方面的服务,节点依赖需要接入的项目jar包,并配置扫描等将Dubbo代理Bean交由Spring Bean管理,通过通用的Controller提供http服务(通用controller后面会说)
<3>Dubbo调用Http:
这个相对简单,只需要对dubbo暴露一个通用接口,调用方在调用的时候指定要调用的链接,入参等参数,做一层转发就可以

(2)方案与问题:

<1> 依赖io.dubbo.springboot:spring-boot-starter-dubbo:1.0.0这个jar中提供了很多接入SC的配置,但在开发完成后发现一个致命问题,就是好像不支持一个消费者配置多个生产者,查看源码也没有找到很好的解决方案(个人水平有限)…此方案相对简单,如果只针对一个生产者,可以考虑此方案
<2> 消费者配置生产者仍然采用原先的xml配置,项目依赖也只是原始的dubbo依赖,其余手动配置(通过@Value从配置中心拿)

3.核心代码
扯了那么多没用的,终于轮到代码了(这里只公开了核心代码)…

启动类:HttpDubboProxyApplication:

/**
 * Created by Kowalski on 2017/7/11
 * Updated by Kowalski on 2017/7/11
 */
@SpringBootApplication
@EnableEurekaClient
@ImportResource("classpath:dubbo-consumer.xml")
public class HttpDubboProxyApplication {
    public static void main(String... args) {
        // 程序启动入口
        SpringApplication.run(HttpDubboProxyApplication.class,args);
    }
}

项目启动时一些基本配置Bean(类xml)ConfigurationBean:

/**
 * Created by Kowalski on 2017/7/17
 * Updated by Kowalski on 2017/7/17
 * 配置bean
 */
@Configuration
public class ConfigurationBean {

    @Bean
    ProxySpringContextsUtil proxySpringContextsUtil(){
        return new ProxySpringContextsUtil();
    }

    @Bean
    AnnotationBean annotationBean(){
        AnnotationBean annotationBean = new AnnotationBean();
        /**启动扫描包(与正常dubbo扫描类似)*/
        annotationBean.setPackage("com.kowalski.proxy.service");
        return annotationBean;
    }

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        /**设置传输格式 避免中文乱码*/
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }
}

我们这里http请求通过restTemplate,也可以使用feign
上段代码的proxySpringContextsUtil:

package com.kowalski.proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Created by Kowalski on 2017/5/18
 * Updated by Kowalski on 2017/5/18
 */
public class ProxySpringContextsUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;    //Spring应用上下文环境

    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        ProxySpringContextsUtil.applicationContext = applicationContext;
    }

    /**
     * @return ApplicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 获取对象
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws BeansException
     */
    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }


    /**
     * 获取类型为requiredType的对象
     * 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
     * @param name       bean注册名
     * @param requiredType 返回对象类型
     * @return Object 返回requiredType类型对象
     * @throws BeansException
     */
    public static Object getBean(String name, Class<?> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
     * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * @param name
     * @return boolean
     * @throws NoSuchBeanDefinitionException
     */
    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws NoSuchBeanDefinitionException
     */
    public static Class<?> getType(String name) {
        return applicationContext.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     * @param name
     * @return
     * @throws NoSuchBeanDefinitionException
     */
    public static String[] getAliases(String name) {
        return applicationContext.getAliases(name);
    }
}

此工具类主要用于获取Spring管理的Bean
http调用dubbo服务通用Controller DubboProxyController :

package com.kowalski.proxy;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.weimob.proxy.common.ProxyErrorResponse;
import com.weimob.proxy.common.ProxyErrorReturnEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by Kowalski on 2017/5/18
 * Updated by Kowalski on 2017/5/18
 *
 * 使用规则:
 * 1.被调用方提供的是单个对象类型入参
 * 2.参数数量必须等于1(暂不支持无参与多参)
 * 3.不支持泛型入参
 */
@Slf4j
@RestController
public class DubboProxyController {

    public static final ObjectMapper objectMapper = new ObjectMapper();
    static {
        /**忽略unknow属性*/
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    private final ConcurrentMap<String, Method> methods = new ConcurrentHashMap<>();

    @RequestMapping("/proxy/{instanceName}/{methodName}")
    public Object runMethod(@PathVariable String instanceName,
                            @PathVariable String methodName,
                            HttpServletRequest request) {

        Object bean;
        try {
            /**从bean factory获取bean实例*/
            bean = ProxySpringContextsUtil.getBean(instanceName);
        }catch (Exception e) {
            log.error("未找到对应实例, instanceName:{} e:{}", instanceName, e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_INSTANCE.getReturnCode(),
                    String.format("未找到对应实例,instanceName:%s", instanceName));
        }
        /**从本地缓存拿取缓存方法*/
        Method methodToDo = methods.get(instanceName + methodName);

        /**如果缓存中没有 则根据方法名从实例中拿*/
        if (methodToDo == null) {
            Method[] declaredMethods;
            try {
                declaredMethods = bean.getClass().getDeclaredMethods();
            }catch (Exception e) {
                log.error("获取接口定义方法失败, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
                return new ProxyErrorResponse(ProxyErrorReturnEnum.ERROR_GET_DECLARED_METHODS.getReturnCode(),
                        String.format("获取接口定义方法失败,instanceName:%s methodName:%s", instanceName, methodName));
            }
            /**根据方法名拿方法*/
            for (Method method : declaredMethods) {
                if (methodName.equals(method.getName())) {
                    methodToDo = method;
                    methods.putIfAbsent(instanceName + methodName, methodToDo);
                    break;
                }
            }
        }

        if (methodToDo == null) {
            return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_METHOD.getReturnCode(),
                    String.format("未找到对应方法,instanceName:%s methodName:%s", instanceName, methodName));
        }
        /**获取参数类型*/
        Type[] types = methodToDo.getParameterTypes();

        /**暂不支持无参方法*/
        if(types == null || types.length == 0) {
            return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_PARAM_TYPE.getReturnCode(),
                    String.format("未获取到方法参数, instanceName:%s methodName:%s", instanceName, methodName));
        }
        /**暂不支持参数数量大于1*/
        if(types.length > 1) {
            return new ProxyErrorResponse(ProxyErrorReturnEnum.TOO_MANY_PARAM_ARGS.getReturnCode(),
                    String.format("方法参数过多, instanceName:%s methodName:%s", instanceName, methodName));
        }

        /**根据request请求内容 转换成对应形式的参数*/
        ServletInputStream inputStream;
        try {
            inputStream = request.getInputStream();
        }catch (Exception e){
            log.error("获取输入流失败, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.GET_INPUT_STREAM_FAILED.getReturnCode(),
                    String.format("获取输入流失败, instanceName:%s methodName:%s", instanceName, methodName));
        }

        /**获取入参类型*/
        TypeFactory tf = objectMapper.getTypeFactory();
        JavaType javaType = tf.constructType(types[0]);
        /**将输入流转化成对应类型的参数*/
        Object param;
        try {
            param = objectMapper.readValue(inputStream, javaType);
        }catch (Exception e){
            log.error("输入流转化入参失败, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.INPUT_STREAM_EXCHANGE_FAILED.getReturnCode(),
                    String.format("输入流转化入参失败, instanceName:%s methodName:%s", instanceName, methodName));
        }

        /**执行方法*/
        Object result;
        try {
            result = methodToDo.invoke(bean, param);
        }catch (Exception e){
            log.error("方法执行错误, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.METHOD_INVOKE_ERROR.getReturnCode(),
                    String.format("方法执行错误, instanceName:%s methodName:%s", instanceName, methodName));
        }
        /**成功返回*/
        return result;
    }
}

由于已经将dubbo的代理Bean交由Spring Bean管理,因此通过ProxySpringContextsUtil 拿到代理,通过方法名拿到方法,通过方法拿到入参类型,再将入参转化成对应类型的参数invoke(之前有考虑过不管参数类型直接交给dubbo代理类去invoke,但好像必须要制定类型的入参,不然报错)

Dubbo调用Http的通用Service HttpProviderProxyService :

package com.kowalski.proxy.service;

/**
 * Created by Kowalski on 2017/7/17
 * Updated by Kowalski on 2017/7/17
 * http代理实现类
 */
public interface HttpProviderProxyService {

    /**
     * 处理dubbo调用http代理请求
     * @param request
     * @return
     */
    Object httpProxyHandle(HttpProxyRequest request);
}

Service实现 HttpProviderProxyServiceImpl:

package com.kowalski.proxy.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.kowalski.proxy.Enum.HttpProxyReqTypeEnum;
import com.kowalski.proxy.common.ProxyErrorResponse;
import com.kowalski.proxy.common.ProxyErrorReturnEnum;
import com.kowalski.proxy.service.HttpProviderProxyService;
import com.kowalski.proxy.service.HttpProxyRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;

/**
 * Created by Kowalski on 2017/7/17
 * Updated by Kowalski on 2017/7/17
 */
@Service
@Slf4j
public class HttpProviderProxyServiceImpl implements HttpProviderProxyService{

    @Value("${zuul.internal.url}")
    String zuuInternalUrl;

    @Autowired
    RestTemplate restTemplate;

    /**
     * 处理dubbo调用http代理请求
     * @param request
     * @return
     */
    @Override
    public Object httpProxyHandle(HttpProxyRequest request) {

        /**入参校验*/
        if(request == null){
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_NULL);
        }

        if(request.getReqType() == null){
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_TYPE_NULL);
        }

        if(StringUtils.isEmpty(request.getProxyUrl())){
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_URL_NULL);
        }

        if(request.getRequest() == null){
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_NULL);
        }
        /**根据不同入参类型处理不同请求*/
        switch (HttpProxyReqTypeEnum.getEnumByCode(request.getReqType())){
            case INTERNAL_ZUUL_COMMON:
                return internalZuulProxy(request);
            case INTERNAL_ZUUL_CUSTOMIZED:
                return internalZuulProxy(request);
            case OUTSIDE_FULL_URL:
                return outsideFullProxy(request);
            default:
                return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_TYPE_UNDEFIND);
        }
    }

    /**处理经由内网网关的代理请求*/
    private Object internalZuulProxy(HttpProxyRequest request){

        String url = zuuInternalUrl + request.getProxyUrl();
        Object result;
        try {
            result =  restTemplate.postForObject(url, request.getRequest(), Object.class);
        }catch (Exception e){
            log.error("HttpProviderProxyServiceImpl internalZuulProxy failed: reqType:{},url:{}, e:{}",
                    request.getReqType(), url, e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_GETRETURN_FAILED);
        }

        return result;
    }
    /**处理全路径的代理请求*/
    private Object outsideFullProxy(HttpProxyRequest request){
        Object result;
        try {
            result =  restTemplate.postForObject(request.getProxyUrl(), request.getRequest(), Object.class);
        }catch (Exception e){
            log.error("HttpProviderProxyServiceImpl internalZuulProxy failed: reqType:{},proxyUrl:{}, e:{}",
                    request.getReqType(), request.getProxyUrl(), e);
            return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_GETRETURN_FAILED);
        }

        return result;
    }
}

这里要注意一下这里的@Service注解,该注解采用的是dubbo的@Service注解而不是Spring的

通用请求 HttpProxyRequest :

package com.kowalski.proxy.service;

import lombok.Data;

/**
 * Created by Kowalski on 2017/7/17
 * Updated by Kowalski on 2017/7/17
 * 代理Http
 * 备注:只接受单个非泛型对象入参
 */
@Data
public class HttpProxyRequest {

    /**请求类型 @see HttpProxyReqTypeEnum*/
    private Integer reqType;

    /**reqType->0:根据内网网关地址请求定制controller requestMapping地址
     * reqType->1:根据内网网关地址请求commonController {serviceId}/{instanceName}/{methodName}
     * reqType->2:请求proxyUrl地址*/
    private String proxyUrl;

    /**请求request*/
    private Object request;
}

配置文件:dubbo-consumer.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:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
    <dubbo:application name="proxy" />
    <dubbo:consumer timeout="1800000" retries="0" />
    <dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.AA.address}" id="A" />
    <!--<dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.BB.address}" id="B" />-->
    <dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.C.address}" id="C"/>

    <dubbo:reference id="aaFacade" registry="A"
                     interface="com.kowalski.facade.AaFacade" check="false"/>

    <!--<dubbo:reference id="bbFacade" interface="com.kowalski.facade.BbFacade"-->
                     <!--check="false" registry="B"/>-->
    <dubbo:reference id="ccFacade" registry="C"
                     interface="com.kowalski.facade.CcFacade" check = "false"/>
</beans>

这里的${dubbo.consumer.zookeeper.AA.address}上产者注册到的zk地址可以直接在application.yml中配置

其余枚举类等:
ProxyErrorResponse:

package com.kowalski.proxy.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * Created by Kowalski on 2017/7/4
 * Updated by Kowalski on 2017/7/4
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProxyErrorResponse implements Serializable{

    private static final long serialVersionUID = -8379940261456006476L;
    private long code;

    private long status;

    private String message;

    public ProxyErrorResponse(long codeOrStatus, String message) {
        this.code = codeOrStatus;
        this.status = codeOrStatus;
        this.message = message;
    }

    public ProxyErrorResponse(ProxyErrorReturnEnum errorReturnEnum) {
        this.code = errorReturnEnum.getReturnCode();
        this.status = errorReturnEnum.getReturnCode();
        this.message = errorReturnEnum.getDiscribe();
    }
}

ProxyErrorReturnEnum:

package com.kowalski.proxy.common;

/**
 * Created by Kowalski on 2017/6/26
 * Updated by Kowalski on 2017/6/26
 */
public enum ProxyErrorReturnEnum {

    SUCCESS                     (0, "SUCCESS"),
    METHOD_INVOKE_ERROR         (-1, "方法执行错误"),
    NO_METHOD                   (-2, "未找到对应方法"),
    NO_INSTANCE                 (-3, "未找到对应实例"),
    ERROR_GET_DECLARED_METHODS  (-4, "获取接口定义方法失败"),
    NO_INSTANCE_BY_CLASS_NAME   (-5, "根据全路径获取实例失败"),
    NO_PARAM_TYPE               (-6, "未获取到方法参数"),
    TOO_MANY_PARAM_ARGS         (-7, "方法参数过多"),
    GET_INPUT_STREAM_FAILED     (-8, "获取输入流失败"),
    INPUT_STREAM_EXCHANGE_FAILED(-9, "输入流转化入参失败"),


    RETURN_JSON_TO_MY_FAILED    (-10, "返回结果解析错误"),
    SYSTEM_ERROR                (-11, "系统错误"),
    REQUEST_FAILED              (-12, "请求失败"),

    /**http代理错误*/
    PROXY_REQUEST_TYPE_NULL     (-13, "代理http类型不能为空"),
    PROXY_URL_NULL              (-14, "代理地址不能为空"),
    PROXY_REQUEST_ARGS_NULL     (-15, "请求入参为空"),
    PROXY_REQUEST_TYPE_UNDEFIND (-16, "代理http类型非法"),
    PROXY_GETRETURN_FAILED      (-17, "请求失败"),
    PROXY_REQUEST_NULL          (-18, "请求不能为空");

    private long returnCode;

    private String discribe;

    ProxyErrorReturnEnum(int returnCode, String discribe) {
        this.returnCode = returnCode;
        this.discribe = discribe;
    }

    public long getReturnCode() {
        return returnCode;
    }

    public void setReturnCode(long returnCode) {
        this.returnCode = returnCode;
    }

    public String getDiscribe() {
        return discribe;
    }

    public void setDiscribe(String discribe) {
        this.discribe = discribe;
    }
}

HttpProxyReqTypeEnum :

package com.kowalski.proxy.Enum;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Kowalski on 2017/7/17
 * Updated by Kowalski on 2017/7/17
 * 传输类型枚举类
 */
public enum HttpProxyReqTypeEnum {

    /**请求走内网网关 不经由外网复杂验证 定制controller处理*/
    INTERNAL_ZUUL_CUSTOMIZED(0, "内网网关定制"),
    /**请求走内网网关 不经由外网复杂验证 通用controller处理(instanceName methodName request)*/
    INTERNAL_ZUUL_COMMON(1, "内网网关通用"),
    /**全路径处理 不走网关(或者直接配置网关全路径)*/
    OUTSIDE_FULL_URL(2, "全路径");

    private int code;
    private String description;

    HttpProxyReqTypeEnum(int code, String description) {
        this.code = code;
        this.description = description;
    }


    private static final Map<Integer, HttpProxyReqTypeEnum> map = new HashMap<Integer, HttpProxyReqTypeEnum>();
    static {
        for (HttpProxyReqTypeEnum enums : HttpProxyReqTypeEnum.values()) {
            map.put(enums.getCode(), enums);
        }
    }

    public static HttpProxyReqTypeEnum getEnumByCode(int code){
        return map.get(code);
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

至此结束~有更好方案的小伙伴欢迎交流~~

最后

以上就是真实大树为你收集整理的Http服务与Dubbo服务相互转换的Spring Boot代理节点实现的全部内容,希望文章能够帮你解决Http服务与Dubbo服务相互转换的Spring Boot代理节点实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部