Feign通过自定义注解实现路径的转义

博客 分享
0 172
优雅殿下
优雅殿下 2022-06-27 21:01:04
悬赏:0 积分 收藏

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)@Documentedpublic @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 */@Componentpublic class SlashBean {    @Bean    public Contract feignContract(){        List<AnnotatedParameterProcessor> processors=new ArrayList<>();        processors.add(new SlashPathVariableParameterProcessor());        return new SpringMvcContract(processors);    }}

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

最后

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

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧??

posted @ 2022-06-27 20:22 初夏的阳光丶 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员