溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringCloud項(xiàng)目中Feign組件添加請求頭所遇到的坑如何解決

發(fā)布時間:2023-04-26 11:23:02 來源:億速云 閱讀:94 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“SpringCloud項(xiàng)目中Feign組件添加請求頭所遇到的坑如何解決”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“SpringCloud項(xiàng)目中Feign組件添加請求頭所遇到的坑如何解決”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

    前言

    在spring cloud的項(xiàng)目中用到了feign組件,簡單配置過后即可完成請求的調(diào)用。

    又因?yàn)橛邢蛘埱筇砑親eader頭的需求,查閱了官方示例后,就覺得很簡單,然后一頓操作之后調(diào)試報錯...

    按官方修改的示例:

    #MidServerClient.java
    import feign.Param;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    @FeignClient(value = "edu-mid-server")
    public interface MidServerClient {
    
        @RequestMapping(value = "/test/header", method = RequestMethod.GET)
        @Headers({"userInfo:{userInfo}"})
        Object headerTest(@Param("userInfo") String userInfo);
    }

    提示錯誤:

    java.lang.IllegalArgumentException: method GET must not have a request body.

    分析

    通過斷點(diǎn)debug發(fā)現(xiàn)feign發(fā)請求時把userInfo參數(shù)當(dāng)成了requestBody來處理,而okhttp3會檢測get請求不允許有body(其他類型的請求哪怕不報錯,但因?yàn)椴皇窃O(shè)置到請求頭,依然不滿足需求)。

    查閱官方文檔里是通過Contract(Feign.Contract.Default)來解析注解的:

    Feign annotations define the Contract between the interface and how the underlying client should work. Feign's default contract defines the following annotations:

    AnnotationInterface TargetUsage
    @RequestLineMethodDefines the HttpMethod and UriTemplate for request. Expressions, values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters.
    @ParamParameterDefines a template variable, whose value will be used to resolve the corresponding template Expression, by name.
    @HeadersMethod, TypeDefines a HeaderTemplate; a variation on a UriTemplate. that uses @Param annotated values to resolve the corresponding Expressions. When used on a Type, the template will be applied to every request. When used on a Method, the template will apply only to the annotated method.
    @QueryMapParameterDefines a Map of name-value pairs, or POJO, to expand into a query string.
    @HeaderMapParameterDefines a Map of name-value pairs, to expand into Http Headers
    @BodyMethodDefines a Template, similar to a UriTemplate and HeaderTemplate, that uses @Param annotated values to resolve the corresponding Expressions.

    從自動配置類找到使用的是spring cloud的SpringMvcContract(用來解析@RequestMapping相關(guān)的注解),而這個注解并不會處理解析上面列的注解

    @Configuration
    public class FeignClientsConfiguration {
    	···
    	@Bean
    	@ConditionalOnMissingBean
    	public Contract feignContract(ConversionService feignConversionService) {
    		return new SpringMvcContract(this.parameterProcessors, feignConversionService);
    	}
    	···

    解決

    原因找到了

    spring cloud使用了自己的SpringMvcContract來解析注解,導(dǎo)致默認(rèn)的注解解析方式失效。

    解決方案自然就是重新解析處理feign的注解,這里通過自定義Contract繼承SpringMvcContract再把Feign.Contract.Default解析邏輯般過來即可(重載的方法是在SpringMvcContract基礎(chǔ)上做進(jìn)一步解析,否則Feign對RequestMapping相關(guān)對注解解析會失效)

    代碼如下(此處只對@Headers、@Param重新做了解析):

    #FeignCustomContract.java
    
    import feign.Headers;
    import feign.MethodMetadata;
    import feign.Param;
    import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
    import org.springframework.cloud.openfeign.support.SpringMvcContract;
    import org.springframework.core.convert.ConversionService;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    import java.util.*;
    
    import static feign.Util.checkState;
    import static feign.Util.emptyToNull;
    
    public class FeignCustomContract extends SpringMvcContract {
    
    
        public FeignCustomContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors, ConversionService conversionService) {
            super(annotatedParameterProcessors, conversionService);
        }
    
        @Override
        protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
            //解析mvc的注解
            super.processAnnotationOnMethod(data, methodAnnotation, method);
            //解析feign的headers注解
            Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
            if (annotationType == Headers.class) {
                String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();
                checkState(headersOnMethod.length > 0, "Headers annotation was empty on method %s.", method.getName());
                data.template().headers(toMap(headersOnMethod));
            }
        }
    
        @Override
        protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
            boolean isMvcHttpAnnotation = super.processAnnotationsOnParameter(data, annotations, paramIndex);
    
            boolean isFeignHttpAnnotation = false;
            for (Annotation annotation : annotations) {
                Class<? extends Annotation> annotationType = annotation.annotationType();
                if (annotationType == Param.class) {
                    Param paramAnnotation = (Param) annotation;
                    String name = paramAnnotation.value();
                    checkState(emptyToNull(name) != null, "Param annotation was empty on param %s.", paramIndex);
                    nameParam(data, name, paramIndex);
                    isFeignHttpAnnotation = true;
                    if (!data.template().hasRequestVariable(name)) {
                        data.formParams().add(name);
                    }
                }
            }
            return isMvcHttpAnnotation || isFeignHttpAnnotation;
    
        }
    
        private static Map<String, Collection<String>> toMap(String[] input) {
            Map<String, Collection<String>> result =
                    new LinkedHashMap<String, Collection<String>>(input.length);
            for (String header : input) {
                int colon = header.indexOf(':');
                String name = header.substring(0, colon);
                if (!result.containsKey(name)) {
                    result.put(name, new ArrayList<String>(1));
                }
                result.get(name).add(header.substring(colon + 1).trim());
            }
            return result;
        }
    }
    #FeignCustomConfiguration.java
    
    import feign.Contract;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.ConversionService;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Configuration
    public class FeignCustomConfiguration {
    
        ···
        @Autowired(required = false)
        private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
    
        @Bean
        @ConditionalOnProperty(name = "feign.feign-custom-contract", havingValue = "true", matchIfMissing = true)
        public Contract feignContract(ConversionService feignConversionService) {
            return new FeignCustomContract(this.parameterProcessors, feignConversionService);
        }
        ···

    改完馬上進(jìn)行新一頓的操作, 看請求日志已經(jīng)設(shè)置成功,響應(yīng)OK!:

    請求:{"type":"OKHTTP_REQ","uri":"/test/header","httpMethod":"GET","header":"{"accept":["/"],"userinfo":["{"userId":"sssss","phone":"13544445678],"x-b3-parentspanid":["e49c55484f6c19af"],"x-b3-sampled":["0"],"x-b3-spanid":["1d131b4ccd08d964"],"x-b3-traceid":["9405ce71a13d8289"]}","param":""}

    響應(yīng){"type":"OKHTTP_RESP","uri":"/test/header","respStatus":0,"status":200,"time":5,"header":"{"cache-control":["no-cache,no-store,max-age=0,must-revalidate"],"connection":["keep-alive"],"content-length":["191"],"content-type":["application/json;charset=UTF-8"],"date":["Fri,11Oct201913:02:41GMT"],"expires":["0"],"pragma":["no-cache"],"x-content-type-options":["nosniff"],"x-frame-options":["DENY"],"x-xss-protection":["1;mode=block"]}"}

    讀到這里,這篇“SpringCloud項(xiàng)目中Feign組件添加請求頭所遇到的坑如何解決”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI