spring-cloud-openfeign 源碼解析:
本文主要針對 spring-cloud-starter-openfeign 的 2.2.3.RELEASE 版本進行源碼的解析。
對於未接觸過 Feign的小伙伴可以參考 https://www.cnblogs.com/wuzhenzhao/p/9472607.html 進行一些基礎知識的了解。
@EnableFeignClients
想要集成 Feign 客戶端,需要我們通過注解 @EnableFeignClients 來開啟。這個注解開啟了FeignClient的解析過程。這個注解的聲明如下,它用到了一個@Import注解,我們知道Import是用來導入一個配置類的,接下來去看一下FeignClientsRegistrar的定義
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { }
FeignClientsRegistrar實現了ImportBeanDefinitionRegistrar,它是一個動態注入bean的接口,Spring Boot啟動的時候,會去調用這個類中的registerBeanDefinitions來實現動態Bean的裝載。它的作用類似於ImportSelector。
對於動態注入不清楚的小伙伴可以參考 : https://www.cnblogs.com/wuzhenzhao/p/9151673.html 。
然后就會進入 FeignClientsRegistrar# registerBeanDefinitions 。registerDefaultConfiguration 方法內部從 SpringBoot 啟動類上檢查是否有@EnableFeignClients, 有該注解的話, 則完成 Feign 框架相關的一些配置內容注冊registerFeignClients 方法內部從 classpath 中, 掃描獲得 @FeignClient 修飾的類, 將類的內容解析為 BeanDefinition , 最終通過調用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 將解析處理過的 FeignClientBeanDeifinition 添加到 spring 容器中.
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注冊@EnableFeignClients中定義defaultConfiguration屬性下的類,包裝成FeignClientSpecification,注冊到Spring容器。 //在@FeignClient中有一個屬性:configuration,這個屬性是表示各個FeignClient自定義的配置類,后面也會通過調用registerClientConfiguration方法來注冊成FeignClientSpecification到容器。 //所以,這里可以完全理解在@EnableFeignClients中配置的是做為兜底的配置,在各個@FeignClient配置的就是自定義的情況。 registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }
這里面需要重點分析的就是 registerFeignClients 方法,這個方法主要是掃描類路徑下所有的@FeignClient注解,然后進行動態Bean的注入。它最終會調用 registerFeignClient 方法。
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; //獲取注解
Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); } // 遍歷配置的掃描包路徑
for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration")); // 注冊Feign 客戶端
registerFeignClient(registry, annotationMetadata, attributes); } } } }
registerFeignClient 在這個方法中,就是去組裝BeanDefinition,也就是Bean的定義,然后注冊到Spring IOC容器。
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); // 省略代碼..... BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
我們關注一下,BeanDefinitionBuilder是用來構建一個BeanDefinition的,它是通過 genericBeanDefinition 來構建的,並且傳入了一個FeignClientFactoryBean的類,代碼如下。
/** * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}. * @param beanClass the {@code Class} of the bean that the definition is being created for */ public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) { BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition()); builder.beanDefinition.setBeanClass(beanClass); return builder; }
我們可以發現,FeignClient被動態注冊成了一個FactoryBean.
Spring Cloud FengnClient實際上是利用Spring的代理工廠來生成代理類,所以在這里地方才會把所有的FeignClient的BeanDefinition設置為FeignClientFactoryBean類型,而FeignClientFactoryBean繼承自FactoryBean,它是一個工廠Bean。在Spring中,FactoryBean是一個工廠Bean,用來創建代理Bean。工廠 Bean 是一種特殊的 Bean, 對於 Bean 的消費者來說, 他邏輯上是感知不到這個 Bean 是普通的 Bean 還是工廠 Bean, 只是按照正常的獲取 Bean 方式去調用, 但工廠bean 最后返回的實例不是工廠Bean 本身, 而是執行工廠 Bean 的 getObject 邏輯返回的示例。
簡單來說,FeignClient標注的這個接口,會通過FeignClientFactoryBean.getObject()這個方法獲得一個代理對象。
上述流程主要可以用下圖表示:
FeignClientFactoryBean.getObject:
getObject調用的是getTarget方法,它從applicationContext取出FeignContext,FeignContext繼承了NamedContextFactory,它是用來統一維護feign中各個feign客戶端相互隔離的上下文。
FeignContext注冊到容器是在FeignAutoConfiguration上完成的。
@Autowired(required = false) private List<FeignClientSpecification> configurations = new ArrayList<>(); @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; }
在初始化FeignContext時,會把configurations在容器中放入FeignContext中。configurations 的來源就是在前面registerFeignClients方法中將@FeignClient的配置 configuration。
接着,構建feign.builder,在構建時會向FeignContext獲取配置的Encoder,Decoder等各種信息。FeignContext在上文中已經提到會為每個Feign客戶端分配了一個容器,它們的父容器就是spring容器
配置完Feign.Builder之后,再判斷是否需要LoadBalance,如果需要,則通過LoadBalance的方法來設置。實際上他們最終調用的是Target.target()方法。
@Override public Object getObject() throws Exception { return getTarget(); } <T> T getTarget() { //實例化Feign上下文對象FeignContext FeignContext context = this.applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context);//構建Builder對象 //如果url為空,則走負載均衡,生成有負載均衡功能的代理類 if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } this.url += cleanPath(); return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); } //如果指定了url,則生成默認的代理類 if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient) client).getDelegate(); } if (client instanceof FeignBlockingLoadBalancerClient) { // not load balancing because we have a url, // but Spring Cloud LoadBalancer is on the classpath, so unwrap client = ((FeignBlockingLoadBalancerClient) client).getDelegate(); } builder.client(client); }//生成默認代理類 Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); }
loadBalance :生成具備負載均衡能力的feign客戶端,為feign客戶端構建起綁定負載均衡客戶端
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, target); } throw new IllegalStateException( "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?"); }
Client client = (Client)this.getOptional(context, Client.class); 從上下文中獲取一個 Client,默認是LoadBalancerFeignClient。它是在FeignRibbonClientAutoConfiguration這個自動裝配類中,通過Import實現的
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class }) public class FeignRibbonClientAutoConfiguration {
.....
}
這里的通過 DefaultFeignLoadBalancedConfiguration 注入客戶端 Client 的實現
@Configuration(proxyBeanMethods = false) class DefaultFeignLoadBalancedConfiguration { @Bean @ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, clientFactory); } }
接下去進入 targeter.target(this, builder, context, target) ,攜帶着構建好的這些對象去創建代理實例 ,這里有兩個實現 HystrixTargeter 、DefaultTargeter 很顯然,我們沒有配置 Hystrix ,這里會走 DefaultTargeter
class DefaultTargeter implements Targeter { @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { return feign.target(target); } }
然后會來到 feign.Feign.Builder#target(feign.Target<T>)
public <T> T target(Target<T> target) { return build().newInstance(target); } public Feign build() { SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
最終會調用 ReflectiveFeign.newInstance
這個方法是用來創建一個動態代理的方法,在生成動態代理之前,會根據Contract協議(協議解析規則,解析接口類的注解信息,解析成內部的MethodHandler的處理方式。
從實現的代碼中可以看到熟悉的Proxy.newProxyInstance方法產生代理類。而這里需要對每個定義的接口方法進行特定的處理實現,所以這里會出現一個MethodHandler的概念,就是對應方法級別的InvocationHandler。
public <T> T newInstance(Target<T> target) { // 解析接口注解信息 //根據接口類和Contract協議解析方式,解析接口類上的方法和注解,轉換成內部的MethodHandler處理方式 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); // 根據方法類型 for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } InvocationHandler handler = factory.create(target, methodToHandler); // 基於Proxy.newProxyInstance 為接口類創建動態實現,將所有的請求轉換給InvocationHandler 處理。 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }
targetToHandlersByName.apply(target) :根據Contract協議規則,解析接口類的注解信息,解析成內部表現:targetToHandlersByName.apply(target);會解析接口方法上的注解,從而解析出方法粒度的特定的配置信息,然后生產一個SynchronousMethodHandler 然后需要維護一個<method,MethodHandler>的map,放入InvocationHandler的實現FeignInvocationHandler中。
public Map<String, MethodHandler> apply(Target target) { List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target); } else { buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target); } if (md.isIgnored()) { result.put(md.configKey(), args -> { throw new IllegalStateException(md.configKey() + " is not a method handled by feign"); }); } else { result.put(md.configKey(), factory.create(target, md, buildTemplate, options, decoder, errorDecoder)); } } return result; }
SpringMvcContract :當前Spring Cloud 微服務解決方案中,為了降低學習成本,采用了Spring MVC的部分注解來完成 請求協議解析,也就是說 ,寫客戶端請求接口和像寫服務端代碼一樣:客戶端和服務端可以通過SDK的方式進行約定,客戶端只需要引入服務端發布的SDK API,就可以使用面向接口的編碼方式對接服務。
OpenFeign調用過程 :
在前面的分析中,我們知道OpenFeign最終返回的是一個 ReflectiveFeign.FeignInvocationHandler 的對象。那么當客戶端發起請求時,會進入到 FeignInvocationHandler.invoke 方法中,這個大家都知道,它是一個動態代理的實現。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("equals".equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } // 利用分發器篩選方法,找到對應的handler 進行處理 return dispatch.get(method).invoke(args); }
而接着,在invoke方法中,會調用 this.dispatch.get(method)).invoke(args) 。this.dispatch.get(method) 會返回一個SynchronousMethodHandler,進行攔截處理。這個方法會根據參數生成完成的RequestTemplate對象,這個對象是Http請求的模版,代碼如下。
@Override public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Options options = findOptions(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template, options); } catch (RetryableException e) { try { retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }
經過上述的代碼,我們已經將restTemplate拼裝完成,上面的代碼中有一個 executeAndDecode() 方法,該方法通過RequestTemplate生成Request請求對象,然后利用Http Client獲取response,來獲取響應信息。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { //轉化為Http請求報文 Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { //發起遠程通信 response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 12 //獲取返回結果 response = response.toBuilder() .request(request) .requestTemplate(template) .build(); } catch (IOException e) { // ....... }
經過上面的分析,這里的 client.execute 的 client 的類型是LoadBalancerFeignClient
這里就很自然的進入 LoadBalancerFeignClient#execute
public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { IOException io = findIOException(e); if (io != null) { throw io; } throw new RuntimeException(e); } }
其實這個execute里面得流程就是 Ribbon 的那一套。我們可以簡單的看一下。首先是構造URI,構造RibbonRequest,選擇 LoadBalance,發起調用。
來看一下lbClient 選擇負載均衡器的時候做了什么
public FeignLoadBalancer create(String clientName) { FeignLoadBalancer client = this.cache.get(clientName); if (client != null) { return client; } IClientConfig config = this.factory.getClientConfig(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client); return client; }
可以得出的結論就是 this.factory.getLoadBalancer(clientName) 跟Ribbon 源碼里的獲取方式一樣,無疑這里獲取的就是默認的 ZoneAwareLoadBalancer。然后包裝成一個 FeignLoadBalancer 進行返回
既然負載均衡器選擇完了,那么一定還有個地方通過該負載去選擇一個服務,接着往下看:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try { return command.submit( new ServerOperation<T>() { @Override public Observable<T> call(Server server) { URI finalUri = reconstructURIWithServer(server, request.getUri()); S requestForServer = (S) request.replaceUri(finalUri); try { return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); } catch (Exception e) { return Observable.error(e); } } }) .toBlocking() .single(); } catch (Exception e) { Throwable t = e.getCause(); if (t instanceof ClientException) { throw (ClientException) t; } else { throw new ClientException(e); } } }
上面這段代碼就是通過獲取到的負載進行執行請求,但是這個時候 服務還沒有選擇,我們跟進去 submit 請求看一看究竟:
public Observable<T> submit(final ServerOperation<T> operation) { final ExecutionInfoContext context = new ExecutionInfoContext(); // ......... Observable<T> o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1<Server, Observable<T>>() { //........ }); // ....... }
可以看到這里有個 selectServer的方法 ,跟進去:
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { String host = null; int port = -1; if (original != null) { host = original.getHost(); } if (original != null) { Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original); port = schemeAndPort.second(); } // Various Supported Cases // The loadbalancer to use and the instances it has is based on how it was registered // In each of these cases, the client might come in using Full Url or Partial URL ILoadBalancer lb = getLoadBalancer(); if (host == null) { // ............ } else { // ...........if (shouldInterpretAsVip) { Server svc = lb.chooseServer(loadBalancerKey); if (svc != null){ host = svc.getHost(); if (host == null){ throw new ClientException(ClientException.ErrorType.GENERAL, "Invalid Server for :" + svc); } logger.debug("using LB returned Server: {} for request: {}", svc, original); return svc; } else { // just fall back as real DNS logger.debug("{}:{} assumed to be a valid VIP address or exists in the DNS", host, port); } } else { // consult LB to obtain vipAddress backed instance given full URL //Full URL execute request - where url!=vipAddress logger.debug("Using full URL passed in by caller (not using load balancer): {}", original); } } // .......... return new Server(host, port); }
可以看到的是這里獲取到了之前構造好的 ZoneAwareLoadBalancer 然后調用 chooseServer 方法獲取server ,這個是跟Ribbon 中是一樣的流程,這里就不贅述了。
獲取到了server 后,會回調先前 executeWithLoadBalancer 方法里構造的 ServerOperation 的 call 方法:
return command.submit( new ServerOperation<T>() { @Override public Observable<T> call(Server server) { URI finalUri = reconstructURIWithServer(server, request.getUri()); S requestForServer = (S) request.replaceUri(finalUri); try { return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); } catch (Exception e) { return Observable.error(e); } } }) .toBlocking() .single();
然后會執行 AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig) 進行最后的調用,實際上這里走的是 FeignLoadBalancer#execute
@Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; if (configOverride != null) { RibbonProperties override = RibbonProperties.from(configOverride); options = new Request.Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } Response response = request.client().execute(request.toRequest(), options); return new RibbonResponse(request.getUri(), response); }
而這里調用的 request.client().execute(request.toRequest(), options) 則是 DefaultFeignLoadBalancedConfiguration 注入的 LoadBalancerFeignClient ,在構造 LoadBalancerFeignClient 的時候 ,傳遞了個 feign.Client.Default ,然后利用 feign.Client.Default 構造了一個 RibbonRequest。
所以這里走 feign.Client.Default#execute :
@Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); }
利用 JDK 提供的 HttpURLConnection 發起遠程的 HTTP通訊。至此發起請求的流程就完成了。下面附上一張這個過程的流程圖,對於Ribbon的調用過程請參考 :Ribbon 源碼分析
OpenFeign Configuration :
針對 feign 的 Configuration ,官方給我們提供了很多的個性化配置,具體可以參考 org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration
public static class FeignClientConfiguration { // 日志 private Logger.Level loggerLevel; // 連接超時 private Integer connectTimeout; private Integer readTimeout; //重試 private Class<Retryer> retryer; //解碼 private Class<ErrorDecoder> errorDecoder; private List<Class<RequestInterceptor>> requestInterceptors; // 編碼 private Boolean decode404; private Class<Decoder> decoder; private Class<Encoder> encoder; // 解析 private Class<Contract> contract; private ExceptionPropagationPolicy exceptionPropagationPolicy; }
這里舉個簡單的例子,以Logger 為例。我們想為每個不同的 FeignClient 設置日志級別。
1.添加配置類:
@Configuration public class FooConfiguration { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
2.配置日志級別 ,logging.level + FeignClient 包的全路徑。
logging.level.com.wuzz.FeignClientService: DEBUG
就這樣就配置完成了。重啟服務就可以看到效果。
更多配置請參考官網。