Column catalog

  1. Spring Cloud OpenFeign source code parsing
  2. Spring Cloud Ribbon source code
  3. Spring Cloud Alibaba Sentinel source code analysis
  4. Spring Cloud Gatway source code analysis
  5. Spring Cloud Alibaba Nacos source code analysis

0. An introduction to the demo

  • This code isOpenFeignGet a sample code forGithubFor all contributors to the repository, create oneissue. The suggestion is to start withDEBUGDebug read source code
interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  
    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign"."feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + "(" + contributor.contributions + ")"); }}}Copy the code

Feign.build injects dependency configuration items

    public Feign build(a) {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
              logLevel, decode404, closeAfterDecode, propagationPolicy);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
              errorDecoder, synchronousMethodHandlerFactory);
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
Copy the code

Call the JDK dynamic proxy to generate the interface proxy class

Dynamic proxies generate interface objects

public class ReflectiveFeign extends Feign {
	@Override
	public <T> T newInstance(Target<T> target) {
		// Use Contract to parse the methods and annotations on the interface class, transforming the separate MethodHandler handling
		Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
		// Use the DK dynamic proxy to generate the proxy object for the interface. The actual business logic is handled by the InvocationHandler, which calls MethodHandler
		InvocationHandler handler = factory.create(target, methodToHandler);
		T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), newClass<? >[]{target.type()}, handler);returnproxy; }}Copy the code

Parse interface method annotation information

  • How to parse the above DemoGithub.contributorsMethod annotation information.FeignProvide aContractParsing protocol, which has the following implementation.


Parsing logic is supported by default

class Default extends Contract.BaseContract {
	protected void processAnnotationOnMethod(a) {
		Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
		if (annotationType == RequestLine.class) {
			// @requestLine annotation processing logic
		} else if (annotationType == Body.class) {
			// @body handles the logic for the annotation
		} else if (annotationType == Headers.class) {
			// @headers annotation processing logic}}protected boolean processAnnotationsOnParameter(a) {
		boolean isHttpAnnotation = false;
		for (Annotation annotation : annotations) {
			Class<? extends Annotation> annotationType = annotation.annotationType();
			if (annotationType == Param.class) {
				Param paramAnnotation = (Param) annotation;
				// @param annotation processing logic
			} else if (annotationType == QueryMap.class) {
				// @queryMap annotation processing logic
			} else if (annotationType == HeaderMap.class) {
				// @headermap annotation processing logic}}returnisHttpAnnotation; }}Copy the code

Native common annotations

Annotation Interface Target
@RequestLine Method
@Param Parameter
@Headers Method, Type
@QueryMap Parameter
@HeaderMap Parameter
@Body Method

Spring MVC extension annotations

  • SpringMvcContract forspring-cloud-open-feignExtended support forSpringMVCAnnotations,feignVersion is also supported
public class SpringMvcContract  {
	
	// handle @requestMapping on the class
	@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class
        clz) {
		if (clz.getInterfaces().length == 0) { RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class); }}RequestMapping (@requestMapping, @postMapping, @getMapping, @postMapping)
	@Override
	protected void processAnnotationOnMethod(a) {
		if(! RequestMapping.class.isInstance(methodAnnotation) && ! methodAnnotation .annotationType().isAnnotationPresent(RequestMapping.class)) {return;
		}
		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// Get the request method
		RequestMethod[] methods = methodMapping.method();
		/ / produce processing
		parseProduces(data, method, methodMapping);
		/ / consumes
		parseConsumes(data, method, methodMapping);
		// handle headers
		parseHeaders(data, method, methodMapping);

		data.indexToExpander(new LinkedHashMap<Integer, Param.Expander>());
	}

	// Handle request parameters SpringMVC native annotations
	@Override
	protected boolean processAnnotationsOnParameter(a) {
		Param.Expander expander = this.convertingExpanderFactory
				.getExpander(typeDescriptor);
		if(expander ! =null) {
			data.indexToExpander().put(paramIndex, expander);
		}
		returnisHttpAnnotation; }}Copy the code

MethodHandler Request processing logic

MethodHandlerrouting

As shown above, routes to different MethodHandler implementations based on different request methods

final class SynchronousMethodHandler implements MethodHandler {
	@Override
	public Object invoke(Object[] argv) throws Throwable {
		// Get the request template
		RequestTemplate template = buildTemplateFromArgs.create(argv);
		// Parameter processing
		Options options = findOptions(argv);
		// The default retries
		Retryer retryer = this.retryer.clone();
		while (true) {
			try {
				// Execute request interceptor
				Request request = targetRequest(template);
				// Outputs request packets
				if(logLevel ! = Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response = client.execute(request, options);// Decode according to the returned status code.return response;
			} catch (RetryableException e) {
				// Perform the logic related to retry}}}}Copy the code

Build the request template based on the different parameters

  • Form submission, or direct body submission

Execute the Request interceptor to generate the final Request

// Get all request interceptors, one by one
  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }
Copy the code

Request log handling

  • Log output level
public enum Level {
	/** ** does not output */
	NONE,
	/** * only output Http method, URL, status code, execution time */
	BASIC,
	/** * Outputs the request header and Http method, URL, status code, execution time */
	HEADERS,
	/** * Outputs the request header, message style and Http method, URL, status code, execution time */
	FULL
}
Copy the code

The Client performs the final Requst request

Default default processing

  • Through the JDKjava.netPackage implementation, create connection implementation without request. Can be configured asHttpClientorOKHttpHigh performance implementation of
class Default implements Client {

	private final SSLSocketFactory sslContextFactory;
	private final HostnameVerifier hostnameVerifier;
	
	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		HttpURLConnection connection = convertAndSend(request, options);
		returnconvertResponse(connection, request); }"Copy the code

Spring Cloud load balancing processing

// The Client implementation of Spring Cloud
public class FeignBlockingLoadBalancerClient {
	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		// For example request: http://pig-auth-server/token/info
		final URI originalUri = URI.create(request.url());
		// Intercepts serviceId: pig-auth-server
		String serviceId = originalUri.getHost();
		// Call the loadBalancer API to get a valid service instance
		ServiceInstance instance = loadBalancerClient.choose(serviceId);
		/ / build the real request URL http://172.17.0.110:8763/token/info
		String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
				.toString();
		// Create the request and execute it
		Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
				request.headers(), request.requestBody());
		returndelegate.execute(newRequest, options); }}Copy the code

Return message Decoder processing

  • The default processing
  class Default implements Encoder {

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) {
      if (bodyType == String.class) {
        template.body(object.toString());
      } else if (bodyType == byte[].class) {
        template.body((byte[]) object, null);
      } else if(object ! =null) {
        throw new EncodeException(
            format("%s is not a type supported by this encoder.", object.getClass())); }}}Copy the code
  • If the packet is returned, an error is reported
  public static class Default implements ErrorDecoder {

    private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();

    @Override
    public Exception decode(String methodKey, Response response) {
      FeignException exception = errorStatus(methodKey, response);
      Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
      if(retryAfter ! =null) {
        return new RetryableException(
            response.status(),
            exception.getMessage(),
            response.request().httpMethod(),
            exception,
            retryAfter,
            response.request());
      }
      return exception;
    }

    private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
      if(map.containsKey(key) && ! map.get(key).isEmpty()) {return map.get(key).iterator().next();
      }
      return null; }}}Copy the code

Injecting custom ErrorDecoders is common.


How does spring-Cloud-open-Feign initialize and run?

【 extension 】 Spring Cloud OpenFeign

EnableFeignClients parsing

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    
}
Copy the code
  • When we do Main, plus@EnableFeignClientsAnnotation, opensspring-cloud-open-feignRelated functions of.
  • Import(FeignClientsRegistrar.class)Import FeignClientsRegistrar and scan@FeignClientInject into the container

FeignClientsRegistrar

class FeignClientsRegistrar {
	@Override
	public void registerBeanDefinitions(a) {
		registerFeignClients(metadata, registry);
	}
	
	public void registerFeignClients(a) {
		
		// Scan @feignClient within the configuration scope of the configuration annotations
		for (String basePackage : basePackages) {
			// Inject the IOC container
			registerClientConfiguration(registry, name,
							attributes.get("configuration")); }}// feignClient <--> bean construct
	private void registerFeignClient(a) {
		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);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		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); . BeanDefinitionHolder holder =new BeanDefinitionHolder(beanDefinition, className,
				newString[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }}Copy the code

The default

public class FeignAutoConfiguration {
    // If the feign-hystrix module is not introduced, then DefaultTargeter is injected
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean
    	public Targeter feignTargeter(a) {
    		return newDefaultTargeter(); }}}Copy the code

Without the introduction of Feign-Hystrix, the process is the same as the original process. Calling feignClient. method triggers a dynamic proxy that executes the logic of the MethodHandler

HystrixFeign

  • So, first of all, it’s introducedHystrixFeignDoes that mean more logic

Originally 0. Demo feign.Builder () became Hystrixfeign.Builder ()

public final class HystrixFeign {
	public static Builder builder(a) {
		return new Builder();
	}
	public static final class Builder extends Feign.Builder {
		
		// Inject an implementation of HystrixInvocationHandler
		Feign build(finalFallbackFactory<? > nullableFallbackFactory) {
			super.invocationHandlerFactory(new InvocationHandlerFactory() {
				@Override
				public InvocationHandler create(a) {
					return newHystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); }});super.contract(new HystrixDelegatingContract(contract));
			return super.build(); }}}Copy the code
  • injectionHystrixInvocationHandlerImplementation, wrapped in HystrixCommand, and ultimately using methodHandler to call the final interface
final class HystrixInvocationHandler implements InvocationHandler {
	
	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {

		// Use HystrixCommand to wrap
		HystrixCommand<Object> hystrixCommand =
			new HystrixCommand<Object>(setterMethodMap.get(method)) {
				@Override
				protected Object run(a) throws Exception {
					try {
						// Calls methodHandler to process the final request
						return HystrixInvocationHandler
						.this.dispatch.get(method).invoke(args);
					} catch (Exception e) {
						throw e;
					} catch (Throwable t) {
						throw(Error) t; }}};returnhystrixCommand.execute(); }}Copy the code

SentinelFeign

  • Look at the class comments firstlike {@link HystrixFeign.Builder}“Borrowed from HystrixFeign
/ * * * {@link Feign.Builder} like {@link HystrixFeign.Builder}.
 */
public final class SentinelFeign {}Copy the code
  • injectionSentinelInvocationHandlerImplementation, using Sentinel wrapper, and ultimately using methodHandler to call the final interface
public class SentinelInvocationHandler implements InvocationHandler {
	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {
		// Use the sentinel wrapper request
		try {
			ContextUtil.enter(resourceName);
			entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
			result = methodHandler.invoke(args);
		}
		catch (Throwable ex) {
			/ / fallback logic
		}
		finally {
			ContextUtil.exit();
		}
		returnresult; }}Copy the code

Summary sequence diagram

The follow-up plan

The Ribbon, Hystrix, Sentinel, Nacos and other components will be updated later.

Note: the above image materials (omnigraffle & 100 million images) can be obtained from the public account JAVA Architecture Diary

“★★★★” based on Spring Boot 2.2, Spring Cloud Hoxton & Alibaba, OAuth2 RBAC authority management system