Feign利用自定义注解实现路径转义详解

目录
  • 背景
  • 解决方案
  • 最后

背景

近期由于项目中需要,所以需要通过Feign封装一个对Harbor操作的sdk信息。

在调用的过程中发现,当请求参数中带有"/"时,Feign默认会将"/"当成路径去解析,而不是当成完整的一个参数解析,实例如下

请求路径为:api/v2.0/projects/{projectName}/repositories

注解参数为:@PathVariable("projectName")

正常请求为:api/v2.0/projects/test/repositories

异常路径为:api/v2.0/projects/test/pro/repositories

相信细心的同学已经发现上面的差异了,正常的{projectName}中对应的值为test,而异常的却对应为test/pro,所以当异常的请求打到harbor的机器时,被解析为api/v2.0/projects/test/pro/repositories,所以会直接返回404

以上就是背景了,所以接下来我们讨论一下解决方案

解决方案

首先我们知道springboot中默认是带有几种注释参数处理器的

@MatrixVariableParameterProcessor
@PathVariableParameterProcessor
@QueryMapParameterProcessor
@RequestHeaderParameterProcessor
@RequestParamParameterProcessor
@RequestPartParameterProcessor

因为我们的请求参数是在路径中的,所以默认我们会使用@PathVariableParameterProcessor来标识路径参数,而我们需要转义的参数其实也是在路径中,所以我们先来看一下@PathVariableParameterProcessor是如何实现的

public boolean processArgument(AnnotatedParameterProcessor.AnnotatedParameterContext context, Annotation annotation, Method method) {
        String name = ((PathVariable)ANNOTATION.cast(annotation)).value();
        Util.checkState(Util.emptyToNull(name) != null, "PathVariable annotation was empty on param %s.", new Object[]{context.getParameterIndex()});
        context.setParameterName(name);
        MethodMetadata data = context.getMethodMetadata();
        String varName = '{' + name + '}';
        if (!data.template().url().contains(varName) && !this.searchMapValues(data.template().queries(), varName) && !this.searchMapValues(data.template().headers(), varName)) {
            data.formParams().add(name);
        }
        return true;
    }

其实在源码中,springboot并没有做什么神器的事情,就是获取使用了PathVariable注解的参数,然后再将其添加到fromParams中就可以。

看到这里我们是不是可以想到,既然在这里我们可以拿到对应的参数了,那想做什么事情不都是由我们自己来决定了,接下来说干就干,

首先我们声明一个属于自己的注解,

import org.springframework.core.annotation.AliasFor;

import java.lang.annotation.*;

/**
  * @CreateAt: 2022/6/11 0:46
 * @ModifyAt: 2022/6/11 0:46
 * @Version 1.0
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SlashPathVariable {

    /**
     * Alias for {@link #name}.
     */
    @AliasFor("name")
    String value() default "";

    /**
     * The name of the path variable to bind to.
     * @since 4.3.3
     */
    @AliasFor("value")
    String name() default "";

    /**
     * Whether the path variable is required.
     * <p>Defaults to {@code true}, leading to an exception being thrown if the path
     * variable is missing in the incoming request. Switch this to {@code false} if
     * you prefer a {@code null} or Java 8 {@code java.util.Optional} in this case.
     * e.g. on a {@code ModelAttribute} method which serves for different requests.
     * @since 4.3.3
     */
    boolean required() default true;
}

声明完注解后,我们就需要来自定义自己的参数解析器了,首先继承AnnotatedParameterProcessor

import feign.MethodMetadata;
import feign.Util;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.web.bind.annotation.PathVariable;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @CreateAt: 2022/6/11 0:36
 * @ModifyAt: 2022/6/11 0:36
 * @Version 1.0
 */
public class SlashPathVariableParameterProcessor implements AnnotatedParameterProcessor {

    private  static  final Class<SlashPathVariable> ANNOTATION=SlashPathVariable.class;
    @Override
    public Class<? extends Annotation> getAnnotationType() {
        return (Class<? extends Annotation>) ANNOTATION;
    }

    @Override
    public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
        MethodMetadata data = context.getMethodMetadata();
        String name = ANNOTATION.cast(annotation).value();
        Util.checkState(Util.emptyToNull(name) != null, "SlashPathVariable annotation was empty on param %s.", new Object[]{context.getParameterIndex()});
        context.setParameterName(name);
        data.indexToExpander().put(context.getParameterIndex(),this::expandMap);
        return true;
    }

    private String expandMap(Object object) {
        String encode = URLEncoder.encode(URLEncoder.encode(object.toString(), Charset.defaultCharset()), Charset.defaultCharset());
        return encode;
    }
}

可以看到上面的代码,我们获取到自定义注解的参数后,将当前参数添加打Param后,并且为当前参数指定自定义的编码格式。

最后,我们再通过Bean的形式将对应的注解添加到容器中

import feign.Contract;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @CreateAt: 2022/6/11 0:48
 * @ModifyAt: 2022/6/11 0:48
 * @Version 1.0
 */
@Component
public class SlashBean {

    @Bean
    public Contract feignContract(){
        List<AnnotatedParameterProcessor> processors=new ArrayList<>();
        processors.add(new SlashPathVariableParameterProcessor());
        return new SpringMvcContract(processors);
    }
}

最后我们将上面的参数注解PathVariable换成我们自定义的@SlashPathVariable,就大功告成了

最后

通过以上的形式进行注入的话,会注入到Spring全局,所以在使用的过程中需要考虑是否符合场景

以上就是Feign利用自定义注解实现路径的转义详解的详细内容,更多关于Feign路径的转义的资料请关注我们其它相关文章!

(0)

相关推荐

  • Feign如何自定义注解翻译器

    目录 Feign自定义注解翻译器 新建自定义注解MyUrl 新建接口,使用MyUrl注解 定义注解翻译器 测试类 启动服务类 Feign注解说明 Feign自定义注解翻译器 新建自定义注解MyUrl package org.crazyit.cloud.contract;  import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Reten

  • 使用Feign调用注解组件(实现字段赋值功能)

    目录 使用效果 优点 如何装配 特殊需求 使用注解的形式,装配在id字段,自动调用fegin赋值给目标字段. 使用效果 1.先给vo类中字段添加注解 2.调用feignDataSetUtils.setData 方法  将vo类放入 比如我的 feignDataSetUtils.setData(Stream.of(vo).collect(Collectors.toList())); 调用前 调用后 产生赋值. 利用类字段注解的形式配置好对应的fegin关系,达到自动调用fegin的效果. 优点 1

  • feign 调用第三方服务中部分特殊符号未转义问题

    目录 调用第三方部分特殊符号未转义 1.问题发现过程 2.解决办法 3.疑问 @RequestParams&符号未转义 feign-core版本 源码分析 测试 解决方案 调用第三方部分特殊符号未转义 开发过程中,发现+(加号)这个符号没有转义,导致再调用服务的时候把加号转义成空格了.导致后台获取到的数据会不正确. 1. 问题发现过程 feign 解析参数的时候,使用的标准是 RFC 3986,这个标准的加号是不需要被转义的.其具体的实现是 feign.template.UriUtils#enc

  • Feign利用自定义注解实现路径转义详解

    目录 背景 解决方案 最后 背景 近期由于项目中需要,所以需要通过Feign封装一个对Harbor操作的sdk信息. 在调用的过程中发现,当请求参数中带有"/"时,Feign默认会将"/"当成路径去解析,而不是当成完整的一个参数解析,实例如下 请求路径为:api/v2.0/projects/{projectName}/repositories 注解参数为:@PathVariable("projectName") 正常请求为:api/v2.0/pr

  • Flask利用自定义接口实现mock应用详解

    问题:后端接口已提供,前端需要依赖后端接口返回的数据进行前端页面的开发,如何配合前端? mock接口 flask自定义接口实现查询接口:查询全部.部分查询 具体看下面的代码: #导入包 from flask import Flask,jsonify #定义内容属性 app = Flask(__name__) #处理中文乱码问题 app.config["JSON_AS_ASCII"]=False #接口数据,接口文档(已知) 多个参数 user_data=[ {"id"

  • SpringBoot如何通过自定义注解实现权限检查详解

    前言 最近开发了一个接口,完成后准备自测时,却被拦截器拦截了,提示:(AUTH-NO)未能获得有效的请求参数!怎么会这样呢? 于是我全局搜了这个提示语,结果发现它被出现在一个Aspect类当中了,并且把一个 @interface 作为了一个切点,原来这里利用了Spring AOP面向切面的方式进行权限控制. SpringBoot通过自定义注解实现日志打印可参考:SpringBoot通过自定义注解实现日志打印 正文 Spring AOP Spring AOP 即面向切面,是对OOP面向对象的一种延

  • Spring Boot如何通过自定义注解实现日志打印详解

    前言 在我们日常的开发过程中通过打印详细的日志信息能够帮助我们很好地去发现开发过程中可能出现的Bug,特别是在开发Controller层的接口时,我们一般会打印出Request请求参数和Response响应结果,但是如果这些打印日志的代码相对而言还是比较重复的,那么我们可以通过什么样的方式来简化日志打印的代码呢? SpringBoot 通过自定义注解实现权限检查可参考我的博客:SpringBoot 通过自定义注解实现权限检查 正文 Spring AOP Spring AOP 即面向切面,是对OO

  • Golang利用自定义模板发送邮件的方法详解

    前言 在几周前,我开始工作于一个证券投资组合网站.虽然我只能使用 React 完成整个网站,但我决定使用 Go 来创建一个可以处理某些任务(例如发送 email)的 API 服务器,相信这是一个很好的做法. 我其中的一个页面是一个 contact 页面,目前看起来像这样: contact me 我想使用专门为此 contact 表单申请的 Gmail 帐户发送一封邮件.除了我以前用过 Javascript 发送电子邮件的事实,我没有特别选择 Go.但为什么不尝试 Go 呢?我觉得 Go 很棒.

  • Mysql5.7.18版本(二进制包安装)自定义安装路径教程详解

    安装路径:/application/mysql-5.7.18 1.前期准备 mysql依赖 libaio yum install -y libaio 创建用户mysql,以该用户的身份执行mysql useradd -s /bin/false -M mysql 下载mysql二进制包并解压 cd /tools wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.18-linux-glibc2.5-x86_64.tar.gz t

  • Java利用自定义注解、反射实现简单BaseDao实例

    在常见的ORM框架中,大都提供了使用注解方式来实现entity与数据库的映射,这里简单地使用自定义注解与反射来生成可执行的sql语句. 这是整体的目录结构,本来是为复习注解建立的项目^.^ 好的,首先我们来确定思路. 1. 自定义@Table @Column注解, 我们稍微模仿hibernate,让@Table作用于类上,来表明实体类与数据表的映射关系,且让@Table中的属性value映射为数据表的名称tableName:让@Column作用于属性上(这里没实现作用于set方法上),表明属性与

  • 基于注解的组件扫描详解

    在使用组件扫描时,需要现在XML配置中指定扫描的路径 <context:component-scan back-package="yangjq.test"> 容器实例化会扫描yangjq.test包及其子包下的所有组件类. 只有当组件类定义前面有下面的注解标记时,这些组件类才会被扫描到Spring容器 - @Component 通用注解 - @Name 通用注解 - @Repository 持久化层组件注解 - @Service 业务层组件注解 - @Controller

  • Spring中@Async注解实现异步调详解

    异步调用 在解释异步调用之前,我们先来看同步调用的定义:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果. 异步调用则是只是发送了调用的指令,调用者无需等待被调用的方法完全执行完毕,继续执行下面的流程.例如, 在某个调用中,需要顺序调用 A, B, C三个过程方法:如他们都是同步调用,则需要将他们都顺序执行完毕之后,过程才执行完毕: 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了. 概述说明 Sp

  • Flutter利用Canvas绘制精美表盘效果详解

    目录 前言 初始化 面板 刻度 刻度线 刻度值 指针 时针 分针 秒针 动起来 前言 趁着周末空闲时间使用 Flutter 的 Canvas制作了一个精美表盘. 最终实现的效果还不错,如下: 前面说到使用 Canvas 实现该表盘效果,而在 Flutter 中使用 Canvas 更多的则是继承 CustomPainter 类实现 paint 方法,然后在 CustomPaint 中使用自定义实现的 CustomPainter. 比如这里创建的 DialPainter 使用如下: @overrid

随机推荐