[Фото] Парсинг исходного кода Spring Cloud OpenFeign

Java

image

Каталог столбцов

  1. Анализ исходного кода Spring Cloud OpenFeign
  2. Анализ исходного кода Spring Cloud Ribbon
  3. Анализ исходного кода Spring Cloud Alibaba Sentinel
  4. Анализ исходного кода Spring Cloud Gateway
  5. Анализ исходного кода Spring Cloud Alibaba Nacos

0. Демонстрация начала работы

  • этот кодOpenFeignпример кода для полученияGithubВсе участники репозитория, создайтеissue. Рекомендуется начать здесьDEBUGОтладка чтения исходного кода
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 + ")");
    }
  }
}

Внедрение зависимостей CI Feign.build

    public Feign build() {
      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);
    }

Вызовите JDK Dynamic Proxy для генерации интерфейса прокси-класса

Динамические прокси генерируют интерфейсные объекты

public class ReflectiveFeign extends Feign {
	@Override
	public <T> T newInstance(Target<T> target) {
		//使用Contract解析接口类上的方法和注解,转换单独MethodHandler处理
		Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
		// 使用DK动态代理为接口生成代理对象,实际业务逻辑交给 InvocationHandler 处理,其实就是调用 MethodHandler 
		InvocationHandler handler = factory.create(target, methodToHandler);
		T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
		return proxy;
	}
}

Разобрать аннотацию метода интерфейса

  • Как разобрать приведенную выше демонстрациюGithub.contributorsинформация аннотации метода.FeignобеспечитьContractПротокол разбора реализован следующим образом.

Логика синтаксического анализа поддерживается по умолчанию.

class Default extends Contract.BaseContract {
	protected void processAnnotationOnMethod() {
		Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
		if (annotationType == RequestLine.class) {
			//@RequestLine	注解处理逻辑
		} else if (annotationType == Body.class) {
			//@Body	注解处理逻辑
		} else if (annotationType == Headers.class) {
			//@Headers	注解处理逻辑
		}
	}
	protected boolean processAnnotationsOnParameter() {
		boolean isHttpAnnotation = false;
		for (Annotation annotation : annotations) {
			Class<? extends Annotation> annotationType = annotation.annotationType();
			if (annotationType == Param.class) {
				Param paramAnnotation = (Param) annotation;
				//@Param	注解处理逻辑
			} else if (annotationType == QueryMap.class) {
				//@QueryMap	注解处理逻辑
			} else if (annotationType == HeaderMap.class) {
				//@HeaderMap	注解处理逻辑
			}
		}
		return isHttpAnnotation;
	}
}

Родные общие аннотации

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

Аннотации расширения Spring MVC

  • SpringMvcContract — этоspring-cloud-open-feignРасширенная поддержкаSpringMVCПримечание, сейчасfeignТакже поддерживаются версии
public class SpringMvcContract  {
	
	// 处理类上的 @RequestMapping
	@Override
	protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
		if (clz.getInterfaces().length == 0) {
			RequestMapping classAnnotation = findMergedAnnotation(clz,
					RequestMapping.class);
		}
	}
	
	// 处理 @RequestMapping 注解,当然也支持衍生注解 @GetMapping @PostMapping 等处理
	@Override
	protected void processAnnotationOnMethod() {
		if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
				.annotationType().isAnnotationPresent(RequestMapping.class)) {
			return;
		}
		RequestMapping methodMapping = findMergedAnnotation(method, RequestMapping.class);
		// 获取请求方法
		RequestMethod[] methods = methodMapping.method();
		// produce处理
		parseProduces(data, method, methodMapping);
		// consumes处理
		parseConsumes(data, method, methodMapping);
		// headers头处理
		parseHeaders(data, method, methodMapping);

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

	// 处理 请求参数 SpringMVC 原生注解
	@Override
	protected boolean processAnnotationsOnParameter() {
		Param.Expander expander = this.convertingExpanderFactory
				.getExpander(typeDescriptor);
		if (expander != null) {
			data.indexToExpander().put(paramIndex, expander);
		}
		return isHttpAnnotation;
	}
}

Логика обработки запроса MethodHandler

MethodHandlerмаршрутизация

Как показано выше, маршрутизация к разным методам запроса в соответствии с разными методами запросаMethodHandlerвыполнить

final class SynchronousMethodHandler implements MethodHandler {
	@Override
	public Object invoke(Object[] argv) throws Throwable {
		// 获取请求模板
		RequestTemplate template = buildTemplateFromArgs.create(argv);
		// 参数处理
		Options options = findOptions(argv);
		// 默认的重试器
		Retryer retryer = this.retryer.clone();
		while (true) {
			try {
				// 执行请求拦截器
				Request request = targetRequest(template);
				// 输出请求报文
				if (logLevel != Logger.Level.NONE) {
					logger.logRequest(metadata.configKey(), logLevel, request);
				}
				Response response = client.execute(request, options);
				// 根据返回的状态码 ,做 Decode 处理
				...
				return response;
			} catch (RetryableException e) {
				// 执行重试的相关逻辑
			}
		}
	}
}

Создавайте шаблоны запросов на основе разных параметров

  • Представление формы или непосредственное представление тела

Запустите перехватчик запроса, чтобы сгенерировать окончательный запрос.

// 获取全部的请求拦截器,一个个执行
  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }

Обработка журнала запросов

  • уровень вывода журнала, настройка
public enum Level {
	/**
	 * 不输出
	 */
	NONE,
	/**
	 * 只记录输出Http 方法、URL、状态码、执行时间
	 */
	BASIC,
	/**
	 * 输出请求头 和 Http 方法、URL、状态码、执行时间
	 */
	HEADERS,
	/**
	 * 输出请求头、报文体 和 Http 方法、URL、状态码、执行时间
	 */
	FULL
}

Клиент выполняет окончательный запрос запроса

Обработка по умолчанию по умолчанию

  • JDKjava.netРеализация пакета, реализация подключения будет создана без запроса. можно настроить какHttpClientилиOKHttpвысокопроизводительная реализация
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);
		return convertResponse(connection, request);
	}
」

Обработка балансировки нагрузки Spring Cloud

// Spring Cloud 的Client 实现
public class FeignBlockingLoadBalancerClient {
	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		// 例如请求: http://pig-auth-server/token/info
		final URI originalUri = URI.create(request.url());
		// 截取到serviceId: pig-auth-server
		String serviceId = originalUri.getHost();
		// 调用 loadBalancer API 获取到可以的服务实例
		ServiceInstance instance = loadBalancerClient.choose(serviceId);
		// 构建真实的请求URL http://172.17.0.110:8763/token/info
		String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
				.toString();
		// 创建请求 并执行
		Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
				request.headers(), request.requestBody());
		return delegate.execute(newRequest, options);
	}
}

Обработка декодера обратного сообщения

  • Обработка по умолчанию
  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()));
      }
    }
  }

  • Если сообщение возвращается, сообщается об ошибке
  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;
    }
  }
}

внедрить обычайErrorDecoderчаще используется.


Вышеупомянутое содержаниеOpenFeignПоток обработки запроса, ниже приведен расширенный контентspring-cloud-open-feignКак он инициализируется и как работает?

[Расширения] Spring Cloud OpenFeign

ENABLEFEIGNCLIENTS анализ

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    
}
  • Когда мы находимся в методе Main, добавляем@EnableFeignClientsАннотации включеныspring-cloud-open-feignсопутствующие функции.
  • Import(FeignClientsRegistrar.class)Импорт FeignClientsRegistrar, сканирование@FeignClientвлить в контейнер

FeignClientsRegistrar

class FeignClientsRegistrar {
	@Override
	public void registerBeanDefinitions() {
		registerFeignClients(metadata, registry);
	}
	
	public void registerFeignClients() {
		
		// 扫描配置注解中配置范围内的 @FeignClient
		for (String basePackage : basePackages) {
			// 注入IOC 容器
			registerClientConfiguration(registry, name,
							attributes.get("configuration"));
		}
	}
	
	//feignclient <--> bean 构造
	private void registerFeignClient() {
		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,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}
}

Дефолт

public class FeignAutoConfiguration {
    // 未引入 feign-hystrix 模块,则还是注入 DefaultTargeter
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
    protected static class DefaultFeignTargeterConfiguration {
    
    	@Bean
    	@ConditionalOnMissingBean
    	public Targeter feignTargeter() {
    		return new DefaultTargeter();
    	}
    }
}

не представленfeign-hystrixТогда описанный выше процесс аналогичен исходному процессу, мы вызываемfeignclient.methodОн запустит динамический прокси и выполнит логику MethodHandler.

HystrixFeign

  • Во-первых, введенHystrixFeign, значит логика поменялась?

исходный0. 入门Demo Feign.builder()он становится hystrixfeign.builder ()

public final class HystrixFeign {
	public static Builder builder() {
		return new Builder();
	}
	public static final class Builder extends Feign.Builder {
		
		// 注入 HystrixInvocationHandler 的实现
		Feign build(final FallbackFactory<?> nullableFallbackFactory) {
			super.invocationHandlerFactory(new InvocationHandlerFactory() {
				@Override
				public InvocationHandler create() {
					return new HystrixInvocationHandler(target, dispatch, setterFactory,
							nullableFallbackFactory);
				}
			});
			super.contract(new HystrixDelegatingContract(contract));
			return super.build();
		}
		
	}
}

  • инъекцияHystrixInvocationHandlerРеализация обернута с помощью HystrixCommand, и, наконец, обработчик метода используется для вызова окончательного интерфейса.
final class HystrixInvocationHandler implements InvocationHandler {
	
	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {

		// 使用HystrixCommand 包装
		HystrixCommand<Object> hystrixCommand =
			new HystrixCommand<Object>(setterMethodMap.get(method)) {
				@Override
				protected Object run() throws Exception {
					try {
						// 调用 methodhandler 处理最终的请求
						return HystrixInvocationHandler
						.this.dispatch.get(method).invoke(args);
					} catch (Exception e) {
						throw e;
					} catch (Throwable t) {
						throw (Error) t;
					}
				}
			};
		return hystrixCommand.execute();
	}
}

SentinelFeign

  • Первый взгляд на аннотации классовlike {@link HystrixFeign.Builder}, "заимствование" у HystrixFeign
/**
 * {@link Feign.Builder} like {@link HystrixFeign.Builder}.
 */
public final class SentinelFeign {
}
  • инъекцияSentinelInvocationHandlerРеализация использования упаковки Sentinel и, наконец, использование обработчика методов для вызова финального интерфейса.
public class SentinelInvocationHandler implements InvocationHandler {
	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args)
			throws Throwable {
		// 使用sentinel 包装请求
		try {
			ContextUtil.enter(resourceName);
			entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
			result = methodHandler.invoke(args);
		}
		catch (Throwable ex) {
			// fallback 逻辑
		}
		finally {
			ContextUtil.exit();
		}
		return result;
	}
}

Сводная таблица синхронизации

план дальнейших действий

Добро пожаловать, чтобы следовать за мной, обновить позжеRibbon,Hystrix,Sentinel,Nacosи другие компоненты графического анализа исходного кода.

Еще одно примечание: Вышеуказанные фотоматериалы (омниграфле и миллиард картинок) можно найти в публичном аккаунте.JAVA架构日记Получать

"★★★★★" Система управления разрешениями RBAC на основе Spring Boot 2.2, Spring Cloud Hoxton & Alibaba, OAuth2