This is the fifth day of my participation in the More text Challenge. For details, see more text Challenge
General documentation: Article directory Github: github.com/black-ant
A. The preface
Article purpose:
- Feign main process source analysis
- Feign’s key points
- Design ideas and diffusion thinking at Feign
2. Source code combing
In the most basic OpenFeign example, we usually do the following:
// Start the Feign client
@EnableFeignClients
@SpringBootApplication
public class ComGangCloudTemplateOrderApplication {... }// Step 2: Prepare FeignClient
@Component
@FeignClient("product-server")
public interface ProductFeignClient {
@GetMapping("/template/get")
CloudTemplateEntity get(@RequestParam("desc") String desc);
}
// Step 3: Call FeignClient object
@Autowired
private ProductFeignClient productFeignClient;
productFeignClient.get("order-server")
Copy the code
Looking at these three steps, we can probably get a few questions:
- What is the role of EnableFeignClients?
- @ FeignClient scanning
2.1 EnableFeignClients using @enablefeignclients
The key configuration is in the FeignClientsRegistrar and will include the following:
First look at the call process for FeignClientsRegistrar:
- RegisterBeanDefinitions by calling registerBeanDefinitions when the Bean is loaded
- Step 2: registerDefaultConfiguration registered config
- Step 3: Scan FeignClient and register
- Step 4: Register all scanned FeignClients
RegisterBeanDefinitions by calling registerBeanDefinitions when the Bean is loaded
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// There are two main steps involved:
// 1. Register Configuration
registerDefaultConfiguration(metadata, registry);
// 2. Register Feign Client
registerFeignClients(metadata, registry);
}
Copy the code
Step 2: registerDefaultConfiguration registered config
// 1. Get attribute above annotation -> PS:001
Map<String, Object> defaultAttrs =
metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
// 2. Register BeanDefinitionBuilder (PS: This step actually builds a BeanDefinitionBuilder)
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
Definitionbuilder (DefinitionBuilder); // Define BeanDefinitions using the constructor pattern
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
// name : default.com.gang.cloud.template.demo.ComGangCloudTemplateOrderApplication
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// default.com.gang.cloud.template.demo.ComGangCloudTemplateOrderApplication.FeignClientSpecification
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition()
);
}
Copy the code
PS: 001 defaultAttrs parameters
Step 3: Scan FeignClient
The overall outline is:
- Get the attribute attrs from EnableFeignClients
- Obtain the basic scan path basePackages through the attribute attrs
- Scan all classes labeled @Component under basePackages
- Get the @feignClient in there
- Registry the class annotated by the annotation
// The annotation class is FeignClientsRegistrarC05- FeignClientsRegistrar(AnnotationMetadata metadata,BeanDefinitionRegistry registry) M5_01- registerFeignClients : FeignClient core mode - ready to object ClassPathScanningCandidateComponentProvider - get marked EnableFeignClients Map collections - build a AnnotationTypeFilter ? - why not use getAnnotationAttributes to get the related class directly -> PS:M5_01_01 - getBasePackages to get the path Set<String>->LV001 FOR- LV001 - findCandidateComponents ->LV002? -> PS:M5_01_02 FOR- loop BeanDefinition: LV002 - Get FeignClient attribute Map<String, Object > - > LV003 - registerClientConfiguration currently registered configuration information - registerFeignClient FeignClient M5_02 - registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) ? - The core function is to construct a BeanDefined, which can be divided into4Step - > PS: M5_02_011- BeanDefinitionBuilder. GenericBeanDefinition build a BeanDefinitionBuilder2- definition. GetBeanDefinition () to build a AbstractBeanDefinition3- newBeanDefinitionHolder {DefinitionHolder;4- BeanDefinitionReaderUtils. RegisterBeanDefinition inject beansCopy the code
That’s the main process, but let’s take a look at a few small details:
// PS: omitted part of the code, you can see the full version of the source code
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// Prepare scan objects for scanning
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// This is an exclusion filter for Scan. It will mask useless beans
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
if (clients == null || clients.length == 0) {
// Add an exclusion filter -> PS:0002
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
} else {
// Omit, again for special matching
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// Get the attribute -> PS:0003
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
/ / register FeignClientregisterFeignClient(registry, annotationMetadata, attributes); }}}}// PS:0002 excludes the use of the filter
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.includeFilters) {
/ /...}}Copy the code
Step 3: Scan FeignClient
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map
attributes)
,> {
String className = annotationMetadata.getClassName();
/ / prepare BeanDefinitionBuilder - > org. Springframework. Cloud. Openfeign. FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// Check whether the attribute is valid
validate(attributes);
// Omit all addPropertyValue operations, which add properties to definition
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// Since there is a default BeanDefinition, this is overwritten by primary
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// Register the given bean definition with the given bean factory
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code
PS: This object is called during the Bean creation process, we will talk about it later!!
2.2 FeignAutoConfiguration configuration
Feign actually supports OKHttp calls. This method is configured in the FeignAutoConfiguration class, which provides two connection frames: HttpClientFeignConfiguration / OkHttpFeignConfiguration
/ / HttpClientFeignConfiguration configuration items
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory,FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
// Maximum number of connections
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(),
// Connect the lifetimes and units
httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(),
this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run(a) { connectionManager.closeExpiredConnections(); }},30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder()
.setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
// Configuration items of OkHttpFeignConfiguration
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool( FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
// OKHttp connection pool property
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).connectionPool(connectionPool)
.build();
return this.okHttpClient;
}
Copy the code
So here’s the question: how do you switch to OKHttp?
PS: There is a method on the Internet, through the configuration of okHttp3.okHttpClient method, but after personal testing, may be due to the version of different problems
// If you look closely at the source code, there are two OkHttpFeignConfiguration
/ / a within FeignAutoConfiguration, another exists in org. Springframework. Cloud. Openfeign. Clientconfig package
// Suspicious: click FeignAutoConfiguration$OkHttpFeignConfiguration. There are multiple conditionals on the configuration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration {
/ /...
}
// [Pro] : if you pass the OnClassCondition test, you will find that ILoadBalancer exists, and the configuration under this class will not be loadedIn org. Springframework. Cloud. Openfeign. Clientconfig configuration under the package, have the following requirements@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
// Therefore, the known configuration will actually cause both configuration items to stay
/ /!!!!!! So how do you configure it?
// Step 1: Add eign-okhttp configuration
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
// Step 2: Import the configuration file on Application
@Import(value = {OkHttpFeignConfiguration.class})
// Step 3: Register Client in config
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureAfter(value = {FeignAutoConfiguration.class, OkHttpFeignConfiguration.class})
public class FeignOkHttpConfig {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(okhttp3.OkHttpClient client) {
// There are some related configurations, which will be omitted temporarily and added when writing the process
return new OkHttpLoadBalancingClient(....);
}
}
Copy the code
conclusion
That’s all for the Configuration section of Feign, which explains the creation of a FeignBean and an Invoke process
The appendix
PS:0003 Attributes include what attributes?
C- FeignClient
M- String value(a) default ""
M- String serviceId(a) default ""
M- String contextId(a) default"" : If present, it will be used as the bean name rather than the name, but not as the service ID. M- Stringname(a) default ""
M- String qualifier(a) default ""
M- String url(a) default ""
M- boolean decode404(a) default falseM- Class<? > []configuration(a): A configuration class customized for the virtual client. M- Class<? >fallback(a)M- Class<? >fallbackFactory(a)
M- String path(a) default ""
M- boolean primary(a) default true
Copy the code
Common FeignClient configuration
// Override the default configuration
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
link : https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-overriding-defaults
// Obtain the rollback cause
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
// Obtain the cause of triggering the rollback
@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",fallbackFactory = TestFallbackFactory.class)
// Configure the main Bean
// PS: When using Feign and Spring Cloud CircuitBreaker to roll back, there are multiple beans in the same type of ApplicationContext
@FeignClient(name = "hello", primary = false)
Copy the code
Application configuration article
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
metrics.enabled: false
// Debug get/set to see the node in use
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
// Disable the default value for SSL authentication
public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
// The default value of the maximum NUMBER of od connections
public static final int DEFAULT_MAX_CONNECTIONS = 200;
// The default maximum number of connections for each route
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
// The default value of the time to live
public static final long DEFAULT_TIME_TO_LIVE = 900L;
// Default survival time unit.
public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;
// Whether to redirect
public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
// The default value for connection timeout
public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
// Connect the timer to repeat the default values
public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;
private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION;
private int maxConnections = DEFAULT_MAX_CONNECTIONS;
private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
private long timeToLive = DEFAULT_TIME_TO_LIVE;
private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;
private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
}
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
private String defaultConfig = "default";
private Map<String, FeignClientConfiguration> config = new HashMap<>();
}
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;
/ /...
}
// Step 1: Config-configured classes
FeignClientConfiguration
FeignHttpClientProperties
FeignClientProperties
// Step 2: The configuration position
C- FeignClientFactoryBean
M- configureUsingProperties
M- configureUsingConfiguration
Copy the code