What is Feign?
Feign is a lightweight framework for HTTP request invocation. It can invoke Http requests in the way of Java interface annotations, instead of directly invoking them by encapsulating HTTP request messages in Java. Feign templates requests by processing annotations. When actually invoked, parameters are passed in, and then applied to requests according to parameters, and then converted into real requests. This kind of request is relatively intuitive.
Feign is widely used in Spring Cloud solutions and is an essential component for learning the Spring Cloud microservice architecture.
1. How to Enable
Check if there is an @EnableFeignClients annotation on the startup configuration, and if there is one, open the package scan and scan the interface annotated by @FeignClient. After scanning the annotation, it is injected into the IOC container through the bean Definition to facilitate subsequent calls.
In FeignClients Registrar, registerFeignClients() completes the operation of registering feign.
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = this.getScanner(); scanner.setResourceLoader(this.resourceLoader); Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); ...... //Traveling through the services to be invoked by the project Iterator var17 = ((Set)basePackages).iterator(); while(var17.hasNext()) { String basePackage = (String)var17.next(); Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage); Iterator var21 = candidateComponents.iterator(); while(var21.hasNext()) { BeanDefinition candidateComponent = (BeanDefinition)var21.next(); if (candidateComponent instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition)candidateComponent; //Get the details in feign AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName()); String name = this.getClientName(attributes); //Registration configuration information this.registerClientConfiguration(registry, name, attributes.get("configuration")); //Register feign client this.registerFeignClient(registry, annotationMetadata, attributes); } } } }
Register the feign client, including all the information configured when using annotations.
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); this.validate(attributes); definition.addPropertyValue("url", this.getUrl(attributes)); definition.addPropertyValue("path", this.getPath(attributes)); String name = this.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(2); String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = ((Boolean)attributes.get("primary")).booleanValue(); beanDefinition.setPrimary(primary); String qualifier = this.getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias}); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
2. How to initiate a request
ReflectiveFeign uses JDK dynamic proxy to generate a dynamic proxy class for the target interface. This class generates an Invocation Handler (jdk dynamic proxy principle) unified method interceptor. At the same time, it generates a Synchronous MethodHandler interceptor for each method of the interface, parses metadata on the method and generates an http request template.
Generate RequestTemplate to initiate the request in the SynchronousMethodHandler class.
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; } } }
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) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } }
Send http requests
@Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection).toBuilder().request(request).build(); } HttpURLConnection convertAndSend(Request request, Options options) throws IOException { final HttpURLConnection connection = (HttpURLConnection) new URL(request.url()).openConnection(); if (connection instanceof HttpsURLConnection) { HttpsURLConnection sslCon = (HttpsURLConnection) connection; if (sslContextFactory != null) { sslCon.setSSLSocketFactory(sslContextFactory); } if (hostnameVerifier != null) { sslCon.setHostnameVerifier(hostnameVerifier); } } connection.setConnectTimeout(options.connectTimeoutMillis()); connection.setReadTimeout(options.readTimeoutMillis()); connection.setAllowUserInteraction(false); connection.setInstanceFollowRedirects(true); connection.setRequestMethod(request.method()); Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING); boolean gzipEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP); boolean deflateEncodedRequest = contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE); boolean hasAcceptHeader = false; Integer contentLength = null; for (String field : request.headers().keySet()) { if (field.equalsIgnoreCase("Accept")) { hasAcceptHeader = true; } for (String value : request.headers().get(field)) { if (field.equals(CONTENT_LENGTH)) { if (!gzipEncodedRequest && !deflateEncodedRequest) { contentLength = Integer.valueOf(value); connection.addRequestProperty(field, value); } } else { connection.addRequestProperty(field, value); } } } // Some servers choke on the default accept string. if (!hasAcceptHeader) { connection.addRequestProperty("Accept", "*/*"); } if (request.body() != null) { if (contentLength != null) { connection.setFixedLengthStreamingMode(contentLength); } else { connection.setChunkedStreamingMode(8196); } connection.setDoOutput(true); OutputStream out = connection.getOutputStream(); if (gzipEncodedRequest) { out = new GZIPOutputStream(out); } else if (deflateEncodedRequest) { out = new DeflaterOutputStream(out); } try { out.write(request.body()); } finally { try { out.close(); } catch (IOException suppressed) { // NOPMD } } } return connection; }