What is a Fegin
Feign is a declarative WebService client. Using Feign makes it easier to write Web Service clients by defining an interface and then adding annotations to it, as well as supporting JAX-RS standard annotations. Feign also supports pluggable encoders and decoders. Spring Cloud encapsulates Feign to support Spring MVC standard annotations and HttpMessageConverters. Feign can be used in combination with Eureka and the Ribbon to support load balancing.
This article focuses on the specific invocation link of Fegin in SpringCloud and the underlying implementation.
Initialization of Feign
@ EnableFeignClients annotations
The @enableFeignClients annotation needs to be added for FeGIN to take effect. Let’s look at EnableFeignClients:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {// Configurable omitted... }Copy the code
With the familiar annotation @import, look at the FeignClientsRegistrar class
Register beanDefinition FeignClientsRegistrar. Java
Main method entry:
FeignClientsRegistrar#registerBeanDefinitions
@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {/ / by EnableFeginClients Configuration build Configuration registerDefaultConfiguration (metadata, registry); // registerFeignClient registerFeignClients(metadata, registry); }Copy the code
FeignClientsRegistrar#registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// Get annotation configuration Map<String, Object> defaultAttrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName(), true); if (defaultAttrs ! = null && defaultAttrs.containsKey("defaultConfiguration")) { String name; / / whether to obtain outside class the if (metadata) hasEnclosingClass ()) {name = "default." + metadata. GetEnclosingClassName (); } else { name = "default." + metadata.getClassName(); } / / registered the Configuration object registerClientConfiguration (registry, name, defaultAttrs. Get (" defaultConfiguration ")); }}Copy the code
FeignClientsRegistrar#registerFeignClients
How to register FeignClient
Public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {//scanner Access to a scanner ClassPathScanningCandidateComponentProvider scanner = getScanner (); / / set the resource loaders scanner. SetResourceLoader (enclosing resourceLoader); // Set<String> basePackages; / / annotation configuration Map < String, Object > attrs = metadata. GetAnnotationAttributes (EnableFeignClients. Class. GetName ()); AnnotationTypeFilter = new AnnotationTypeFilter(feignClient.class); final Class<? >[] clients = attrs == null ? null : (Class<? >[]) attrs.get("clients"); // No clients attribute configured, filter set, And package path if esau (clients = = null | | clients. The length = = 0) {scanner. AddIncludeFilter (annotationTypeFilter); basePackages = getBasePackages(metadata); } // The clients attribute is configured else {... } // Package path for (String basePackage: BasePackages) {// Scan all BeanDefinition objects that match the filter Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : CandidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {// Validate is an interface type AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // Get @feignClient configuration Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); / / to individual FeignClient configuration register registerClientConfiguration (registry, the name, the attributes. The get (" configuration ")); // Register a FactoryBean registerFeignClient(Registry, annotationMetadata, Attributes); }}}}Copy the code
FeignClientsRegistrar#registerFeignClient
Here are the steps to register the FeignClientFactoryBean into the IOC container. You can see below. The FeignClient configuration Settings are resolved to BeanDefinitionBuilder, and AbstractBeanDefinition is generated. Finally, BeanDefinitionHolder is generated for BeanDefinition registration. BeanDefinitionHolder is a hook class that records information such as the BeanDefinition corresponding to the class name
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code
At this point the EnableFeignClients annotation is done, which is simply to scan all packages, register all @FeignClient classes as FactoryBeans, and then use the getObject method to get the corresponding class when the bean is created.
FeignClientFactoryBean. Java beans generated objects
Next look at the getObject method of the FeignClientFactoryBean, which calls the getTarget method
FeignClientFactoryBean#getObject
@Override
public Object getObject() throws Exception {
return getTarget();
}
Copy the code
FeignClientFactoryBean#getTarget
Go to the getTarget method and look only at the case where the URL is not configured
< T > T getTarget () {/ / access to feign context FeignContext context. = applicationContext getBean (FeignContext. Class); Feign.Builder = Feign (context); Feign (context); // If FeignClient is not configured with the specified URL, go this way. StringUtils.hasText(this.url)) { String url; // If there is no configuration and name does not start with HTTP, spell protocol if (! this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); Return (T) loadBalance(Builder, context, new HardCodedTarget<>(this.type, this.name, url)); return (T) loadBalance(Builder, context, new HardCodedTarget<>(this.type, this.name, url)); }... }Copy the code
FeignClientFactoryBean#loadBalance
Now what does the loadBalance method do
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {// This is a LoadBalanceFeignClient object. Client = getOptional(context, client.class); if (client ! = null) {// Wrap client Builder.client (client); Targeter targeter = get(context, Targeter.class); Return targeter.target(this, builder, context, target); }}Copy the code
HystrixTargeter#target
See the target method, primarily the feign.target method
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
...
}
Copy the code
Feign.target can see that the object is instantiated, so focus on what build() does
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
Copy the code
Feign. Build mainly initialization SynchronousMethodHandler. Factory and generates ReflectiveFeign object, into the ReflectiveFeign newInstance method.
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
Copy the code
ReflectiveFeign#newInstance
Public <T> T newInstance(Target<T> Target) { 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 ()) {... } // Generate interceptors are familiar with JDK dynamic proxy requirements! InvocationHandler handler = factory.create(target, methodToHandler); T proxy = (T) proxy.newproxyInstance (target.type().getClassLoader(), new Class<? >[]{target.type()}, handler); for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; }Copy the code
As shown below, nameToHandler is storedAfter looking at the generated invocationHandler, you only need to worry about what happens in the invocationHandler.
Fegin calls
ReflectiveFeign# FeignInvocationHandler# invoke method
The Dispatches here are actually the methodToHandler that was just passed in, and that methodToHandler logs the Method and its corresponding handler class, the SynchronousMethodHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
return dispatch.get(method).invoke(args);
}
Copy the code
So go to the Invoke method of the SynchronousMethodHandler
SynchronousMethodHandler#invoke
So you can see that we’re doing a retry here with a while, and then we see a try catch, so normal business code is written in a try, so we go straight to executeAndDecode
public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while (true) { try { return executeAndDecode(template); } catch (RetryableException e) { retryer.continueOrPropagate(e); if (logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; }}}Copy the code
SynchronousMethodHandler#executeAndDecode
Let’s go straight to the execute method in the main try, and the client here is LoadBalanceFeignClient, so LoadBalanceFeignClient
Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); . Response response; long start = System.nanoTime(); try { response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } catch (IOException e) { ... }... }Copy the code
LoadBalancerFeignClient#execute
Here you can see the familiar lbClient and ribbon work on load balancing
@Override 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) { ... }}Copy the code
The next see AbstractLoadBalancerAwareClient executeWithLoadBalancer method
AbstractLoadBalancerAwareClient#executeWithLoadBalancer
The Submit method of LoadBalancerCommand passes in a ServerOperation object and goes directly to the Submit method
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) {
...
}
Copy the code
LoadBalancerCommand#submit
We just need to focus on the key steps
final ExecutionInfoContext context = new ExecutionInfoContext(); . // Use the load balancer Observable<T> o = (server == null ? selectServer() : Observable.just(server)) ... return operation.call(server).doOnEach(new Observer<T>() { private T entity; @Override public void onCompleted() { recordStats(tracer, stats, entity, null); // TODO: What to do if onNext or onError are never called? } @Override public void onError(Throwable e) { recordStats(tracer, stats, null, e); logger.debug("Got error {} when executed on server {}", e, server); if (listenerInvoker ! = null) { listenerInvoker.onExceptionWithServer(e, context.toExecutionInfo()); } } @Override public void onNext(T entity) { this.entity = entity; if (listenerInvoker ! = null) { listenerInvoker.onExecutionSuccess(entity, context.toExecutionInfo()); } } private void recordStats(Stopwatch tracer, ServerStats stats, Object entity, Throwable exception) { tracer.stop(); loadBalancerContext.noteRequestCompletion(stats, entity, exception, tracer.getDuration(TimeUnit.MILLISECONDS), retryHandler); }}); }}); . return ... }Copy the code
SelectServer () from selectServer(
server == null ? selectServer() : Observable.just(server)
Copy the code
LoadBalancerCommand#selectServer
Directly obtain getServerFromLoadBalancer server
private Observable<Server> selectServer() { return Observable.create(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); }}}); }Copy the code
LoadBalancerContext#getServerFromLoadBalancer
Here you see the familiar getLoadBalancer method and chooseServer method
public Server getServerFromLoadBalancer(@Nullable URI original, @nullable Object loadBalancerKey) throws ClientException {// Obtain the load balancer ILoadBalancer lb = getLoadBalancer(); If (host == null) {// If the load balancer is not empty, select server if (lb! = null){ Server svc = lb.chooseServer(loadBalancerKey); . return new Server(host, port); }Copy the code
GetLoadBalancer and chooseServer are not parsed here because the Ribbon has already parsed them.
Once you get the Server, the operation.call method is executed, which is the call method of the ServerOperation object you just passed in. This is a request for service.
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
Copy the code
Java, which has a default implementation of Deafult
public static class Default implements Client
You can see that HttpUrlConnection actually sent the request
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection).toBuilder().request(request).build();
}
Copy the code
This is the Feign initialization and call process! If there is any problem, please big guys point out!
Ribbon portal
What is Ribbon now? Juejin. Cn/post / 696426…