一、業務需求
在Spring Cloud的項目中,A服務使用Feign調用B服務的某個接口,如果需要傳遞全局認證token或參數,在方法參數里面加相應字段的方式顯然是不可取的。
首先想到的是AOP方式,使用切面攔截Feign方法,在AOP切面里面向方法參數里面添加數據,Feign方法執行完成之后,從響應對象里面獲取返回的數據,這樣的方式可以解決數據的傳遞和接收,但也必將需要方法參數和響應對象的支持,與業務耦合,並不是合理的架構實現方案。
如果有某種機制可以攔截到Feign的請求對象和響應對象,便可以獲取到請求頭和響應頭,就可以使用請求頭和響應頭來傳遞數據。
經過一番調查,了解到Feign的RequestInterceptor可以攔截到Feign請求,可以獲取到請求對象和請求頭,但是RequestInterceptor無法處理響應。
於是又進行調查,得知解碼器Decoder是對響應進行解碼的組件,可以獲取到響應對象和響應頭。
在調查過程中,還有另外的收獲:FeignClientsConfiguration類。
二、FeignClientsConfiguration類
Feign默認配置類是FeignClientsConfiguration類,該類定義了Feign默認的編碼器、解碼器、所使用的契約等。
Spring Cloud允許通過注解@FeignClient的configuration屬性自定義Feign配置,自定義配置的優先級比FeignClientsConfiguration要高。
這個類的核心代碼如下:
1 @Configuration 2 public class FeignClientsConfiguration { 3 4 @Autowired 5 private ObjectFactory<HttpMessageConverters> messageConverters; 6 7 @Autowired(required = false) 8 private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>(); 9 10 @Autowired(required = false) 11 private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>(); 12 13 @Autowired(required = false) 14 private Logger logger; 15 16 @Bean 17 @ConditionalOnMissingBean 18 public Decoder feignDecoder() { 19 return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); 20 } 21 22 @Bean 23 @ConditionalOnMissingBean 24 @ConditionalOnMissingClass("org.springframework.data.domain.Pageable") 25 public Encoder feignEncoder() { 26 return new SpringEncoder(this.messageConverters); 27 } 28 29 @Bean 30 @ConditionalOnMissingBean 31 public Contract feignContract(ConversionService feignConversionService) { 32 return new SpringMvcContract(this.parameterProcessors, feignConversionService); 33 } 34 35 @Bean 36 @Scope("prototype") 37 @ConditionalOnMissingBean 38 public Feign.Builder feignBuilder(Retryer retryer) { 39 return Feign.builder().retryer(retryer); 40 } 41 }
三、Feign請求攔截器
參考FeignClientsConfiguration類,我們可以編寫一個configuration類,注入自定義的RequestInterceptor實現類對象,在apply(RequestTemplate requestTemplate)方法中獲取到請求RestTemplate對象,使用RequestTemplate對象設置請求參數、添加請求頭。
示例如下:
1 @Configuration 2 public class MyConfig { 3 @Bean("myInterceptor") 4 public RequestInterceptor getRequestInterceptor() { 5 return new MyClientInterceptor(); 6 } 7 }
MyClientInterceptor類:
1 class MyClientInterceptor implements RequestInterceptor { 2 @Override 3 public void apply(RequestTemplate requestTemplate) { 4 requestTemplate.query("name", "Allen"); 5 requestTemplate 6 .header("token", "token") 7 .header("id", id); 8 } 9 }
四、Feign解碼器
1、解碼器概述
Feign解碼器負責對響應進行解碼,返回符合Feign接口需求的對象。
我們可以參考FeignClientsConfiguration類中的方式編寫和注入自定義的解碼器。
1 @Bean 2 @ConditionalOnMissingBean 3 public Decoder feignDecoder() { 4 return new OptionalDecoder( 5 new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); 6 }
2、繼承SpringDecoder自定義解碼器
1 class TraceDecoder extends SpringDecoder { 2 3 TraceDecoder(ObjectFactory<HttpMessageConverters> messageConverters) { 4 super(messageConverters); 5 } 6 7 @Override 8 public Object decode(Response response, Type type) throws IOException, FeignException { 9 10 // 這里可以從response對象里面獲取響應頭和響應體 11 12 // 獲取響應頭 13 Map<String, Collection<String>> headers = response.headers(); 14 15 return super.decode(response, type); 16 } 17 }
3、注入自定義解碼器
feignDecoder()方法完全參考了FeignClientsConfiguration類的寫法。
1 @Slf4j 2 @Configuration 3 @ConditionalOnClass({Feign.class}) 4 @AutoConfigureBefore(FeignAutoConfiguration.class) 5 public class CommonLogFeignConfig { 6 7 @Autowired 8 private ObjectFactory<HttpMessageConverters> messageConverters; 9 10 @Bean 11 public Decoder feignDecoder() { 12 return new OptionalDecoder(new ResponseEntityDecoder(new TraceDecoder(this.messageConverters))); 13 } 14 }