最全面的總結spring cloud Feign的常見問題

本文踩坑有二:

  1. not annotated with HTTP method type (ex. GET, POST)
  2. FeignException$BadRequest: status 400

1.not annotated with HTTP method

報錯內容如下:

java.lang.IllegalStateException:
 Method XXX not annotated with HTTP method type (ex. GET, POST)

Feign可以使用自帶註解@RequestLine以及spring的註解@RequestMapping、@GetMapping等

幾個需要注意的點:

  • 使用spring的註解@RequestMapping需要指定method屬性以及value(路徑信息)
  • 看了很多其他博客說不可以使用@GetMapping等這類註解,本人親測,可以使用
  • 第三點比較重要的
    如果使用Feign自帶註解@RequestLine,需要修改默認配置。spring cloud netflix默認爲feign提供的默認bean如下:
Decoder feignDecoder:ResponseEntityDecoder(其中包含SpringDecoder)

Encoder feignEncoder:SpringEncoder

Logger feignLogger:Slf4jLogger

Contract feignContract:SpringMvcContract

Feign.Builder feignBuilder:HystrixFeign.Builder

Client feignClient:如果Ribbon啓用,則爲LoadBalancerFeignClient,否則將使用默認的feign客戶端。

看上方的Contract feignContract:SpringMvcContract,說明feign默認使用springmvc的協議或者約定,使用@RequestMapping的註解,若需要使用@RequestLine,需要修改約束配置,如下

@Configuration
public class Configuration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }
}

在feignclient註解裏配置

@FeignClient(value = "client", url = "${XXX}", configuration = Configuration .class)

2.status 400

報錯內容如下:

FeignException$BadRequest: status 400 reading xxService#xxmethod(String, Interger)

看錯誤碼400,http錯誤碼400主要有兩種形式:
1、bad request 意思是 “錯誤的請求”;
2、invalid hostname 意思是 “不存在的域名”。
400 Bad Request 是由於明顯的客戶端錯誤(例如,格式錯誤的請求語法,太大的大小,無效的請求消息或欺騙性路由請求),服務器不能或不會處理該請求。

原因

查找其他博客得到結果有一下幾類:

  • 傳遞的參數爲空
  • 參數長度過長
  • header過長
  • RequestMapping沒有指定method屬性
  • 本人遇到的情況,header參數錯誤(下面講解錯誤原因)

解決

對於第一種傳參爲空,可以指定參數require=false,如下

@RequestParam(value = "param",required = false) String param

還要注意的是如果使用rest風格的請求,路徑有佔位符,一定要使用@PathVariable註解,這也是很多人踩過的坑。
對於第二種問題,是由於springboot內嵌tomcat對參數長度限制是8K,可以修改配置進行解決

server.max-http-header-size=20480

第三種情況與第二種情況一致。
第四種情況直接指定方法就好。

本人踩坑復現

本人遇到的問題是header設置的問題。由於需要使用token進行驗證,而且不同的請求對應不同的token,導致無法使用靜態header進行指定,於是使用修改配置的方式進行,直接修改restTemplate.

@Configuration
public class AuthorityConfig implements RequestInterceptor{
    @Override
    public void apply(RequestTemplate template) {
        SecurityContext securityCxt = SecurityContextHolder.getContext();
        XXX token = XXX securityCxt.getAuthentication();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //添加token
        template.header(Constants.TOKEN, request.getHeader(token.getToken()));
        //template.headers().remove("Content-Type");
//        Map<String, Collection<String>> headers = new HashMap<>();
//        ArrayList<String> headerToken = new ArrayList<>();
//        headerToken.add(request.getHeader(token.getToken()));
//        headers.put(Constants.TOKEN, headerToken);
//        headers.put("Content-Type", new ArrayList(){{add("application/x-www-form-urlencoded");}});
        template.headers(new HashMap<>());
        template.header("Content-Type", "application/x-www-form-urlencoded");
        template.header(Constants.TOKEN_TEACHER, token.getToken());
        System.out.println("headers is : "+template.headers());
    }
}

上方代碼看倒數第四行,我設置了一個空的map,之後才進行的其他header的添加。這是踩坑之後才發現得這麼做的。
看我註釋掉的代碼發現,template.header()需要一個string和一個集合,也就是說,template會把key相同的屬性放到一個集合裏。這似乎也很正常,坑的是它本身有一些默認的header,如果你添加了同名的header後只是加入到了一個集合而不是覆蓋,導致我註釋的代碼添加Content-Type後,該key對應的header是

[application/json,application/x-www-form-urlencoded]

這就很尷尬,服務器因爲這個拒絕了我。查看其源碼實現

public RequestTemplate headers(Map<String, Collection<String>> headers) {
    if (headers != null && !headers.isEmpty()) {
      headers.forEach(this::header);
    } else {
      this.headers.clear();
    }
    return this;
  }

發現傳入null或者空可以實現clear(),所以我傳了空先進行清除後再逐個添加,這樣解決了400的問題。

推薦閱讀:feign實戰中header的巧妙處理
以上是這兩天的踩坑集錦,有誤之處請指正,不勝感激~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章