Column series: SpringCloud column series
Series of articles:
SpringCloud source series (1) – registry initialization for Eureka
SpringCloud source code series (2) – Registry Eureka service registration, renewal
SpringCloud source code series (3) – Registry Eureka crawl registry
SpringCloud source code series (4) – Registry Eureka service offline, failure, self-protection mechanism
SpringCloud source series (5) – Registry EurekaServer cluster for Eureka
SpringCloud source Code Series (6) – Summary of the Registry Eureka
SpringCloud source series (7) – load balancing Ribbon RestTemplate
SpringCloud source series (8) – load balancing Ribbon core principles
SpringCloud source series (9) – load balancing Ribbon core components and configuration
SpringCloud source Series (10) – HTTP client component of load balancing Ribbon
SpringCloud Source Series (11) – Retries and summaries of the Load Balancing Ribbon
SpringCloud source Code Series (12) – Basic usage of Service invocation Feign
SpringCloud source Code Series (13) – Service invocation of Feign’s scanning @FeignClient annotation interface
The dynamic proxy factory component FeignClientFactoryBean
The FeignClientFactoryBean component that was analyzed in the previous article is the component that generates the dynamic proxy for the FeignClient interface.
FeignClientFactoryBean implements the FactoryBean interface. When a Bean implements the FactoryBean interface, Spring instantiates the factory and then calls getObject() to create the real Bean if needed.
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean.ApplicationContextAware {}Copy the code
The FeignClientFactoryBean implements the getObject() method, which in turn calls the getTarget() method, and getTarget() finally creates a dynamic proxy object for the FeignClient interface.
The main process for creating dynamic proxy objects is as follows:
- The Feign context is first obtained
FeignContext
FeignContext and RibbonSpringClientFactory
Similarly, you can get the context of each service. Because each service has its own configuration, Encoder, Decoder components, and so on, the components of the current service can be retrieved from the FeignContext. - And then you get that from the FeignContext
Feign.Builder
The feign.Builder is the constructor that is ultimately used to create dynamic proxy objects. - @feignClient If no
url
, constructs the url with the service name from the service name, similar to the RestTemplate, which must end up as a load balancing request. If the URL is configured, the address is called directly. - All get one from FeignContext
Client
If the URL is configured, the url is obtained from the clientProxy objects
And set it to builder. Otherwise, set Client directly to Builder. That is, determine whether to use a load-balancing Client based on the URL. - Eventually they callTargeter 的
target()
Method to construct a dynamic proxy object. The parameters passed in by target include the current FeignClientFactoryBean object, Feign.Builder, FeignContext, and the encapsulatedHardCodedTarget
Object.
// Get the entry to the FeignClient proxy object
@Override
public Object getObject(a) throws Exception {
return getTarget();
}
/** * create a proxy object for the FeignClient interface@FeignClientThe interface type of the annotation * *@param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context information
*/
<T> T getTarget(a) {
// Feign context
FeignContext context = applicationContext.getBean(FeignContext.class);
// Feign constructor
Feign.Builder builder = feign(context);
// If the URL is not configured directly, the load balancing request is processed
if(! StringUtils.hasText(url)) {if(! name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
// Address with service name => http://demo-consumer
url += cleanPath();
// The type returned must be load-balancing; HardCodedTarget => HardCodedTarget
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
}
// If the url is configured, request the URL directly
if(StringUtils.hasText(url) && ! url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
// Client => Feign the core component that initiates the HTTP call
Client client = getOptional(context, Client.class);
if(client ! =null) {
if (client instanceof LoadBalancerFeignClient) {
// Get the proxy object, which is the native client.default
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// Get the proxy object, which is the native client.default
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
// targeter creates a dynamic proxy object
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}
Copy the code
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
/ / for the Client
Client client = getOptional(context, Client.class);
if(client ! =null) {
builder.client(client);
// Targeter => HystrixTargeter
Targeter targeter = get(context, Targeter.class);
// targeter creates a dynamic proxy object
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?");
}
Copy the code
Dynamic proxy constructor feign.builder
The feign() method returns feign.Builder, which is also retrieved from the FeignContext. The most important thing about this method is that it sets Logger, Encoder, Decoder, Contract, And read the configuration file feign.client.* related configuration. FeignClientsConfiguration configured in the default implementation of this a few interface classes, we can also customize these implementation class.
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(type);
// We can customize Logger, Encoder, Decoder, Contract
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// Read the configuration of feign. Client.* in the configuration file to configure feign
configureFeign(context, builder);
return builder;
}
Copy the code
What is the default implementation of Feign.Builder? Can know from FeignClientsConfiguration, by default is Feign. Builder, if you enable Feign hystrix. Enabled, the default implementation is HystrixFeign. The Builder.
What’s the difference between Feign.Builder and Hystrixfeign.build? The main difference is that the implementation class InvocationHandler to create the dynamic proxy is different. In the case of Hystrix, there are fuses, downgrades, etc. Hystrixfeign. Build also sets fallback and fallbackFactory classes configured by @FeignClient. We’ll look at this later when we analyze the Hystrix source code. All you need to know is that Feign is not hystrix enabled and @FeignClient’s fallback/fallbackFactory downgrade callbacks are not valid.
public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer(a) {
// Never retry
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
// Default is feign.builder
return Feign.builder().retryer(retryer);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
// Hystrix is introduced and feign.hystrix.enabled = true
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder(a) {
// feign When hystrix is enabled, feign. Builder is hystrixfeign. Builder
returnHystrixFeign.builder(); }}}Copy the code
Feign configuration
The configureFeign() method configures feign.Builder to verify the priority of the Feign configuration in the base article.
Feign has three configurations. One is to configure Feign in Configuration mode and then set the Configuration parameter to @FeignClient. This is followed by the global feign.client.default configuration, and the service-specific configuration feign.client.
.
As you can see from the configureFeign() method, by default, the lowest priority is the code configuration, followed by the default configuration, and the highest priority is the service-specific configuration.
If you want the code configuration to take precedence over the configuration in the file, you can set feign.client.defalut-to-properties=false to change the priority for the feign configuration to take effect.
protected void configureFeign(FeignContext context, Feign.Builder builder) {
Feign.client.* Client configuration in the configuration file
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
if(properties ! =null && inheritParentContext) {
// defaultToProperties: The configuration in the configuration file is preferred
if (properties.isDefaultToProperties()) {
// Lowest priority: use Configuration in the code
configureUsingConfiguration(context, builder);
// Secondary priority: use the default value feign.client.default
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
// High priority: the configuration defined using feign.client.
configureUsingProperties(properties.getConfig().get(contextId), builder);
}
// Java code configuration is preferred
else{ configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(contextId), builder); configureUsingConfiguration(context, builder); }}else{ configureUsingConfiguration(context, builder); }}Copy the code
The network calls the component Client
Client is a component in Feign-core. It has only one interface, Execute, which is the URL that calls Request and encapsulates the return interface into Response.
public interface Client {
/**
* Executes a request against its {@link Request#url() url} and returns a response.
*
* @param request safe to replay.
* @param options options to apply to this request.
* @return connected response, {@link Response.Body} is absent or unread.
* @throws IOException on a network error connecting to {@link Request#url()}.
*/
Response execute(Request request, Options options) throws IOException;
}
Copy the code
Client has the following implementation classes:
Client automation configuration class is FeignRibbonClientAutoConfiguration, FeignRibbonClientAutoConfiguration imported the HttpClient, OkHttp and default Feign load balance configuration class.
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled", matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {}Copy the code
To enable the Apache HttpClient
As can be seen from the HttpClientFeignLoadBalancedConfiguration configuration, to enable the apache httpclient, need to set the feign. Httpclient. Enabled = true (the default is true). And you need to add the feign-httpclient dependency (ApacheHttpClient)
After ApacheHttpClient is enabled, the proxy object of LoadBalancerFeignClient is apache HttpClient in Feign-HttpClient.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return newLoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }}Copy the code
Enable OkHttp
As can be seen from the OkHttpFeignLoadBalancedConfiguration configuration, to enable the okhttp, need to set the feign. Okhttp. Enabled = true, The feign-okhttp dependency (OkHttpClient) needs to be introduced.
When okHTTP is enabled, the proxy object of LoadBalancerFeignClient is the OkHttpClient of Feign-okHTTP.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return newLoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }}Copy the code
The default configuration
Without introducing feign – httpclient or feign – okhttp, will leave the default DefaultFeignLoadBalancedConfiguration. The Default proxy object, client. Default, uses HttpURLConnection to make HTTP calls.
@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null.null), cachingFactory, clientFactory); }}Copy the code
As you can see, the Client objects created by the three configuration classes are LoadBalancerFeignClient, which supports load balancing requests. The Default proxy class is Client.Default, and the underlying is HttpURLConnection.
This is similar to the load balancing for RestTemplate when analyzing the Ribbon source code.
Dynamic proxy target Targeter
The Targeter interface has only one interface method, which fetches a dynamic proxy object through the target() method. Targeter has two implementation classes DefaultTargeter and HystrixTargeter.
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget
target)
;
}
Copy the code
As you can see in the FeignAutoConfiguration configuration class, the default implementation of Targeter is HystrixTargeter whenever HystrixFeign is introduced.
HystrixTargeter was originally designed to integrate Feign with Hystrix, allowing feign calls to fuse, limit, and degrade.
public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter(a) {
return newHystrixTargeter(); }}@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter(a) {
return newDefaultTargeter(); }}}Copy the code
The difference between HystrixTargeter and DefaultTargeter is that HystrixTargeter sets the demotion callback processing class to feign.Builder so that when a Feign call triggers a fuse break, the demotion can be entered into the callback class processing.
They all end up essentially calling the target() method of feign.Builder to create a dynamic proxy object.
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget
target)
{
if(! (feigninstanceof feign.hystrix.HystrixFeign.Builder)) {
// If not hystrixfeign. Builder, call target directly
return feign.target(target);
}
When Hystrix is enabled, the callback class or callback factory is set to hystrixfeign.Builderfeign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName() : factory.getContextId(); Class<? > fallback = factory.getFallback();// Set the callback class
if(fallback ! =void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
// Set the callback factory classClass<? > fallbackFactory = factory.getFallbackFactory();if(fallbackFactory ! =void.class) {
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
}
// Call feign.builder to create the dynamic proxy
returnfeign.target(target); }}Copy the code
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget
target)
{
returnfeign.target(target); }}Copy the code
Feign.builder creates dynamic proxy
Builder is the default implementation of feign. Builder. HystrixTargeter calls the target method of Feign.Builder to create a dynamic proxy.
- Called first in the target method
build()
Method to constructFeign
And then call Feign’snewInstance
Create dynamic proxy objects. build()
Method that reads the configuration firstClient, Retryer, Logger, Contract, Encoder, Decoder
Such as object.- And then we get
InvocationHandlerFactory
The default isInvocationHandlerFactory.Default
, which is a factory class provided by Feign to create proxy objectsInvocationHandler
. - The interface method processor factory is then created
SynchronousMethodHandler.Factory
Is used to encapsulate an interface method as a method executorMethodHandler
, the default implementation class isSynchronousMethodHandler
. - A SpringMVC annotation handler is also created
ParseHandlersByName
As you can imagine, this is used to handle springMVC annotations in the interface, parsing the REST interface to produce MethodHandler. - Finally, the Feign object is created, and the implementation class is ReflectiveFeign. After that, it is time to use ReflectiveFeign to create the dynamic proxy object.
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
/ / build Feign
public Feign build(a) {
// Feign Http call Client.Default is client.default
Client client = Capability.enrich(this.client, capabilities);
// Retries, the default is reretry
Retryer retryer = Capability.enrich(this.retryer, capabilities);
// the Feign request interceptor can do some customization to the Feign RequestTemplate RequestTemplate
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
// Log component, default Slf4jLogger
Logger logger = Capability.enrich(this.logger, capabilities);
// Interface protocol component, default is SpringMvcContract
Contract contract = Capability.enrich(this.contract, capabilities);
/ / configuration class
Options options = Capability.enrich(this.options, capabilities);
/ / encoder
Encoder encoder = Capability.enrich(this.encoder, capabilities);
/ / decoder
Decoder decoder = Capability.enrich(this.decoder, capabilities);
// Create a factory class for InvocationHandler
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
// Interface method processor factory
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
// Parse springMVC annotations
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
// ReflectiveFeign
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
Copy the code
InvocationHandlerFactory contains a create interface method, the Default implementation is InvocationHandlerFactory. The Default, Return the InvocationHandler type is ReflectiveFeign FeignInvocationHandler.
package feign;
public interface InvocationHandlerFactory {
// Create a dynamic proxy
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
// Method handler
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return newReflectiveFeign.FeignInvocationHandler(target, dispatch); }}}Copy the code
Then look at the newInstance() method of ReflectiveFeign:
newInstance
The target argument is encapsulated aboveTarget.HardCodedTarget
It encapsulates the clientType, url
Such attributes.- The first is to use
ParseHandlersByName
Convert the interface in the FeignClient interface to MethodHandler, the actual typeSynchronousMethodHandler
This detail is not to be seen. - And then use
InvocationHandlerFactory
Create the InvocationHandler proxy object, that isReflectiveFeign.FeignInvocationHandler
Methods that call dynamic proxy objects eventually go into the execution handler. - Finally, you can see where the dynamic proxy is created
Proxy
Creates a FeignClient dynamic proxy object whose type is the type of the interface annotated by @FeignClient. Once finally injected into the IoC container, you can inject your own FeignClient client component into your code.
The final step is to create a dynamic Proxy that implements the FeignClient interface through the Proxy, and then all calls to the interface methods are intercepted by the FeignInvocationHandler.
public <T> T newInstance(Target<T> target) {
// Use ParseHandlersByName to convert the interface in the FeignClient interface to MethodHandler. Springmvc annotations are handled by the Contract component
// MethodHandler => SynchronousMethodHandler
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
// Convert to method-methodHandler mapping
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))); }}/ / SynchronousMethodHandler. Create SynchronousMethodHandler Factory
InvocationHandler handler = factory.create(target, methodToHandler);
// Use Proxy to create a dynamic Proxy, which is a SynchronousMethodHandler
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
Copy the code
A diagram summarizes the FeignClient process for generating dynamic proxies
The following diagram summarizes the process of generating FeignClient dynamic proxies:
- First of all,
@EnableFeignClients
The imported registryFeignClientsRegistrar
scans@FeignClient
Annotated interface, and generatedFeingClientFactoryBean
的BeanDefinition
Register with the container. The FeingClientFactoryBean will be calledgetObject
Method to get a dynamic proxy object for the interface. - The getObject method that goes into the FeingClientFactoryBean gets it first
FeignContext
It is essentially a container for each client, similar to a Map structure that caches the relationship between the client and the container. Most of the subsequent components are retrieved from the FeignContext. - Get the Feign constructor from the FeignContext
Feign.Builder
, and configure Feign.Builder. The configuration source has many places, the highest priority is the configuration in Application. You can also configurefeign.client.default-to-properties=false
Sets the Java code configuration to high priority. - If @feignClient is configured with a URL, the load balancing request will be sent.
- If a URL is configured to represent a specific address, set the LoadBalancerFeignClient’s delegate as the Client to feign.Builder.
- If the URL is not configured, indicating that the request is made by the service name, LoadBalancerFeignClient is set to feign.Builder as a Client.
- And get it from FeignContext
Targeter
, to call ittarget
Method to get the dynamic proxy. - In the Target method, the feign.Builder’s
build()
Method to constructReflectiveFeign
:- First, get the proxy object factory
InvocationHandlerFactory
, used to createInvocationHandler
- Then, a method processor factory is constructed from each component
SynchronousMethodHandler.Factory
Next, a method resolver is createdParseHandlersByName
- Finally, it is constructed based on InvocationHandlerFactory and ParseHandlersByName
ReflectiveFeign
- First, get the proxy object factory
- Finally, call ReflectiveFeign
newInstance
Method reflects the dynamic proxy for creating the interface:- First use the method parser ParseHandlersByName to parse the interface into
SynchronousMethodHandler
- The proxy object is then created using the InvocationHandlerFactory
InvocationHandler
(ReflectiveFeign FeignInvocationHandler) - Finally using
Proxy
Create a dynamic proxy object whose type is the type of the interface and whose type is the proxy objectReflectiveFeign.FeignInvocationHandler
.
- First use the method parser ParseHandlersByName to parse the interface into