广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Feign 请求动态URL方式
  • 849
分享到

Feign 请求动态URL方式

2024-04-02 19:04:59 849人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录Feign 请求动态URL注意事项Feign重写URL以及RequestMapping场景效果展示整体思路实现Feign 请求动态URL 注意事项 FeignClient 中不要

Feign 请求动态URL

注意事项

  • FeignClient 中不要写url, 使用 @RequestLine修饰方法
  • 调用地方必须引入 FeignClientConfiguration, 必须有Decoder, Encoder
  • 调用类必须以构建函数(Constructor) 的方式注入 FeignClient 类
  • 传入URL作为参数;

代码如下:

FeignClient类:

@CompileStatic
@FeignClient(name = "xxxxClient")
public interface XxxFeignClient {
    @RequestLine("POST")
    ResponseDto notifySomething(URI baseUri, ApproveNotifyDto notifyDto);
    
    @RequestLine("GET")
    ResponseDto getSomething(URI baseUri, @QueryMap Map<String, String> queryMap)
  
}

ClientCaller类:

@CompileStatic
@Slf4j
@Component
@Import(FeignClientsConfiguration.class)
public class CallerService {
    private XxxFeignClient xxxFeignClient
    @Autowired
    public CallerService(Decoder decoder, Encoder encoder) {
        xxxFeignClient = Feign.builder()
                //.client(client)
                .encoder(encoder)
                .decoder(decoder)
                .target(Target.EmptyTarget.create(XxxFeignClient.class))
    }
    public ResponseDto notifySomething(String url, XxxxDto dto) {
        return xxxFeignClient.notifySomething(URI.create(url), dto)
    }
    
    public String getSomething(String url, String userId) {
        return xxxFeignClient.getSomething(URI.create(url), ["userId": userId])
    }
}

Feign重写URL以及RequestMapping

背景

由于项目采用的是 消费层 + api层(FeignClient) +服务层 的方式。 

导致原项目有太多代码冗余复制的地方。例如FeignClient上要写@RequestMapping,同时要写请求路径,消费层还要写上RequestBody等等,比较麻烦。遂改进了一下方案,简化日常代码开发的工作量

场景

项目的架构微服务架构,SpringBootSpringCloud均为原生版本。

效果展示

feign层无需写RequestMapping以及RequestBody或者RequestParam等

public interface UserDemoApi {
    
    ApiResponse<UserDemo> add(UserDemo userDemo);
    
    ApiResponse<UserDemo> findByUserIdAndUsername(String userId,String username);
    
    ApiResponse<PageResult<UserDemo>> findByCondition(UserDemoReqVo reqVo);
}
@FeignClient(value = "${api.feign.method.value}",fallback = UserDemoFeignApiFallbackImpl.class)
public interface UserDemoFeignApi extends UserDemoApi {
}

整体思路

  • 首先要拆成两部分处理,一部分是服务端层,另一部分是客户端层
  • 服务端层:主要做的事情就是注册RequestMapping.由于我们的api上是没有写任何注解的,所以我们需要在项目启动的时候把api上的方法都注册上去。另外一个要做的就是,由于我们不写RequestBody,所以要做参数解析配置
  • 客户端层:主要做的事情是,项目启动的时候,feign会扫描api上的方法,在它扫描的同时,直接把url定下来存入RequestTemplate中。这样即使后续feign在执行apply没有路径时也不影响feign的正常请求。

实现

Feign的服务端层

1. 继承RequestMappingHandlerMapping,重写getMappingFORMethod

  • METHOD_MAPPING 为自定义的路径(即RequestMapping的value值)
  • 在服务层的实现类中,打上自定义注解@CustomerAnnotation,只有有该注解的实现类才重写RequestMapping。
  • 自定义的路径采用的是 类名+方法名

重写的内容

public class CustomerRequestMapping extends RequestMappingHandlerMapping {
    private final static String METHOD_MAPPING = "/remote/%s/%s";
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
        RequestMappingInfo requestMappingInfo = super.getMappingForMethod(method,handlerType);
        if(requestMappingInfo!=null){
            if(handlerType.isAnnotationPresent(CustomerAnnotation.class)){
                if(requestMappingInfo.getPatternsCondition().getPatterns().isEmpty()||
                        requestMappingInfo.getPatternsCondition().getPatterns().contains("")){
                    String[] path = getMethodPath(method, handlerType);
                    requestMappingInfo = RequestMappingInfo.paths(path).build().combine(requestMappingInfo);
                }
            }
        }else{
            if(handlerType.isAnnotationPresent(CustomerAnnotation.class)){
                String[] path = getMethodPath(method, handlerType);
                requestMappingInfo = RequestMappingInfo.paths(path).methods(RequestMethod.POST).build();
            }
        }
        return requestMappingInfo;
    }
    private String[] getMethodPath(Method method, Class<?> handlerType) {
        Class<?>[] interfaces = handlerType.getInterfaces();
        String methodClassName = interfaces[0].getSimpleName();
        methodClassName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, methodClassName);
        String[] path = {String.format(METHOD_MAPPING,methodClassName, method.getName())};
        return path;
    }

覆盖RequestMappingHandlerMapping

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class VersionControlWEBmvcConfiguration implements WebMvcReGIStrations {
    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new CustomerRequestMapping();
    }
}

2. 服务层Controller

  • 实现api接口(即Feign层的接口)
  • 打上自定义的标识注解@CustomerAnnotation
@RestController
@CustomerAnnotation
@RequestMapping
public class UserDemoController implements UserDemoFeignApi {
    private final static Logger logger = LoggerFactory.getLogger(UserDemoController.class);
    @Resource
    private UserDemoService userDemoService;
    @Override
    public ApiResponse<UserDemo> add(UserDemo userDemo) {
        logger.info("request data:<{}>",userDemo);
        return userDemoService.add(userDemo);
    }
    @Override
    public ApiResponse<UserDemo> findByUserIdAndUsername(String userId,String username) {
        logger.info("request data:<{}>",userId+":"+username);
        return ApiResponse.success(new UserDemo());
    }
    @Override
    public ApiResponse<PageResult<UserDemo>> findByCondition(UserDemoReqVo reqVo) {
        logger.info("request data:<{}>",reqVo);
        return userDemoService.findByCondition(reqVo);
    }
}

自此,Fiegn的服务端的配置已经配置完毕。现在我们来配置Feign的客户端

Feign的客户端配置

重写Feign的上下文SpringMvcContract

核心代码为重写processAnnotationOnClass的内容。

  • 从MethodMetadata 对象中获取到RequestTemplate,后续的所有操作都是针对于这个
  • 获取到类名以及方法名,作为RequestTemplate对象中url的值,此处应与服务层的配置的URL路径一致
  • 默认请求方式都为POST

特殊情况处理:在正常情况下,多参数且没有@RequestParams参数注解的情况下,Feign会直接抛异常且终止启动。所以需要对多参数做额外处理

  • 判断当前方法的参数数量,如果不超过2个不做任何处理
  • 对于超过2个参数的方法,需要对其做限制。首先,方法必须满足命名规范,即类似findByUserIdAndUsername。以By为起始,And作为连接。
  • 截取并获取参数名称
  • 将名称按顺序存入RequestTemplate对象中的querie属性中。同时,要记得MethodMetadata 对象中的indexToName也需要存入信息。Map<Integer,Collection> map,key为参数的位置(从0开始),value为参数的名称
@Component
public class CustomerContact extends springMVCContract {
    private final static Logger logger = LoggerFactory.getLogger(CustomerContact.class);
    private final static String METHOD_PATTERN_BY = "By";
    private final static String METHOD_PATTERN_AND = "And";
    private final Map<String, Method> processedMethods = new HashMap<>();
    private final static String METHOD_MAPPING = "/remote/%s/%s";
    private Map<String, Integer> parameterIndexMap = new ConcurrentHashMap<>(100);
    public CustomerContact() {
        this(Collections.emptyList());
    }
    public CustomerContact(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
        this(annotatedParameterProcessors,new DefaultConversionService());
    }
    public CustomerContact(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
        super(annotatedParameterProcessors, conversionService);
    }
    
    @Override
    protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
        RequestTemplate template = data.template();
        String configKey = data.configKey();
        if(StringUtils.isBlank(template.url())){
            template.append(getTemplatePath(configKey));
            template.method(RequestMethod.POST.name());
        }
        // 构造查询条件
        templateQuery(template,data);
        super.processAnnotationOnClass(data, clz);
    }
    
    private void templateQuery(RequestTemplate template,MethodMetadata data){
        try{
            String configKey = data.configKey();
            if(manyParameters(data.configKey())){
                Method method = processedMethods.get(configKey);
                String methodName = method.getName();
                String key = getTemplatePath(configKey);
                Integer parameterIndex = 0;
                if(parameterIndexMap.containsKey(key)){
                    parameterIndexMap.put(key,parameterIndex++);
                }else{
                    parameterIndexMap.put(key,parameterIndex);
                }
                int index = methodName.indexOf(METHOD_PATTERN_BY);
                if(index>=0){
                    String[] parametersName = methodName.substring(index+METHOD_PATTERN_BY.length()).split(METHOD_PATTERN_AND);
                    String parameterName = parametersName[parameterIndex];
                    String caseName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, parameterName);
                    Collection<String> param = addTemplatedParam(template.queries().get(parameterName), caseName);
                    template.query(caseName,param);
                    setNameParam(data,caseName,parameterIndex);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            logger.error("template construct query failed:<{}>",e.getMessage());
        }
    }
    
    private String getTemplatePath(String configKey) {
        Method method = processedMethods.get(configKey);
        int first = configKey.indexOf("#");
        String apiName = configKey.substring(0,first);
        String methodClassName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, apiName);
        return String.format(METHOD_MAPPING,methodClassName,method.getName());
    }
    @Override
    public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
        this.processedMethods.put(Feign.configKey(targetType, method), method);
        return super.parseAndValidateMetadata(targetType,method);
    }
    @Override
    protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
        if(manyParameters(data.configKey())){
            return true;
        }
        return super.processAnnotationsOnParameter(data,annotations,paramIndex);
    }
    private void setNameParam(MethodMetadata data, String name, int i) {
        Collection<String>
                names =
                data.indexToName().containsKey(i) ? data.indexToName().get(i) : new ArrayList<String>();
        names.add(name);
        data.indexToName().put(i, names);
    }
    
    private boolean manyParameters(String configKey){
        Method method = processedMethods.get(configKey);
        return method.getParameterTypes().length > 1;
    }

最后还有一处修改

由于我们在方法上没有写上RequestBody注解,所以此处需要进行额外的处理

  • 只针对于带有FeignClient的实现类才做特殊处理
  • 如果入参为非自定义对象,即为基本数据类型,则直接返回即可
  • 自定义对象,JSON转换后再返回
@Configuration
public class CustomerArgumentResolvers implements HandlerMethodArgumentResolver {
    // 基本数据类型
    private static final Class[] BASE_TYPE = new Class[]{String.class,int.class,Integer.class,boolean.class,Boolean.class, MultipartFile.class};
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //springcloud的接口入参没有写@RequestBody,并且是自定义类型对象 也按jsON解析
        if (AnnotatedElementUtils.hasAnnotation(parameter.getContaininGClass(), FeignClient.class)) {
            if(parameter.getExecutable().getParameters().length<=1){
                return true;
            }
        }
        return false;
    }
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        final Type type = parameter.getGenericParameterType();
        String parameters = getParameters(nativeWebRequest);
        if(applyType(type)){
            return parameters;
        }else {
            return JSON.parseObject(parameters,type);
        }
    }
    private String getParameters(NativeWebRequest nativeWebRequest) throws Exception{
        httpservletRequest servletRequest = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        String jsonBody = "";
        if(servletRequest!=null){
            ServletInputStream inputStream = servletRequest.getInputStream();
            jsonBody =  IOUtils.toString(inputStream);
        }
        return jsonBody;
    }
    private boolean applyType(Type type){
        for (Class classType : BASE_TYPE) {
            if(type.equals(classType)){
                return true;
            }
        }
        return false;
    }
}
@Configuration
public class CustomerConfigAdapter implements WebMvcConfigurer {
    @Resource
    private CustomerArgumentResolvers customerArgumentResolvers;
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(customerArgumentResolvers);
    }
}

以上就是配置的所有内容,整体代码量很少。

但可能需要读下源码才能理解 

这些仅为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: Feign 请求动态URL方式

本文链接: https://www.lsjlt.com/news/153306.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Feign 请求动态URL方式
    目录Feign 请求动态URL注意事项Feign重写URL以及RequestMapping场景效果展示整体思路实现Feign 请求动态URL 注意事项 FeignClient 中不要...
    99+
    2022-11-13
  • SpringBoot feign动态设置数据源(https请求)
    目录1、背景2、代码2.2、 编写请求方法2.3、发送请求总结:1、背景 在使用springboot-feign/spingcloud-feign时,需要在启动springboot的...
    99+
    2022-11-12
  • Spring Cloud Feign怎么实现动态URL
    这篇“Spring Cloud Feign怎么实现动态URL”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇...
    99+
    2023-06-29
  • Feign的请求和响应日志方式是什么
    这篇文章主要介绍了Feign的请求和响应日志方式是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Feign的请求和响应日志方式是什么文章都会有所收获,下面我们一起来看看吧。1、项目里定义FeignClien...
    99+
    2023-07-02
  • 全局记录Feign的请求和响应日志方式
    目录1、项目里定义FeignClient接口2、单个FeignClient接口开启日志3、所有FeignClient接口 开启日志3.1、修改FeignConfiguration3....
    99+
    2022-11-13
  • 使用Feign消费服务时POST/GET请求方式详解
    声明:本结论基于Spring Cloud Dalston.RC1、Spring Boot1.5.2.RELEASE。 总体说明 feign消费服务时,以GET方式请求的条件: 如果想...
    99+
    2022-11-12
  • Feign如何实现第三方的HTTP请求
    目录Feign调用的简单实现1. 默认模式,不使用配置类,作用于服务内部调用而非三方请求接口2.自定义配置类3.自定义配置类法2 4. @FeignClient参数说明5....
    99+
    2022-11-13
    Feign HTTP请求 第三方HTTP请求 Feign第三方HTTP请求
  • 使用Feign调用时添加验证信息token到请求头方式
    目录Feign调用添加验证信息token到请求头1、这是最简单的一个方法2、这个方法是网上大多数人的用法3、第三种方法就是大神的方法了Feign中增加请求头最近遇到项目在调用Feig...
    99+
    2022-11-13
  • feign调用实现url和接口路径的动态配置化
    做接口开发请求第三方服务的接口,大概率会用feign做请求,而feign也是最常用的一种rpc框架; 这里主要是说明在进行feign请求的时候,第三方服务的url和接口也是可以通过读取配置文件的配置,来进行请求的; 至于为什么要把接口和ur...
    99+
    2023-09-06
    java 后端
  • vue3-HTTP请求方式
    目录vue3-HTTP请求jsonp原理 结合node.jsGet Post请求vue3-HTTP请求发出后,判断哪里出问题了在请求后添加vue3-HTTP请求 jsonp原理 结合...
    99+
    2022-11-13
  • Android 网络请求方式
    前言 最近需要将Android 项目接入物联网公司提供的接口,所以顺便给大家分享一下Android中我们常用的网络请求吧!提醒大家一下,我们遇到接口需求,一定要先在Postman上测试接口是否正确,然后再去项目上写程序来请求接口;否则,请求...
    99+
    2023-09-21
    android
  • php怎么获取数据请求方的url地址
    你可以使用`$_SERVER['HTTP_REFERER']`变量来获取数据请求方的URL地址。该变量包含了发送请求的页面的URL地...
    99+
    2023-08-15
    php
  • jquery有哪些请求方式
    这篇文章主要讲解了“jquery有哪些请求方式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“jquery有哪些请求方式”吧! j...
    99+
    2022-10-19
  • python的HTTP请求方式(sock
    关于python的HTTP请求方式HTTP请求步骤为:       1. 域名解析2. 发起TCP的3次握手3. 建立TCP连接后发起http请求4. 服务器端响应http请求,浏览器得到html代码5. 浏览器解析html代码,并请求h...
    99+
    2023-01-31
    方式 python HTTP
  • 前端取消请求及取消重复请求方式
    目录一、前言二、项目准备三、原生方法四、promise五、借助Promise.race六、总结一、前言 今天,我想跟大家分享几种前端取消请求的几种方式。 相信大家在平时的开发中,肯定...
    99+
    2022-11-13
  • 使用Express处理请求和托管静态资源方式
    目录ExpressExpress对比Http模块Express的主要作用Express安装Express静态资源处理express.static()托管多个静态资源目录挂载路径前缀E...
    99+
    2022-11-13
  • Java Spring Boot请求方式与请求映射过程分析
    目录请求方式非Rest风格使用Rest风格表单提交过程分析请求映射过程分析请求方式 Spring Boot支持Rest风格:使用HTTP请求方式的动词来表示对资源的操作 非Rest风...
    99+
    2022-11-13
  • vue axios中的get请求方式
    目录vue axios中的get请求一、安装二、使用常见错误vue axios post请求参数错误400vue axios中的get请求 一、安装 使用 npm:npm insta...
    99+
    2022-11-13
  • ajax的请求方式有哪些
    Ajax的请求方式有以下几种:1. GET:使用GET方法发送请求,获取指定资源。这是最常用的请求方式之一。例如:`$.get(ur...
    99+
    2023-09-13
    ajax
  • ajax请求的方式有哪些
    这篇文章主要讲解了“ajax请求的方式有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“ajax请求的方式有哪些”吧! aja...
    99+
    2022-10-19
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作