• Feign实现动态URL


    需求描述

    动态URL的需求场景:
    有一个异步服务S,它为其他业务(业务A,业务B...)提供异步服务接口,在这些异步接口中执行完指定逻辑之后需要回调相应业务方的接口。
    统一回调方法

    这在诸如风控审核,支付回调等场景中挺常见的。

    那么,这个回调业务方接口该怎么实现呢?
    首先,需要约定好回调这些业务方接口时的请求方法(通常为POST请求),请求参数格式(通常为JSON格式,方便扩展)和响应消息格式(通常为JSON格式)。

    具体调用业务方接口时有2种办法来实现:
    1.在服务S的每一个异步接口中都独立写一套回调的逻辑
    2.因为回调的方法类型和参数格式是约定好的,所以可以写一个统一的公共回调方法即可

    方法1显然不是最优选择,这样做会带来大量重复的代码逻辑,而且非常不利用后期维护和升级。
    方法2的实现更加灵活一些,便于扩展。

    如下将阐述如何使用Feign框架定义一个公共的回调方法。

    具体实现

    在Feign中能实现动态URL的基础是框架本身就支持,只需要在接口方法中包含一个java.net.URI参数,Feign就会将该参数值作为目标主机地址,详见Interface Annotations一节中的“Overriding the Request Line”部分。
    如下将分别阐述独立使用Feign和使用Spring Cloud OpenFeign实现定义统一的回调方法。

    使用Feign定义统一回调方法

    定义统一回调方法:

    public interface CallbackAPI {
        /**
         * 统一回调接口方法,请求消息体格式为JSON,响应消息体格式也为JSON
         * @param host 接口主机地址,如:http://localhost:8080,该参数是实现动态URL的关键
         * @param path 接口路径,如:/test/hello
         * @param queryMap 动态URL参数集合
         * @param body 请求消息体对象
         * @return
         */
        @RequestLine("POST {path}")
        @Headers({
            "Content-Type: application/json",
            "Accept: application/json"
        })
        Object callback(URI host, @Param("path") String path, @QueryMap Map<String, Object> queryMap, Subject body);
    
       /**
         * 统一回调接口方法,请求消息体格式为JSON,响应消息体格式也为JSON
         * @param uri 完整的请求路径地址,如:http://localhost:8080/test/hello
         * @param queryMap 动态URL参数集合
         * @param body 请求消息体对象
         * @return
         */
        @RequestLine("POST")
        @Headers({
            "Content-Type: application/json",
            "Accept: application/json"
        })
        Object callback(URI uri, @QueryMap Map<String, Object> queryMap, Subject body);
    }
    

    调用回调方法:

    CallbackAPI callbackAPI = Feign.builder()
            .encoder(new GsonEncoder())
            .decoder(new GsonDecoder())
            .logger(new Slf4jLogger())
            .logLevel(Logger.Level.FULL)
            .target(CallbackAPI.class, "EMPTY"); // 注意:这里的url参数不能为空字符串,但是可以设置为任意字符串值,在这里设置为“EMPTY”
    String uri = "http://localhost:8080";
    Map<String, Object> queryMap = new HashMap<>(0);
    queryMap.put("k", "v");
    Subject body = Subject.builder().id(10).build();
    // 请求主机地址与路径分开传递
    callbackAPI.callback(URI.create(uri), "/test/simple/post/json", queryMap, body);
    // 直接将完整请求完整路径作为uri类型参数
    callbackAPI.callback(URI.create("http://localhost:8080/test/simple/post/json"), queryMap, body);
    

    详细请求日志如下:

    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] ---> POST http://localhost:8080/test/simple/post/json?k=v HTTP/1.1
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Accept: application/json
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Content-Length: 14
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] Content-Type: application/json
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] 
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] {
      "id": 10
    }
    2022-02-14 15:32:13,118 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] ---> END HTTP (14-byte body)
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] <--- HTTP/1.1 200 (32ms)
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] connection: keep-alive
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] content-length: 9
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] content-type: application/json
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] date: Mon, 14 Feb 2022 07:32:13 GMT
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] keep-alive: timeout=60
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] 
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] {"id":10}
    2022-02-14 15:32:13,153 DEBUG [main] f.Logger [Slf4jLogger.java:72] [CallbackAPI#callback] <--- END HTTP (9-byte body)
    

    显然,动态设置目标主机和接口路径已经成功了。

    使用Spring Cloud Feign定义统一回调方法

    Spring Cloud Feign中实现定义统一回调接口方法可以直接使用注解进行标注,非常简洁。

    定义统一回调方法:

    // 注意:这里的url属性值不能为空字符串,但是可以设置为任意字符串值,在这里设置为“EMPTY”
    @FeignClient(value = "CallbackAPI", url = "EMPTY", configuration = CallbackConfiguration.class)
    public interface CallbackAPI {
        /**
         * 统一回调接口方法,请求消息体格式为JSON,响应消息体格式也为JSON
         * @param host 接口主机地址,如:http://localhost:8080
         * @param path 接口路径,如:/test/hello
         * @param queryMap 动态URL参数集合
         * @param body 请求消息体对象
         * @return
         */
        @RequestMapping(value = "{path}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
        Object callback(URI host,
                        @PathVariable("path") String path,
                        @SpringQueryMap Map<String, Object> queryMap,
                        @RequestBody Object body);
        /**
         * 统一回调接口方法,请求消息体格式为JSON,响应消息体格式也为JSON
         * @param uri 完整的请求路径地址,如:http://localhost:8080/test/hello
         * @param queryMap 动态URL参数集合
         * @param body 请求消息体对象
         * @return
         */
        @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
        Object callback(URI uri,
                        @SpringQueryMap Map<String, Object> queryMap,
                        @RequestBody Object body);
    }
    
    // 回调接口配置
    public class CallbackConfiguration {
        @Bean
        public Encoder feignEncoder() {
            return new GsonEncoder();
        }
    
        @Bean
        public Decoder feignDecoder() {
            return new GsonDecoder();
        }
    
        @Bean
        public Retryer feignRetryer() {
            return new Retryer.Default();
        }
    
        @Bean
        public Logger feignLogger() {
            return new Slf4jLogger();
        }
    
        @Bean
        public Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    }
    

    调用回调方法:

    @Autowired
    CallbackAPI callbackAPI;
    
    String uri = "http://localhost:8080";
    Map<String, Object> queryMap = new HashMap<>(0);
    queryMap.put("k", "v");
    Subject subject = Subject.builder().id(10).build();
    // 请求主机地址与路径分开传递
    this.callbackAPI.callback(URI.create(uri), "/test/simple/post/json", queryMap, subject);
    // 直接将完整请求完整路径作为uri类型参数
    this.callbackAPI.callback(URI.create("http://localhost:8080/test/simple/post/json"), queryMap, subject);
    

    详细请求日志如下:

    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] ---> POST http://localhost:8080/test/simple/post/json?k=v HTTP/1.1
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Accept: application/json
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Content-Length: 14
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] Content-Type: application/json
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] 
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] {
      "id": 10
    }
    2022-02-14 15:38:38.908 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] ---> END HTTP (14-byte body)
    2022-02-14 15:38:38.924 DEBUG 20184 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@539c48307 pairs: {POST /test/simple/post/json?k=v HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{User-Agent: Java/1.8.0_271}{Host: localhost:8080}{Connection: keep-alive}{Content-Length: 14}
    2022-02-14 15:38:38.945 DEBUG 20184 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@681e913c6 pairs: {null: HTTP/1.1 200}{Content-Type: application/json}{Content-Length: 9}{Date: Mon, 14 Feb 2022 07:38:38 GMT}{Keep-Alive: timeout=60}{Connection: keep-alive}
    2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] <--- HTTP/1.1 200 (37ms)
    2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] connection: keep-alive
    2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] content-length: 9
    2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] content-type: application/json
    2022-02-14 15:38:38.952 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] date: Mon, 14 Feb 2022 07:38:38 GMT
    2022-02-14 15:38:38.953 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] keep-alive: timeout=60
    2022-02-14 15:38:38.953 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] 
    2022-02-14 15:38:38.955 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] {"id":10}
    2022-02-14 15:38:38.955 DEBUG 20184 --- [           main] feign.Logger                             : [CallbackAPI#callback] <--- END HTTP (9-byte body)
    

    从日志详情看,在Spring Cloud Feign中同样实现动态URL的效果。

    总结

    在Feign中实现动态URL时:
    1.可以使用URI类型的参数作为请求目标主机地址或者完整请求地址
    2.如果URI类型的参数值仅为主机部分,请求路径部分可以使用表达式进行替换

    【参考】
    https://www.cnblogs.com/syui-terra/p/14386188.html Feign 动态URL 解决记录
    https://blog.csdn.net/kysmkj/article/details/89672952 Feign 访问远程api,动态指定url

  • 相关阅读:
    20169221 2016-2017-2 《移动平台应用开发实践》第十一周学习总结
    20169221 2016-2017-2 《移动平台应用与开发》第十周实验总结
    20169201 2016-2017-2 《网络攻防实践》实验三 缓冲区溢出实验
    20169218 2016-2017-2 《网络攻防实践》第九周作业
    2016-2017 《移动平台开发》实验三 敏捷开发与XP实践
    20169221 2016-2017-2 实验一 网络攻防环境的搭建与测试
    实验二《Java面向对象》实验报告
    20169221 2016-2017-2 《移动平台开发》第七周学习总结
    20169221 2016-2017-2 《网络攻防》第七周学习总结
    20169221 2016-2017-2 《移动平台开发》第六周学习总结
  • 原文地址:https://www.cnblogs.com/nuccch/p/15893833.html
Copyright © 2020-2023  润新知