• 【解决方案】如何使用 Http API 代替 OpenFeign 进行远程服务调用


    前言

    看到标题大家可能会有点疑惑吧:OpenFeign 不是挺好用的吗?尤其是微服务之间的远程调用,平时用的也挺习惯的,为啥要替换呢?

    背景和原因是这样的:

    1. 部门/团队在安全性上有所考虑,即尽可能地减少/消除引入外部依赖,尽量只使用自研依赖、apache、Spring等必须的开源依赖;
    2. 而 OpenFeign 的使用则是引入了 Spring Cloud 依赖(不在安全要求范围内),所以需要考虑替换;
    3. 为以后团队的项目上 Spring 6做铺垫,Spring 6 会有 Spring 内置的 Http Interface 发起远程服务调用。

    下面将从介绍 OpenFeign、常见的 Http API 以及重点介绍 Spring 自带的 RestTemplate Http 模板这3个方面展开。


    一、何为OpenFeign

    OpenFeign 是 Spring Cloud 在 Feign 的基础上支持了 SpringMVC 的注解,如 @RequesMapping 等,其底层默认使用的是 URLConnection 实现。

    OpenFeign 的 @FeignClient 注解可以解析 SpringMVC 的@RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

    1.1@FeignClient注解

    只要是使用 OpenFeign 那么这个注解是一定会使用到的,该注解的主要属性如下:

    • url:可以手动指定 @FeignClient 调用的远程服务地址,如果同时声明 url 和 name 则以 url 为准,此时 name 仅作为该 FeignClient 的名称而已;
    • name:指定当前 FeignClient 的名称,如果项目使用了 Ribbon,那么 name 属性会作为微服务的名称,用于服务的发现
    • value:实际上和 name 是用一个属性,因为这两个属性互相使用了别名,使用的时候两者选其一即可;
    • path:定义当前 FeignClient 的统一前缀,即表示所有调用的远程服务都会走这个 path 声明的 http 前缀;
    • configuration:Feign 的配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract 等;
    • fallback:定义容错的处理类,当调用远程接口失败或超时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。

    简单示例如下:

    @FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient", 
                 configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
    public interface SubmitTaskClient {
    
        /**
         * 调用远程接口实现,入参为 json 字符串
         * @param paramJsonStr
         * @param header
         * @return
         */
        @PostMapping
        String submitNormalTask(@RequestBody String paramJsonStr, @RequestHeader Map header);
    
        /**
         * 调用远程接口实现,入参为 map 的表单形式
         * @param map
         * @return
         */
        @PostMapping(value = "/task/create", headers = {"content-type=application/x-www-form-urlencoded"})
        String submitTransTask(Map map);
    
    }
    

    1.2注意事项

    在远程服务调用一般存在两种情况:

    1. 远程服务在注册中心

      如果远程服务的提供方已经注册到注册中心,那么 name 或者 value 的值为:注册中心的服务名称,且必须为所有客户端指定一个 name 或者 value。

      @FeignClient(name = "SubmitTaskService", configuration = OpenFeignFormConfig.class, fallback = HystrixFallbackConfig.class)
      
    2. 单独的远程 http 接口

      此处 name 的值为当前 feignClient 客户端的名称,指定的 url 则为远程服务的地址。

      @FeignClient(url = "https://xxx.abcdef.com", name = "SubmitTaskClient", configuration = OpenFeignFormConfig.class)
      

      以上两种方式都能正常进行远程服务调用。name 可以为注册中心的服务名称,同时有 url 属性时,name 就与注册中心服务名称无关。


    二、常见的Http API

    OpenFeign 本质上还是使用 http 请求完成服务的调用,其实使用以下的这些 Http API 经过适当的改造后,也可以达到效果。

    2.1Apache

    在后端领域,出现比较早而且使用仍然很广泛的 HTTP 客户端框架非 Apache HttpClien 莫属了,目前大量项目和公司仍在采用该框架。

    Apache HttpClient 有着不错的性能、丰富的功能以及强大的自定义实现等特色。但是随着技术的发展和设计理念的改变,Apache HttpClient 显的有些落伍了。

    个人认为其最不受欢迎的点主要在于 API 的设计过于臃肿,大量的配置需要手动声明,当见过了更多好的的 Http API 后你可能就会不太想继续用了。当然公司框架正在使用 Apache HttpClient 的情况下也无可厚非,虽然复杂点,但用还是可以用的。

    引入 pom 依赖:

            
            <dependency>
                <groupId>org.apache.httpcomponentsgroupId>
                <artifactId>httpclientartifactId>
                <version>4.5.13version>
            dependency>
    

    POST 请求示例如下:

        public String apacheHttpClientPost(String url, String params) throws Exception {
            CloseableHttpClient httpclient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/json");
            String charSet = "UTF-8";
            StringEntity entity = new StringEntity(params, charSet);
            httpPost.setEntity(entity);
            CloseableHttpResponse response = null;
            try {
                response = httpclient.execute(httpPost);
                StatusLine status = response.getStatusLine();
                int state = status.getStatusCode();
                if (state == 200) {
                    HttpEntity responseEntity = response.getEntity();
                    return EntityUtils.toString(responseEntity);
                }
            } finally {
                if (response != null) {
                    try {
                        response.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    httpclient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    

    2.2Okhttp

    OkHttp3 是 OkHttp 发展到版本3.0之后的名字。在 maven 中央仓库搜索 okhttp,可以看到 3.0 之后的版本统一称为 OkHttp3。

    OKHttp3 是一个当前主流的网络请求的开源框架,由 Square 公司开发,目标是用于替代 HttpUrlConnection 和 Apache HttpClient。

            
            <dependency>
                <groupId>com.squareup.okhttp3groupId>
                <artifactId>okhttpartifactId>
                <version>4.9.1version>
            dependency>
    

    POST 请求示例如下:

    	public String okHttpPostMethod(String url,String body,  OkHttpClient okHttpClient) throws IOException {
            MediaType JSON_TYPE = MediaType.parse("application/json");
            Request request = new Request.Builder()
                    .url(url).post(RequestBody.create(JSON_TYPE, body)).addHeader("Content-Type", "application/json")
                    .build();
            Response response = null;
            try {
                response = okHttpClient.newCall(request).execute();
            } catch (Exception e) {
                e.printStackTrace();
            }
            assert response != null;
            if (response.isSuccessful()) {
                return response.body() == null ? "" : response.body().string();
            }
            return null;
        }
    

    对于需要单独处理 POST、GET 等请求的情况来说,OkHttp3 是很适合的。

    但是对于一些通用请求,比如在一个通用方法的参数里只需要传入 Method 枚举(POST、GET 等)就可以实现对应类型的请求,Hutool 和 RestTemplate 可能更为合适。

    2.3Hutool

    Hutool 中的工具方法来自每个用户的精雕细琢,它涵盖了 Java 开发底层代码中的方方面面,是国内 Java 开发工具类库的集大成者,很多公司的很多项目都在用。

    其中 Hutool 的 http 部分是基于 HttpUrlConnection 的 Http 客户端封装,大致发起调用的步骤:首先构建一个http请求,包括请求的地址、请求方式、请求头、请求参数等信息,然后执行请求返回一个 http 响应类,最后通过这个相应类可以获取响应的主体、是否请求成功等信息。

    但遗憾的是,团队里也有比较明确的安全规定:不允许在项目中引入 Hutool 依赖包。

    引入 pom 依赖:

            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
                <version>5.8.8version>
            dependency>
    

    创建通用请求的示例如下:

        public String huToolMethod(String url, HttpMethod httpMethod, RequestBody body) {
            Map headers = new HashMap<>();
            headers.put(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf-8");
            // 创建通用请求, 可以涵盖所有常见的 HTTP 方法, 同时放入 url
            HttpRequest request = HttpUtil.createRequest(Method.valueOf(httpMethod.name()), url);
            // 放入请求的 header 和 body
            HttpResponse response = request.addHeaders(headers).body(JSON.toJSONString(body)).execute();
            return response.body();
        }
    

    三、RestTemplate

    RestTemplate 是 Spring 框架用来访问 RESTFUL 服务的客户端模板类,主要功能有:

    1、发起 HTTP 请求,包括 RESTful 风格的 GET,POST,PUT,DELETE 等常见方法;

    2、自动将响应结果映射为 Java 对象,不用手动解析 JSON 或 XML。

    3、自定义设置请求头、消息转码、Cookie 等功能。

    4、对不同的输入/输出类型提供对应的方法,如字符串、对象、多部分等。

    5、同时还支持远程调用,不受同源策略限制。

    引入 pom 依赖:

            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-webartifactId>
                <version>5.3.22version>
            dependency>
    

    配置类:

    @Configuration
    public class RestTemplateConfig {
    
        @Bean
        public RestTemplate restTemplate(ClientHttpRequestFactory factory){
            return new RestTemplate(factory);
        }
    
        @Bean
        public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(10000);
            factory.setReadTimeout(10000);
            return factory;
        }
    }
    

    3.1详解.execute()

    .execute() 是 RestTemplate 中最常见的关于执行 HTTP 请求的方法,它允许开发人员高度定制 HTTP 请求。

    先来给一段示例:

        public String restTemplateExecuteMethod(String url, String token, Object body, HttpMethodName method){
            HttpHeaders httpHeaders = new HttpHeaders();
            // headers:HttpHeaders 类型,包括所有头信息
            httpHeaders.add("Authorization", token);
            httpHeaders.add("Content-Type", "application/json;charset=utf-8");
            // body:请求体,可以是任何对象,也可以是 null
            HttpEntity httpEntity = new HttpEntity<>(body, httpHeaders);
            RequestCallback requestCallback = restTemplate.httpEntityCallback(httpEntity, Object.class);
            ResponseExtractor> responseExtractor = restTemplate.responseEntityExtractor(Object.class);
            // 发送请求,method.name() 表示传入的方法,包括 GET、POST、DELETE 等
            ResponseEntity entity = restTemplate.execute(url, HttpMethod.valueOf(method.name()), requestCallback, responseExtractor);
            // 直接返回 body
            Assert.notNull(entity, "返回体为空!");
            log.info("---返回的内容:{}---", JSON.toJSONString(entity.getBody()));
            return JSON.toJSONString(entity.getBody());
        }
    
    

    下面是一些对象的介绍:

    • HttpEntity 对象

      它主要有两个作用:

      1、表示 HTTP 请求:当表示 HTTP 请求时,HttpEntity 有两个主要组成部分:请求头和请求体。

      2、表示 HTTP 响应:当表示 HTTP 响应时,有三个部分:状态码、响应头和响应体。

      其中的参数:

      • headers:HttpHeaders 类型,包括所有头信息;
      • body:请求或响应体,可以是任何对象,也可以是null;
      • statusCode:HttpStatus 类型,只有在表示响应时才有效。
    • RequestCallback 对象

      RequestCallback 是 RestTemplate中用来定制HTTP请求的一个接口,可以设置请求头、请求体、查询字符串参数。

      Callback接口只有一个方法:

      void doWithRequest(ClientHttpRequest request) throws IOException
      

    四、文章小结

    文章的最后,我选择了 okhttp3 和 RestTemplate 来进行 OpenFeign 的替换工作:okhttp3 处理单个 POST/GET 等请求,使用.execute() 处理通用 HTTP 请求。

    那么如何使用 Http API 代替 OpenFeign 进行远程服务调用的分享到这里就结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

  • 相关阅读:
    【安卓开发】安卓布局控件
    《数据库》期末考试复习手写笔记-第11章 并发控制(锁)【10分】
    U盘格式化后 容量变小如何解决
    大二Web课程设计——张家界旅游网站设计与实现(HTML+CSS+JavaScript)
    【科普向】Jmeter 如何测试接口保姆式教程
    xtrabackup相关参数
    我的master节点是好的,现在是第一个node1节点利用master节点给出的加入的代码结果报错,如何解决?(标签-kubernetes)
    42、Flink 的table api与sql之Hive Catalog
    redis中使用pipeline批量执行命令,提升性能
    15 【严格模式】
  • 原文地址:https://www.cnblogs.com/CodeBlogMan/p/17979942