17.springcloud_openfeign之扩展组件一

文章目录

  • 一、前言
  • 二、默认约定配置
    • FeignAutoConfiguration
      • CachingCapability
      • FeignCachingInvocationHandlerFactory
      • FeignJacksonConfiguration
      • 熔断器配置
      • FeignCircuitBreakerTargeter
      • FeignCircuitBreaker.Builder
    • FeignClientsConfiguration
      • CircuitBreakerFactory
  • 总结

一、前言

前面介绍了springcloud_openfeign可以从父子容器中获取对应的组件从而构建Feign.Builder对象, 现在的企业级开发中, 一般我们都使用的springboot, 而springboot是约定大于配置的, 那么它提供的一套默认配置是什么呢, 本节我们就来认识一下。

约定大于配置(Convention Over Configuration)Spring Boot 的核心设计理念之一,它的主要目的是减少开发者的配置负担,让开发者专注于业务逻辑的实现,而非繁杂的配置。

基本含义

约定大于配置(Convention Over Configuration)指的是:

  • 框架提供一套默认约定(默认规则或行为)。
  • 如果开发者遵循这些约定,就不需要进行额外的配置。
  • 如果开发者需要定制化,可以显式覆盖默认配置。

简单理解:如果你按照框架的约定方式开发,框架会自动提供合理的默认行为,避免你写大量的配置文件。

二、默认约定配置

spring-cloud-openfeign-core模块中, 添加了org.springframework.boot.autoconfigure.AutoConfiguration.imports) 配置文件

org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration
org.springframework.cloud.openfeign.FeignAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration

FeignAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class,FeignEncoderProperties.class })
public class FeignAutoConfiguration {/*** springcloud的NamedContextFactory中给子容器添加实例用的*/@Autowired(required = false)private List<FeignClientSpecification> configurations = new ArrayList<>();/*** 实例化feign子容器对象*/@Beanpublic FeignClientFactory feignContext() {FeignClientFactory context = new FeignClientFactory();// 设置子容器实例对象context.setConfigurations(this.configurations);return context;}
}

关于FeignClientFactory, 在父子容器的时候有介绍过, 它就是Feign接口父子容器的封装对象, 这里将解析出来的FeignClientSpecification添加到容器对象中去, 用于给子容器添加一些组件对象。

CachingCapability

/*** 对InvocationHandlerFactory缓存增强*/
@Bean
@ConditionalOnProperty(value = "spring.cloud.openfeign.cache.enabled", matchIfMissing = true)
@ConditionalOnBean(CacheInterceptor.class)
public Capability cachingCapability(CacheInterceptor cacheInterceptor) {return new CachingCapability(cacheInterceptor);
}

默认添加了一个对请求的增强CachingCapability, 这里注意@ConditionalOnProperty的定义, 它默认是开启的(matchIfMissing = true), 但是它需要一个CacheInterceptor对象, 这个默认是没有提供的,需要去注册一个, 否则启动会报错; 也可以使用spring.cloud.openfeign.cache.enabled=false来关闭这个bean。下面认识一下这个对象

public class CachingCapability implements Capability {private final CacheInterceptor cacheInterceptor;public CachingCapability(CacheInterceptor cacheInterceptor) {this.cacheInterceptor = cacheInterceptor;}@Overridepublic InvocationHandlerFactory enrich(InvocationHandlerFactory invocationHandlerFactory) {// 对InvocationHandlerFactory对增强return new FeignCachingInvocationHandlerFactory(invocationHandlerFactory, cacheInterceptor);}
}

CachingCapability实际就是对InvocationHandlerFactory对象做了增强, 使用FeignCachingInvocationHandlerFactory对象包装原始的InvocationHandlerFactorycacheInterceptor

FeignCachingInvocationHandlerFactory

public class FeignCachingInvocationHandlerFactory implements InvocationHandlerFactory {/*** 代理的InvocationHandlerFactory对象*/private final InvocationHandlerFactory delegateFactory;/*** 缓存拦截器*/private final CacheInterceptor cacheInterceptor;@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {// 这里用feign的InvocationHandlerFactory对象构建InvocationHandlerfinal InvocationHandler delegateHandler = delegateFactory.create(target, dispatch);return (proxy, method, argsNullable) -> {Object[] args = Optional.ofNullable(argsNullable).orElseGet(() -> new Object[0]);return cacheInterceptor.invoke(new MethodInvocation() {// ... 省略部分方法@Overridepublic Object proceed() throws Throwable {return delegateHandler.invoke(proxy, method, args);}});};}
}

FeignCachingInvocationHandlerFactory的实际作用就是创建了一个匿名的java.lang.reflect.InvocationHandler, 在实际调用的时候使用CacheInterceptor去执行请求, 所以这个核心就是把CacheInterceptorInvocationHandler对应起来, 然后在CacheInterceptor内部做一些处理后再执行真正的代理工作

CacheInterceptor对象是org.springframework.cache.interceptor包下的一个对象, 它可以对指定参数的结果做缓存, 多次调用的时候不需要重复做真实调用, 直接从缓存中获取结果即可

举个例子

// 服务端接口
@PostMapping("/configDemo/getPerson2")
public Person getPerson2(@RequestBody Person person) {System.out.println("uncleqiao 收到body:" + person);person.setName("小杜同学");person.setAge(20);return person;
}/*** 定义一个客户端缓存拦截器*/
@Configuration
public class MyCacheInterceptorConfig {@Bean@ConditionalOnMissingBean(CacheOperationSource.class)public CacheOperationSource cacheOperationSource() {// 对缓存相关的注解生效return new AnnotationCacheOperationSource(false);}@Beanpublic org.springframework.cache.interceptor.CacheInterceptor myCacheInterceptor(CacheOperationSource cacheOperationSource) {CacheInterceptor cacheInterceptor = new CacheInterceptor();cacheInterceptor.setCacheOperationSource(cacheOperationSource);return cacheInterceptor;}
}// feign接口
@FeignClient(contextId = "urlDemoRemote", url = "localhost:8080", configuration = {MyContextDecoder.class, MyContextEncode.class}, name = "url-demo", path = "/configDemo")
public interface UrlDemoRemote {@PostMapping(value = "/getPerson2", consumes = "application/json")@Cacheable(cacheNames = "demoCache", key = "'getPerson2'+#p0.name")Person getPerson2(Person person);
}// 测试类
@Test
void acheInterceptorTest() {Person person = urlDemoRemote.getPerson2(new Person("小乔同学", 18, 1, LocalDate.now()));System.out.println(person);Person person2 = urlDemoRemote.getPerson2(new Person("小乔同学", 18, 1, LocalDate.now()));System.out.println(person2);
}

结果

服务端打印一次, 客户端打印两次, 即实际只发送了一次请求

// 服务端打印
Person(name=小杜同学, age=20, gender=1, birthday=2024-12-17)// 客户端打印
Person(name=小杜同学, age=20, gender=1, birthday=2024-12-17)
Person(name=小杜同学, age=20, gender=1, birthday=2024-12-17)

注意spring的缓存默认是用hashMap存在本地, 可以通过自定义cacheManager和Cache对象的手段实现将缓存数据存到其它地方, 例如redis

FeignJacksonConfiguration

/*** jackson配置*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Module.class, Page.class, Sort.class })
@ConditionalOnProperty(value = "spring.cloud.openfeign.autoconfiguration.jackson.enabled", havingValue = "true",matchIfMissing = true)
protected static class FeignJacksonConfiguration {@Bean@ConditionalOnMissingBean(PageJacksonModule.class)public PageJacksonModule pageJacksonModule() {return new PageJacksonModule();}@Bean@ConditionalOnMissingBean(SortJacksonModule.class)public SortJacksonModule sortModule() {return new SortJacksonModule();}}

默认也提供了对jackson的支持, 这里仅仅是添加了两个Moudle, PageJacksonModule和SortJacksonModule; 分页和排序

DefaultFeignTargeterConfiguration

@Configuration(proxyBeanMethods = false)
@Conditional(FeignCircuitBreakerDisabledConditions.class)
protected static class DefaultFeignTargeterConfiguration {@Bean@ConditionalOnMissingBeanpublic Targeter feignTargeter() {return new DefaultTargeter();}
}class DefaultTargeter implements Targeter {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,Target.HardCodedTarget<T> target) {return feign.target(target);}}

默认使用的target对象; 作为一个默认实现, DefaultTargeter本身啥也没做

熔断器配置

/*** 熔断器配置*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CircuitBreaker.class)
@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "true")
protected static class CircuitBreakerPresentFeignTargeterConfiguration {// 默认配置, 当没有CircuitBreakerFactory时生效@Bean@ConditionalOnMissingBean(CircuitBreakerFactory.class)public Targeter defaultFeignTargeter() {return new DefaultTargeter();}/*** 1.需要配置CircuitBreakerFactory的bean* 2. spring.cloud.openfeign.circuitbreaker.group.enabled=true 来设置circuitBreakerGroupEnabled为true*/@SuppressWarnings("rawtypes")@Bean@ConditionalOnMissingBean@ConditionalOnBean(CircuitBreakerFactory.class)public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,@Value("${spring.cloud.openfeign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,CircuitBreakerNameResolver circuitBreakerNameResolver) {return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,circuitBreakerNameResolver);}}
  1. 它需要设置spring.cloud.openfeign.circuitbreaker.enabled=true来开启

  2. 当没有配置CircuitBreakerFactory对象时, 默认使用的是DefaultTargeter包装了feign的Target对象

  3. 当配置了CircuitBreakerFactory对象时, 使用FeignCircuitBreakerTargeter包装feign的Target对象

FeignCircuitBreakerTargeter

class FeignCircuitBreakerTargeter implements Targeter {/*** 创建CircuitBreaker的工厂*/private final CircuitBreakerFactory circuitBreakerFactory;private final boolean circuitBreakerGroupEnabled;/*** CircuitBreaker名称解析器*/private final CircuitBreakerNameResolver circuitBreakerNameResolver;}

它有三个属性

  1. circuitBreakerFactory: 用来创建CircuitBreaker对象的工厂
  2. circuitBreakerGroupEnabled:
  3. circuitBreakerNameResolver: CircuitBreaker名称的解析器

并且实现了Targeter接口的target方法

@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignClientFactory context,Target.HardCodedTarget<T> target) {// 这里如果Feign.Builder不是FeignCircuitBreaker.Builder类型, 直接就执行并返回了if (!(feign instanceof FeignCircuitBreaker.Builder builder)) {return feign.target(target);}// 客户端名称,同时它也是容器的名称; 默认是contextId, 否则取nameString name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();// @FeignClient注解的fallback属性对象Class<?> fallback = factory.getFallback();// 如果配置了if (fallback != void.class) {return targetWithFallback(name, context, target, builder, fallback);}// @FeignClient注解的fallbackFactory属性对象Class<?> fallbackFactory = factory.getFallbackFactory();// 如果配置了if (fallbackFactory != void.class) {return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);}// 到这里就是没有熔断对象// 1.填充Feign.Builder的属性  2.构建代理对象return builder(name, builder).target(target);
}

下面看一下targetWithFallbacktargetWithFallbackFactory方法

targetWithFallback

// 
private <T> T targetWithFallback(String feignClientName, FeignClientFactory context,Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallback) {// 从父子容器中获取fallback对象, 它必须是feign接口的实现类T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());return builder(feignClientName, builder).target(target, fallbackInstance);
}// 从容器中获取fallbackFactoryClass对象
private <T> T targetWithFallbackFactory(String feignClientName, FeignClientFactory context,Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {// 从父子容器中获取FallbackFactory对象, 它必须是FallbackFactory接口的实现类FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",feignClientName, context, fallbackFactoryClass, FallbackFactory.class);return builder(feignClientName, builder).target(target, fallbackFactory);
}// 从容器feignClientName中获取指定类型beanType的实例, 并且它必须是targetType的子类
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignClientFactory context, Class<?> beanType, Class<T> targetType) {// 父子容器中获取beanType类型的对象Object fallbackInstance = context.getInstance(feignClientName, beanType);// 对象还必须存在if (fallbackInstance == null) {throw new ...}// 该对象是工厂if (fallbackInstance instanceof FactoryBean<?> factoryBean) {try {fallbackInstance = factoryBean.getObject();}catch (Exception e) {...}}else {// fallback不是接口类型, 直接抛异常if (!targetType.isAssignableFrom(beanType)) {throw new ...}}return (T) fallbackInstance;}

方法小结

  1. 这里要求我们传入的Feign.Builder对象必须是FeignCircuitBreaker.Builder对象, 才会做熔断的相关动作, 否则直接就给调用了
  2. 客户端的名称(容器名称)取值顺序为 contextId>name
  3. 如果配置了@FeignClient注解的fallback属性对象, 那么从父子容器中获取该对象, 该对象必须是feign目标接口的子类
  4. 如果配置了@FeignClient注解的fallbackFactory属性对象, 那么从父子容器中获取该对象, 该对象必须是FallbackFactory的子类
  5. 填充FeignCircuitBreaker.Builder对象的属性
  6. 创建接口的实例对象

上面是先处理FeignCircuitBreaker.Builder, 再构建feign接口实例对象, 那么我们先来认识一下FeignCircuitBreaker.Builder

FeignCircuitBreaker.Builder

public final class FeignCircuitBreaker {public static final class Builder extends Feign.Builder {private CircuitBreakerFactory circuitBreakerFactory;private String feignClientName;private boolean circuitBreakerGroupEnabled;private CircuitBreakerNameResolver circuitBreakerNameResolver;// 省略部分setter方法// 如果fallback存在, 默认构建成FallbackFactory.Default对象public <T> T target(Target<T> target, T fallback) {return build(fallback != null ? new FallbackFactory.Default<>(fallback) : null).newInstance(target);}public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {return build(fallbackFactory).newInstance(target);}@Overridepublic <T> T target(Target<T> target) {return build(null).newInstance(target);}public Feign build(final FallbackFactory<?> nullableFallbackFactory) {// 设置InvocationHandlerFactory对象super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,circuitBreakerGroupEnabled, circuitBreakerNameResolver));return super.build();}}
}

它实际上也是Feign.Builder的子类, 覆盖了target方法

  1. @FeignClient的fallback属性默认也会包装成FallbackFactory对象,这里是FallbackFactory.Default, 而@FeignClient的fallbackFactory需要自己实现创建熔断对象
  2. 覆盖的target方法使用熔断对象(FallbackFactory)创建了一个匿名InvocationHandlerFactory对象设置到了Feign.Builder

通过前面的学习, 我们现在是知道InvocationHandlerFactory是用来创建jdk动态代理的方法代理句柄的, 它具备实际调用的回调能力, 我们了解一下这个匿名的InvocationHandlerFactory对象

匿名InvocationHandlerFactory

先回顾一下InvocationHandlerFactory对象

public interface InvocationHandlerFactory {InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);// 省略其它内容
}

它仅提供了抽象方法create, 用来创建jdk的方法调用句柄InvocationHandler, 并接受Target和方法句柄对象。那么我们就可以使用匿名类型的lambda表达式表示这个接口的实现

(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,circuitBreakerGroupEnabled, circuitBreakerNameResolver))

所以当调用InvocationHandlerFactory.create方法的时候, 其实就是调用的new FeignCircuitBreakerInvocationHandler动作

class FeignCircuitBreakerInvocationHandler implements InvocationHandler {@Overridepublic Object invoke(final Object proxy, final Method method, final Object[] args) {// early exit if the invoked method is from java.lang.Object// code is the same as ReflectiveFeign.FeignInvocationHandlerif ("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();}// 获取熔断名称, 默认是[feignClientName-methodName]String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);// 创建熔断对象CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName): factory.create(circuitName);// 熔断对象执行方法Supplier<Object> supplier = asSupplier(method, args);// 回调方法不为空if (this.nullableFallbackFactory != null) {Function<Throwable, Object> fallbackFunction = throwable -> {// 回调对象Object fallback = this.nullableFallbackFactory.create(throwable);try {// 调用回调方法return this.fallbackMethodMap.get(method).invoke(fallback, args);}catch (Exception exception) {unwrapAndRethrow(exception);}return null;};// 熔断器执行return circuitBreaker.run(supplier, fallbackFunction);}// 熔断器执行, 没有回调return circuitBreaker.run(supplier);}
}

方法小结

  1. 提供了对equals, hashCode, toString的支持
  2. 使用CircuitBreakerFactory创建熔断器对象CircuitBreaker
  3. 封装目标执行方法为Supplier<Object>
  4. 如果有熔断回调对象nullableFallbackFactory, 则使用circuitBreaker.run(supplier, fallbackFunction);执行, 支持对熔断的回到
  5. 如果没有熔断回调对象, 熔断器直接执行目标方法circuitBreaker.run(supplier)

这里看一下对目标方法的封装

private Supplier<Object> asSupplier(final Method method, final Object[] args) {// 获取当前线程的请求属性final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();final Thread caller = Thread.currentThread();return () -> {// 是否是异步调用boolean isAsync = caller != Thread.currentThread();try {// 设置请求属性if (isAsync) {RequestContextHolder.setRequestAttributes(requestAttributes);}// 调用方法句柄return dispatch.get(method).invoke(args);}catch (RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new RuntimeException(throwable);}finally {// 清除请求属性if (isAsync) {RequestContextHolder.resetRequestAttributes();}}};
}

这个方法比较简单, 异步的时候设置当前上下文请求属性到异步现成中, 然后执行目标方法, 这里判断异步动作是比较巧妙的。

因为返回的Supplier对象是个可执行请求的方法, 它可能同步执行也可能异步执行, 如果是同步执行, 那么不管后面执行动作是在哪里发生, 那么当前线程和执行时候的线程会是同一个线程, 如果放在异步中执行, 那么当前线程和这段lambda表达的Supplier将会在不同的线程中, caller != Thread.currentThread()就会成立。

FeignClientsConfiguration

上面介绍到FeignCircuitBreakerTargeter#target方法如果Feign.Builder不是FeignCircuitBreaker.Builder类型时, 它不会走构建熔断回调部分, 那么FeignCircuitBreaker.Builder对象怎么来的呢?

上面说到, 想要开启熔断回调的第一步就是配置spring.cloud.openfeign.circuitbreaker.enabled=true, 并且前面介绍父子容器的时候知道FeignClientFactory对象在创建的时候会将FeignClientsConfiguration作为defaultConfigType注入到每个子容器中, 而FeignClientsConfiguration中有个bean定义如下

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {// .... 省略其它配置/*** 这个条件FeignCircuitBreakerDisabledConditions默认为true* spring.cloud.openfeign.circuitbreaker.enabled为false或者不配置时, 这里生效, 为true时不生效*/@Configuration(proxyBeanMethods = false)@Conditional(FeignCircuitBreakerDisabledConditions.class)protected static class DefaultFeignBuilderConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBeanpublic Feign.Builder feignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}}// spring.cloud.openfeign.circuitbreaker.enabled为true时, 这里生效@Configuration(proxyBeanMethods = false)@ConditionalOnClass(CircuitBreaker.class)@ConditionalOnProperty("spring.cloud.openfeign.circuitbreaker.enabled")protected static class CircuitBreakerPresentFeignBuilderConfiguration {// 1. 没有配置Feign.Builder对象 并且 2.没有配置CircuitBreakerFactory对象@Bean@Scope("prototype")@ConditionalOnMissingBean({ Feign.Builder.class, CircuitBreakerFactory.class })public Feign.Builder defaultFeignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}// 配置了CircuitBreakerFactory对象@Bean@Scope("prototype")@ConditionalOnMissingBean@ConditionalOnBean(CircuitBreakerFactory.class)public Feign.Builder circuitBreakerFeignBuilder() {return FeignCircuitBreaker.builder();}}
}

这里先看这个条件@Conditional(FeignCircuitBreakerDisabledConditions.class)

/*** 当满足以下任一条件时, 该condition 生效*/
class FeignCircuitBreakerDisabledConditions extends AnyNestedCondition {FeignCircuitBreakerDisabledConditions() {// 应用在@Configuration注解中super(ConfigurationPhase.PARSE_CONFIGURATION);}/*** 条件1: 断路器CircuitBreaker不存在* 默认存在, 为false*/@ConditionalOnMissingClass("org.springframework.cloud.client.circuitbreaker.CircuitBreaker")static class CircuitBreakerClassMissing {}/*** 条件2: 断路器CircuitBreaker显示指定未不开启* 没有配置的话, 默认为true*/@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "false",matchIfMissing = true)static class CircuitBreakerDisabled {}
}

当满足如下一个条件时, FeignCircuitBreakerDisabledConditions为true

  1. 当不存在org.springframework.cloud.client.circuitbreaker.CircuitBreaker.class, 为true; 默认是存在的, 所以默认值是false
  2. spring.cloud.openfeign.circuitbreaker.enabled配置为true, 或者没有配置是为true; 默认是没有配置的, 所以是true

我们平时用的多个@Condition标注在@Bean的方法或者@Configuration的类上, 都属于并且条件, 也就是A成立并且B成立; 这里的AnyNestedCondition就是A成立或者B成立。

所以@Conditional(FeignCircuitBreakerDisabledConditions.class)默认是true, 当@ConditionalOnMissingClass("org.springframework.cloud.client.circuitbreaker.CircuitBreaker")为false, 并且@ConditionalOnProperty(value = "spring.cloud.openfeign.circuitbreaker.enabled", havingValue = "false", matchIfMissing = true)为false时, @Conditional(FeignCircuitBreakerDisabledConditions.class)才为false, 也就是存在一个CircuitBreaker对象, 并且设置spring.cloud.openfeign.circuitbreaker.enabled=false@Conditional(FeignCircuitBreakerDisabledConditions.class)条件会不生效

所以如下配置默认是生效的, 默认使用的是Feign.Builder对象

/*** 这个条件FeignCircuitBreakerDisabledConditions默认为true*/
@Configuration(proxyBeanMethods = false)
@Conditional(FeignCircuitBreakerDisabledConditions.class)
protected static class DefaultFeignBuilderConfiguration {@Bean@Scope("prototype")@ConditionalOnMissingBeanpublic Feign.Builder feignBuilder(Retryer retryer) {return Feign.builder().retryer(retryer);}
}

当配置spring.cloud.openfeign.circuitbreaker.enabled为true时, DefaultFeignBuilderConfiguration不会被装配, 而CircuitBreakerPresentFeignBuilderConfiguration会被装配。

当配置spring.cloud.openfeign.circuitbreaker.enabled为true, 并且配置了CircuitBreakerFactory的bean对象, 那么将会使用FeignCircuitBreaker.Builder对象; 那么CircuitBreakerFactory是什么呢?? 下面介绍

CircuitBreakerFactory

public abstract class CircuitBreakerFactory<CONF, CONFB extends ConfigBuilder<CONF>>extends AbstractCircuitBreakerFactory<CONF, CONFB> {public abstract CircuitBreaker create(String id);public CircuitBreaker create(String id, String groupName) {return create(id);}
}

它提供了创建CircuitBreaker的方法;

如果我们配置了CircuitBreakerFactory的bean, 并且配置spring.cloud.openfeign.circuitbreaker.enabled为true, 那么是不是就和CircuitBreakerPresentFeignTargeterConfiguration#circuitBreakerFeignTargeter呼应上了

@SuppressWarnings("rawtypes")
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,@Value("${spring.cloud.openfeign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,CircuitBreakerNameResolver circuitBreakerNameResolver) {return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,circuitBreakerNameResolver);
}

并且FeignCircuitBreakerInvocationHandler#invoke中的创建CircuitBreaker的部分是不是也就有值了

class FeignCircuitBreakerInvocationHandler implements InvocationHandler {/*** 创建熔断的工厂*/private final CircuitBreakerFactory factory;public Object invoke(final Object proxy, final Method method, final Object[] args) {// 创建熔断对象CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName): factory.create(circuitName);}
}

最后我们再看一下CircuitBreaker对象

public interface CircuitBreaker {default <T> T run(Supplier<T> toRun) {return run(toRun, throwable -> {throw new NoFallbackAvailableException("No fallback available.", throwable);});}// p1: 目标执行方法  p2:异常回调<T> T run(Supplier<T> toRun, Function<Throwable, T> fallback);
}

它提供一个默认方法和一个抽象方法, 如果我们要实现它的话, 就需要实现<T> T run(Supplier<T> toRun, Function<Throwable, T> fallback);方法。

例如

/*** 自定义创建CircuitBreaker的工厂*/
@Configuration
public class MyCircuitBreakerFactory extends CircuitBreakerFactory {@Overridepublic CircuitBreaker create(String id) {System.out.println("子容器id==" + id);return new MyCircuitBreaker();}@Overrideprotected ConfigBuilder configBuilder(String id) {return null;}@Overridepublic void configureDefault(Function defaultConfiguration) {}
}/*** 自定义熔断器*/
public class MyCircuitBreaker implements CircuitBreaker {private AtomicLong failureCount = new AtomicLong();@Overridepublic <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {try {// 调用目标方法return run(toRun);}catch (Throwable throwable) {// 记录失败次数failureCount.incrementAndGet();// 熔断回调return fallback.apply(throwable);}}
}

总结

  1. springcloud_openfeign通过自动装配引入了FeignAutoConfiguration配置类
  2. 注入了对父子容器支持的FeignClientFactory对象
  3. 注入CachingCapability对InvocationHandlerFactory的增强, 可以对返回值做缓存处理; 可以通过spring.cloud.openfeign.cache.enabled=false关闭该配置
  4. 默认注入了jackson的两个module, PageJacksonModule和SortJacksonModule; 可以通过spring.cloud.openfeign.autoconfiguration.jackson.enabled=false关闭
  5. 熔断器配置, 满足一下两个配置
  • 配置spring.cloud.openfeign.circuitbreaker.enabled=true
  • 自定义CircuitBreakerFactory的bean
  1. 熔断回调对象fallback和fallbackFactory; 它们是从父子容器中获取的
  • fallback配置的对象必须是feign接口的子类
  • fallbackFactory配置的对象必须是FallbackFactory接口的实现类, 并且这个factory创建的对象必须是feign接口的子类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/64572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python读取Excel批量写入到PPT生成词卡

一、问题的提出 有网友想把Excel表中的三列数据&#xff0c;分别是&#xff1a;单词、音标和释义分别写入到PPT当中&#xff0c;每一张PPT写一个单词的内容。这种批量操作是python的强项&#xff0c;尤其是在办公领域&#xff0c;它能较好地解放双手&#xff0c;读取Excel表后…

Proteus(8.15)仿真下载安装过程(附详细安装过程图)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、Proteus是什么&#xff1f; 二、下载链接 三、下安装步骤 1.解压&#xff0c;有键管理员运行 2.点击Next&#xff0c;进行下一步 3.勾选I accept…&#…

防止私接小路由器

电脑获取到IP地址不是DHCP服务器的IP地址段&#xff0c;导致整个公司网络瘫痪&#xff0c;这些故障现象通常80%原因是私接小路由器导致的&#xff0c;以下防止私接小路由器措施。 一、交换机配置DHCP Sooping DHCP snooping是一种DHCP安全特性&#xff0c;用于防止非法设备获…

动态导出word文件支持转pdf

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、功能说明二、使用步骤1.controller2.工具类 DocumentUtil 导出样式 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff…

紧固件设计之——开槽六角头防脱出杆螺栓仿真APP

按照产品形态分类&#xff0c;紧固件通常包括以下12类&#xff1a;螺栓、螺柱、螺钉、螺母、自攻螺钉、木螺钉、垫圈、挡圈、销、铆钉、焊钉、组合件与连接副&#xff0c;是一类用于连接和固定各种构件和零部件的重要机械零件&#xff0c;可确保机械装置或设备结构的牢固和稳定…

【Python装饰器】编写一个装饰器,并将其放到适当的位置,目的是让代码 1 秒钟打印一个结果

import timedef fib():back1, back2 0, 1def func():nonlocal back1, back2back1, back2 back2, back1 back2print(back1, end )return funcdef get_fib(n):f fib()for i in range(n):f()n int(input("请输入需要获取的斐波那契数&#xff1a;"))get_fib(n) imp…

mysql中与并发相关的问题?

今天我们来聊聊 MySQL 中与并发相关的一些问题。作为一名资深 Python 开发工程师&#xff0c;我觉得这些问题不仅关乎数据库的稳定性和数据的一致性&#xff0c;更与我们的代码实现和业务逻辑密切相关。 尤其是在高并发环境下&#xff0c;如何保证数据的一致性&#xff0c;如何…

【Mac】安装 PaddleOCR

环境&#xff1a;Mac M1 芯片 1、安装 Anaconda 安装较为简单&#xff0c;直接在 Anaconda 官网 下载pkg文件&#xff0c;根据向导提示完成安装。 Anaconda 用于搭建 Python 虚拟环境&#xff0c;目的是为了避免与之前环境安装库的版本冲突&#xff0c;另外 paddle 对Python…

使用k6进行kafka负载测试

1.安装环境 kafka环境 参考Docker搭建kafka环境-CSDN博客 xk6-kafka环境 ./xk6 build --with github.com/mostafa/xk6-kafkalatest 查看安装情况 2.编写脚本 test_kafka.js // Either import the module object import * as kafka from "k6/x/kafka";// Or in…

服务器ip:port服务用nginx 域名代理

ubuntu 1、安装nginx # 更新软件包列表 sudo apt update# 安装Nginx sudo apt install nginx -y# 检查Nginx状态 sudo systemctl status nginx2、创建存放域名 SSL证书的目录 # 创建目录 sudo mkdir -p /etc/nginx/ssl# 复制证书文件到该目录 sudo cp play.cn_bundle.crt /et…

[机器学习]XGBoost(3)——确定树的结构

XGBoost的目标函数详见[机器学习]XGBoost&#xff08;2&#xff09;——目标函数&#xff08;公式详解&#xff09; 确定树的结构 之前在关于目标函数的计算中&#xff0c;均假设树的结构是确定的&#xff0c;但实际上&#xff0c;当划分条件不同时&#xff0c;叶子节点包含的…

springboot444新冠物资管理系统的设计与实现(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装新冠物资管理系统软件来发挥其高效地信息处理的作用&#x…

Javascript-web API-day02

文章目录 01-事件监听02-点击关闭广告03-随机点名案例04-鼠标经过或离开事件05-可点击的轮播图06-小米搜索框07-键盘类型事件08-键盘事件-发布评论案例09-focus选择器10-评论回车发布11-事件对象12-trim方法13-环境对象14-回调函数15-tab栏切换 01-事件监听 <!DOCTYPE html…

使用xjar 对Spring-Boot JAR 包加密运行

1 Xjar 介绍 Spring Boot JAR 安全加密运行工具&#xff0c;同时支持的原生JAR。 基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动&#xff0c;动态解密运行的方案&#xff0c;避免源码泄露或反编译。 功能特性 无需侵入代码&#xff0c;只需要把编译好的…

深度学习的下一站:解锁人工智能的新边界

引言&#xff1a;新边界的呼唤 深度学习的诞生&#xff0c;犹如人工智能领域的一次革命&#xff0c;激发了语音助手、自动驾驶、智能医疗等前沿技术的飞速发展。然而&#xff0c;面对现实世界的复杂性&#xff0c;现有的深度学习模型仍然存在数据依赖、可解释性差、环境适应力不…

基于DockerCompose搭建Redis主从哨兵模式

linux目录结构 内网配置 哨兵配置文件如下&#xff0c;创建3个哨兵配置文件 # sentinel26379.conf sentinel26380.conf sentinel26381.conf 内容如下 protected-mode no sentinel monitor mymaster redis-master 6379 2 sentinel down-after-milliseconds mymaster 60000 s…

Vite 与 Webpack 的区别

在前端开发中&#xff0c;构建工具是不可或缺的&#xff0c;Webpack 和 Vite 是当前最流行的选择之一。尽管它们的目标相似&#xff0c;但在实现方式和开发体验上却有显著差异。本文将探讨 Vite 和 Webpack 的主要区别&#xff0c;以便于根据项目需求选择合适的工具。 1. 构建…

upload-labs靶场1-19关

第 1 关&#xff08;删除前端js校验&#xff09; 点击第一关&#xff0c;我们可以看到页面上传区可以上传一个图片&#xff0c;我们要上传一个 webshell&#xff0c;这里我们上传一句话木马的 php 点击上传 显示文件不支持上传&#xff0c;这时我们查看源码 查看代码后发现&am…

vue3+vite 引入动画组件库 Inspira UI

关于Inspira UI Inspira UI不是传统的组件库。相反&#xff0c;它是精选的优雅组件集合&#xff0c;您可以轻松将其集成到您的应用程序中。只需选择所需的组件&#xff0c;复制代码&#xff0c;然后自定义以适合您的项目即可。您可以随意使用和修改代码&#xff01; 官网地址…

Go语言启动独立进程

文章目录 问题解决方案1. **将 npc.exe 启动为独立的进程**2. **修改 exec.Command 函数**示例代码解释为什么这样有效注意 问题 在你当前的代码中&#xff0c;调用 exec.Command("XXX.exe") 启动 XXX.exe 程序时&#xff0c;这个程序是由 Go 程序直接启动的。如果 …