Spring Cloud 之 Zuul网关集成Dubbo泛化调用(十八)

PS:源码已上传Github, 欢迎指教。https://github.com/shileishmily/spring-cloud-x.git

之前已经讲过zuul网关搭建和网关Filter使用,参考下面两篇文章。
 
本篇主要Zuul和Dubbo如何集成,因为笔者所在的公司既有Dubbo服务,又有SpringCloud服务。对外(公网)提供服务的时候,不可能把dubbo服务再改造成Http协议,一是成本大,二是风险高。所以我们采用Zuul网关实现SpringCloud和Dubbo服务分发。在Zuul网关之上再挂Nginx对外提供服务。
 
 
本篇演示离不开zookeeper,zkui,Dubbo Admin。关于这三个应用的安装请参考下方链接,本文不做重点介绍。
zookeeper,zkui安装参考:Zookeeper 注册中心安装
Dubbo Admin安装参考:https://github.com/apache/dubbo-admin
 
1、Dubbo接口定义
在x-demo-service-api模块新建一个HelloDubboService接口
package com.x.demo.api;

public interface HelloDubboService {
    void sayHello();

    String sayHi(String name);
}

2、创建一个名称为x-demo-dubbo-provider的模块

3、gradle依赖

dependencies {
    compile project(":x-demo-service-api")

    compile(group: 'org.apache.dubbo', name: 'dubbo-spring-boot-starter', version: '2.7.8') {
        exclude(module: 'slf4j-log4j12')
    }

    compile(group: 'org.apache.dubbo', name: 'dubbo-dependencies-zookeeper', version: '2.7.8') {
        exclude(module: 'slf4j-log4j12')
    }
}

4、bootstrap.yml配置文件

server:
  port: 6060

dubbo:
  application:
    name: x-demo-dubbo-provider
  registry:
    address: zookeeper://localhost:2181
    protocol: zookeeper
  protocol:
    name: dubbo
    port: 20880
  scan:
    base-packages: com.x.dubbo.provider.service
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
    enabled: false

5、创建HelloDubboService实现类,代码如下

这里我们采用注解声明式配置,实现类上加@DubboService

/**
 * @author Leo
 */
@Slf4j
@DubboService(version = "1.0.0", interfaceClass = HelloDubboService.class, methods = {
        @Method(name = "sayHello")
})
public class HelloDubboServiceImpl implements HelloDubboService {
    @Override
    public void sayHello() {
        log.info("Hello");
    }

    @Override
    public String sayHi(String name) {
        log.info("Hello: {}", name);

        return "Hello: " + name;
    }
}

6、创建启动类

注意必须加@EnableDubbo注解,否则服务无法注册到zookeeper。

/**
 * @author Leo
 */
@EnableDubbo
@SpringBootApplication
public class DubboProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboProviderApplication.class, args);
    }
}

到此dubbo服务提供者完成。下面开始改造spring-cloud-gateway模块

7、创建路由Filter

package com.x.gateway.zuul.filter;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.x.gateway.zuul.util.DubboCallbackUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.List;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ROUTE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER;

/**
 * Dubbo泛化调用
 *
 * @author Leo
 */
@Slf4j
@Component
public class DubboForwardFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return ROUTE_TYPE;
    }

    @Override
    public int filterOrder() {
        return SEND_RESPONSE_FILTER_ORDER - 2;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());

        if ("POST".equals(request.getMethod())) {
            LinkedHashMap param = null;
            try (InputStream inputStream = request.getInputStream()) {
                String body = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
                log.info("原始请求参数:{}", body);
                param = JSONObject.parseObject(body, LinkedHashMap.class, Feature.OrderedField);

                String interfaceName = (String) param.get("interfaceName");
                String method = (String) param.get("method");
                String version = (String) param.get("version");
                String address = (String) param.get("address");
                String json = param.get("param").toString();
                List<Object> paramList = JSONObject.parseArray(json);

                if (!StringUtils.isEmpty(interfaceName) && !StringUtils.isEmpty(method)) {

                    Object responseTxt = DubboCallbackUtil.invoke(interfaceName, method, paramList, address, version);
                    requestContext.setSendZuulResponse(true);
                    requestContext.setResponseStatusCode(HttpStatus.OK.value());
                    requestContext.setResponseBody((String) responseTxt);
                }
            } catch (IOException e) {
                log.error("dubbo泛化调动异常", e);
            }
        }

        return null;
    }
}

8、Dubbo泛化调用工具类

/**
 * @author Leo
 */
public class DubboCallbackUtil {

    private static Logger logger = LogManager.getLogger(DubboCallbackUtil.class);

    /**
     * 当前应用的信息
     */
    private static ApplicationConfig application = new ApplicationConfig();

    /**
     * 注册中心信息缓存
     */
    private static Map<String, RegistryConfig> registryConfigCache = new ConcurrentHashMap<>();

    /**
     * 各个业务方的ReferenceConfig缓存
     */
    private static Map<String, ReferenceConfig> referenceCache = new ConcurrentHashMap<>();

    static {
        application.setName("spring-cloud-gateway");
    }

    /**
     * 获取注册中心信息
     *
     * @param address zk注册地址
     * @param group   dubbo服务所在的组
     * @return
     */
    private static RegistryConfig getRegistryConfig(String address, String group, String version) {
        String key = address + "-" + group + "-" + version;
        RegistryConfig registryConfig = registryConfigCache.get(key);
        if (null == registryConfig) {
            registryConfig = new RegistryConfig();
            if (StringUtils.isNotEmpty(address)) {
                registryConfig.setAddress(address);
            }
            if (StringUtils.isNotEmpty(version)) {
                registryConfig.setVersion(version);
            }
            if (StringUtils.isNotEmpty(group)) {
                registryConfig.setGroup(group);
            }
            registryConfigCache.put(key, registryConfig);
        }
        return registryConfig;
    }

    private static ReferenceConfig getReferenceConfig(String interfaceName, String address,
                                                      String group, String version) {
        String referenceKey = interfaceName;

        ReferenceConfig referenceConfig = referenceCache.get(referenceKey);
        if (null == referenceConfig) {
            try {
                referenceConfig = new ReferenceConfig<>();
                referenceConfig.setApplication(application);
                referenceConfig.setRegistry(getRegistryConfig(address, group, version));
                Class interfaceClass = forName(interfaceName);
                referenceConfig.setInterface(interfaceClass);
                if (StringUtils.isNotEmpty(version)) {
                    referenceConfig.setVersion(version);
                }
                referenceConfig.setGeneric(true);
                //referenceConfig.setUrl("dubbo://10.1.50.167:20880/com.test.service.HelloService");
                referenceCache.put(referenceKey, referenceConfig);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return referenceConfig;
    }

    public static Object invoke(String interfaceName, String methodName, List<Object> paramList, String address, String version) {
        ReferenceConfig reference = getReferenceConfig(interfaceName, address, null, version);
        if (null != reference) {
            GenericService genericService = (GenericService) reference.get();
            if (genericService == null) {
                logger.debug("GenericService 不存在:{}", interfaceName);
                return null;
            }

            Object[] paramObject = null;
            if (!CollectionUtils.isEmpty(paramList)) {
                paramObject = new Object[paramList.size()];
                for (int i = 0; i < paramList.size(); i++) {
                    paramObject[i] = paramList.get(i);
                }
            }


            Object resultParam = genericService.$invoke(methodName, getMethodParamType(interfaceName, methodName), paramObject);
            return resultParam;
        }
        return null;
    }


    public static String[] getMethodParamType(String interfaceName, String methodName) {
        try {
            //创建类
            Class<?> class1 = Class.forName(interfaceName);
            //获取所有的公共的方法
            Method[] methods = class1.getMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] paramClassList = method.getParameterTypes();
                    String[] paramTypeList = new String[paramClassList.length];
                    int i = 0;
                    for (Class className : paramClassList) {
                        paramTypeList[i] = className.getTypeName();
                        i++;
                    }
                    return paramTypeList;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }
}

9、bootstrap.yml配置修改

注意标红的地方,加上这个配置,不管我们有多少个dubbo服务,都通过统一URLhttp://localhost:9001/dubbo请求到自己定路由DubboForwardFilter。

spring:
  application:
    name: spring-cloud-gateway

server:
  port: 9001

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/,http://localhost:8763/eureka/

management:
  endpoints:
    web:
      exposure:
        include: '*'

zuul:
  routes:
    x-demo-dubbo-provider: /dubbo/**

10、依次启动zk,x-demo-dubbo-provider,spring-cloud-gateway

通过PostMan访问Zuul网关,成功收到Dubbo服务返回:“Hello Leo”。

请求地址:http://localhost:9001/dubbo

请求参数:

{

    "interfaceName":"com.x.demo.api.HelloDubboService",
    "method":"sayHi",
    "version":"1.0.0",
    "param":["Leo"],
    "address":"zookeeper://localhost:2181"
}

当然如果要应用到生产,还需要细化打磨代码。但是有一点可以肯定,此方案在性能上是没有问题,因为实践出真知!!!

原文地址:https://www.cnblogs.com/shileibrave/p/14476139.html