Spring Cloud服务调用

版本信息

Spring Cloud : Hoxton.SR1

Spring Boot : 2.2.2.RELEASE

Zookeeper : 3.5.6 (注册中心使用)

OpenFeign核心api

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0,
and WebSocket

  • feign.Feign 核心类
  • feign.Contract 服务接口类的注解和值解析
  • feign.Client 调用
  • feign.Retryer 重试
  • feign.codec.Encoder 序列化,对象转成Http请求
  • feign.codec.Decoder 反序列化,Http请求转换为对象
  • feign.QueryMapEncoder 查询参数的编码
  • feign.codec.ErrorDecoder 错误信息编码
  • feign.Request 请求参数
  • feign.InvocationHandlerFactory 控制反射方法分派

Spring Cloud Feign使用

服务端

一个简单的对外提供服务,基于Zookeeper注册中心

1.添加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--zookeeper 客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

2.简单的对外提供服务端点

/**
 * 演示服务端点
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@RestController
public class EchoController {

    @Autowired
    private Environment environment;


    @GetMapping("/echo")
    public String echo(@RequestParam String message) {
        // 由于采用的是随机端口,这地方必须采用这个方式获取端口
        String port = environment.getProperty("local.server.port");
        return "ECHO(" + port + "):" + message;
    }
}

3.配置文件application.yml

spring:
  application:
    name: feign-server
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181
server:
  port: 0

4.服务启动程序类FeignServerApplication

/**
 * Feign 服务端应用
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@SpringBootApplication
public class FeignServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignServerApplication.class, args);
    }
}

启动服务,根据启动日志查看本地的随机端口,此次端口是60981

http://127.0.0.1:60981/echo?message=Hello 返回信息ECHO(60981):Hello

客户端
1.添加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--zookeeper 客户端-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<!--openfeign 客户端调用-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.服务提供接口

/**
 * 服务提供echo服务
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@FeignClient("feign-server") //标注是Feign Client
public interface EchoService {

    @GetMapping("/echo")
    String echo(@RequestParam("message") String message);
}

3.客户端提供服务端点

/**
 * 演示服务端点
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@RestController
public class EchoController {

    @Autowired
    private EchoService echoService;

    @GetMapping("/echo")
    public String echo(String message) {
        return this.echoService.echo(message);
    }
}

4.配置文件application.yml

spring:
  application:
    name: feign-client
  cloud:
    zookeeper:
      connect-string: 127.0.0.1:2181
server:
  port: 8090

5.服务启动程序类FeignClientApplication

/**
 * Feign 客户端服务
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@SpringBootApplication
@EnableFeignClients(clients = EchoService.class) //启用Feign Client
public class FeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }
}

启动服务,http://localhost:8090/echo?message=hello 返回信息ECHO(60981):hello

RestTemplate自定义实现服务调用

自定义客户端调用注解

/**
 * rest 客户端调用
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestFeignClient {

    /**
     * 服务名称
     *
     */
    String value() default "";
}

自定义客户端调用服务

/**
 * 服务提供echo服务
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@RestFeignClient("feign-server") //标注是Feign Client
public interface RestEchoService {

    @GetMapping("/echo")
    String echo(@RequestParam("message") String message);
}

自定义客户端启用注解

/**
 * 启用Rest 客户端
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RestFeignClientsRegistrar.class)
public @interface EnableRestFeignClients {

    /**
     * {@link RestFeignClient}接口列表
     */
    Class<?>[] clients() default {};
}

导入RestFeignClientsRegistrar

/**
 * rest 客户端注册
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
public class RestFeignClientsRegistrar
        implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 类加载类
        ClassLoader classLoader = metadata.getClass().getClassLoader();
        // 获取EnableRestFeignClients注解的内容
        Map<String, Object> annotationAttributes = metadata
                .getAnnotationAttributes(EnableRestFeignClients.class.getName());
        // 获取clients
        Class<?>[] clients = (Class<?>[]) annotationAttributes.get("clients");
        Stream.of(clients)
                .filter(Class::isInterface)
                .filter(interfaceClass ->
                        Objects.nonNull(AnnotationUtils.findAnnotation(interfaceClass, RestFeignClient.class)))
                .forEach(restClientClass -> {
                    // 获取服务名称
                    RestFeignClient restFeignClient = AnnotationUtils
                            .findAnnotation(restClientClass, RestFeignClient.class);
                    String serviceName = restFeignClient.value();


                    Object proxyInstance = Proxy.newProxyInstance(classLoader, new Class<?>[]{restClientClass},
                            new RestFeignClientInvocationHandler(serviceName, this.beanFactory));

                    // 注册Bean
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                            .genericBeanDefinition(RestFeignClientFactoryBean.class);
                    beanDefinitionBuilder.addConstructorArgValue(proxyInstance);
                    beanDefinitionBuilder.addConstructorArgValue(restClientClass);
                    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
                    String beanName = serviceName + restClientClass.getName();
                    beanDefinition.setFactoryBeanName("&" + beanName);
                    registry.registerBeanDefinition(beanName, beanDefinition);

                });
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    private static class RestFeignClientFactoryBean implements FactoryBean<Object> {

        private final Object object;
        private final Class<?> objectType;

        private RestFeignClientFactoryBean(Object object, Class<?> objectType) {
            this.object = object;
            this.objectType = objectType;
        }


        @Override
        public Object getObject() throws Exception {
            return this.object;
        }

        @Override
        public Class<?> getObjectType() {
            return this.objectType;
        }
    }
}

动态代理调用RestFeignClientInvocationHandler

/**
 * Rest 客户端调用
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
public class RestFeignClientInvocationHandler implements InvocationHandler {

    private final String serverName;

    private final BeanFactory beanFactory;

    public RestFeignClientInvocationHandler(String serverName, BeanFactory beanFactory) {
        this.serverName = serverName;
        this.beanFactory = beanFactory;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 带有requestMapping的方法
        RequestMapping requestMapping =
                AnnotatedElementUtils
                        .findMergedAnnotation(method, RequestMapping.class);
        // 没有RequestMapping的方法调用原来的方法
        if (Objects.isNull(requestMapping)) {
            return method.invoke(proxy, args);
        }
        // 路径,默认只取第一个
        String path = requestMapping.path()[0];
        // beanFactory的默认实现是DefaultListableBeanFactory,可以直接强制转成ListableBeanFactory
        ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
        // 获取所有的RestTemplate的Bean
        Map<String, RestTemplate> restTemplateMap = listableBeanFactory
                .getBeansOfType(RestTemplate.class);
        // 获取标注有的@LoadBalanced的RestTemplate的BeanName
        String restTemplateName = restTemplateMap.keySet()
                .stream()
                .filter(beanName -> Objects
                        .nonNull(listableBeanFactory.findAnnotationOnBean(beanName,
                                LoadBalanced.class)))
                .findAny()
                .orElseThrow(() -> new RuntimeException("没有@LoadBalanced标注的RestTemplate"));
        // 获取标注有的@LoadBalanced的RestTemplate
        RestTemplate restTemplate = restTemplateMap.get(restTemplateName);
        StringBuilder paramBuilder = new StringBuilder("http://").append(serverName).append(path).append("?");
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[0];
            RequestParam requestParam = AnnotationUtils.getAnnotation(parameter, RequestParam.class);
            if (Objects.isNull(requestParam)) {
                continue;
            }
            String paramName = requestParam.value();
            String paramValue = String.valueOf(args[i]);
            paramBuilder.append("&").append(paramName).append("=").append(paramValue);
        }
        String url = paramBuilder.toString();
        RequestMethod[] requestMethods = requestMapping.method();
        // 默认取第一个
        HttpMethod httpMethod = HttpMethod.resolve(requestMethods[0].name());
        ResponseEntity<?> exchange = restTemplate.exchange(url, httpMethod, null,
                method.getReturnType());
        return exchange.getBody();
    }
}

启动添加启用注解

/**
 * Feign 客户端服务
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@SpringBootApplication
@EnableFeignClients(clients = EchoService.class) //启用Feign Client
@EnableRestFeignClients(clients = RestEchoService.class) // 启动Rest Client
public class FeignClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class, args);
    }

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

演示服务端点

/**
 * 演示服务端点
 *
 * @author FelixFly <chenglinxu@yeah.net>
 * @date 2020/2/7
 */
@RestController
public class EchoController {

    @Autowired
    private EchoService echoService;

    @Autowired
    private RestEchoService restEchoService;

    @GetMapping("/echo")
    public String echo(String message) {
        return this.echoService.echo(message);
    }

    @GetMapping("/rest/echo")
    public String restEcho(String message) {
        return this.restEchoService.echo(message);
    }
}

启动服务,http://localhost:8090/rest/echo?message=hello
返回信息ECHO(60981):hello

Spring Cloud Feign源码分析

@EnableFeignClients分析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {

	...
}

导入了FeignClientsRegistrar

class FeignClientsRegistrar
		implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

	FeignClientsRegistrar() {
	}

	...

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // 注册默认配置
		registerDefaultConfiguration(metadata, registry);
        // 注册Feign 客户端
		registerFeignClients(metadata, registry);
	}

	private void registerDefaultConfiguration(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		Map<String, Object> defaultAttrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName(), true);

		if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
			String name;
			if (metadata.hasEnclosingClass()) {
				name = "default." + metadata.getEnclosingClassName();
			}
			else {
				name = "default." + metadata.getClassName();
			}
			registerClientConfiguration(registry, name,
					defaultAttrs.get("defaultConfiguration"));
		}
	}

	public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));
					 // 注册Feign 客户端
					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
        // 定义了一个FeignClientFactoryBean的BeanDefinition
		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);

		String alias = contextId + "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);
	}

	...

}

注册了FeignClientFactoryBean

class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

	/***********************************
	 * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
	 * lifecycle race condition.
	 ***********************************/

	private Class<?> type;

	private String name;

	private String url;

	private String contextId;

	private String path;

	private boolean decode404;

	private ApplicationContext applicationContext;

	private Class<?> fallback = void.class;

	private Class<?> fallbackFactory = void.class;

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.hasText(this.contextId, "Context id must be set");
		Assert.hasText(this.name, "Name must be set");
	}

	protected Feign.Builder feign(FeignContext context) {
		FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
		Logger logger = loggerFactory.create(this.type);

		// @formatter:off
		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));
		// @formatter:on

		configureFeign(context, builder);

		return builder;
	}

	protected void configureFeign(FeignContext context, Feign.Builder builder) {
		FeignClientProperties properties = this.applicationContext
				.getBean(FeignClientProperties.class);
		if (properties != null) {
			if (properties.isDefaultToProperties()) {
				configureUsingConfiguration(context, builder);
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
			}
			else {
				configureUsingProperties(
						properties.getConfig().get(properties.getDefaultConfig()),
						builder);
				configureUsingProperties(properties.getConfig().get(this.contextId),
						builder);
				configureUsingConfiguration(context, builder);
			}
		}
		else {
			configureUsingConfiguration(context, builder);
		}
	}

	protected void configureUsingConfiguration(FeignContext context,
			Feign.Builder builder) {
		Logger.Level level = getOptional(context, Logger.Level.class);
		if (level != null) {
			builder.logLevel(level);
		}
		Retryer retryer = getOptional(context, Retryer.class);
		if (retryer != null) {
			builder.retryer(retryer);
		}
		ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
		if (errorDecoder != null) {
			builder.errorDecoder(errorDecoder);
		}
		Request.Options options = getOptional(context, Request.Options.class);
		if (options != null) {
			builder.options(options);
		}
		Map<String, RequestInterceptor> requestInterceptors = context
				.getInstances(this.contextId, RequestInterceptor.class);
		if (requestInterceptors != null) {
			builder.requestInterceptors(requestInterceptors.values());
		}
		QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);
		if (queryMapEncoder != null) {
			builder.queryMapEncoder(queryMapEncoder);
		}
		if (this.decode404) {
			builder.decode404();
		}
	}

	protected void configureUsingProperties(
			FeignClientProperties.FeignClientConfiguration config,
			Feign.Builder builder) {
		if (config == null) {
			return;
		}

		if (config.getLoggerLevel() != null) {
			builder.logLevel(config.getLoggerLevel());
		}

		if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
			builder.options(new Request.Options(config.getConnectTimeout(),
					config.getReadTimeout()));
		}

		if (config.getRetryer() != null) {
			Retryer retryer = getOrInstantiate(config.getRetryer());
			builder.retryer(retryer);
		}

		if (config.getErrorDecoder() != null) {
			ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
			builder.errorDecoder(errorDecoder);
		}

		if (config.getRequestInterceptors() != null
				&& !config.getRequestInterceptors().isEmpty()) {
			// this will add request interceptor to builder, not replace existing
			for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
				RequestInterceptor interceptor = getOrInstantiate(bean);
				builder.requestInterceptor(interceptor);
			}
		}

		if (config.getDecode404() != null) {
			if (config.getDecode404()) {
				builder.decode404();
			}
		}

		if (Objects.nonNull(config.getEncoder())) {
			builder.encoder(getOrInstantiate(config.getEncoder()));
		}

		if (Objects.nonNull(config.getDecoder())) {
			builder.decoder(getOrInstantiate(config.getDecoder()));
		}

		if (Objects.nonNull(config.getContract())) {
			builder.contract(getOrInstantiate(config.getContract()));
		}
	}

	private <T> T getOrInstantiate(Class<T> tClass) {
		try {
			return this.applicationContext.getBean(tClass);
		}
		catch (NoSuchBeanDefinitionException e) {
			return BeanUtils.instantiateClass(tClass);
		}
	}

	protected <T> T get(FeignContext context, Class<T> type) {
		T instance = context.getInstance(this.contextId, type);
		if (instance == null) {
			throw new IllegalStateException(
					"No bean found of type " + type + " for " + this.contextId);
		}
		return instance;
	}

	protected <T> T getOptional(FeignContext context, Class<T> type) {
		return context.getInstance(this.contextId, type);
	}

	protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
			HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			Targeter targeter = get(context, Targeter.class);
			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?");
	}

	@Override
	public Object getObject() throws Exception {
		return getTarget();
	}

	/**
	 * @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() {
		FeignContext context = this.applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(this.url)) {
			if (!this.name.startsWith("http")) {
				this.url = "http://" + this.name;
			}
			else {
				this.url = this.name;
			}
			this.url += cleanPath();
			return (T) loadBalance(builder, context,
					new HardCodedTarget<>(this.type, this.name, this.url));
		}
		if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
			this.url = "http://" + this.url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof LoadBalancerFeignClient) {
				// not load balancing because we have a url,
				// but ribbon is on the classpath, so unwrap
				client = ((LoadBalancerFeignClient) client).getDelegate();
			}
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}
		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context,
				new HardCodedTarget<>(this.type, this.name, url));
	}

	...

}

构建了一个Feign调用,客户端有两种LoadBalancerFeignClient以及FeignBlockingLoadBalancerClient,

配置属性FeignClientProperties

OpenFeign核心api在Spring Cloud中的扩展

org.springframework.cloud.openfeign.FeignClientsConfiguration默认的配置类

  • feign.Contract 服务接口类的注解和值解析
    • org.springframework.cloud.openfeign.support.SpringMvcContract

支持Spring MVC的注解,比如@RequestMapping、@PathVariable等等

参数注解处理核心接口:org.springframework.cloud.openfeign.AnnotatedParameterProcessorr

•@PathVariable PathVariableParameterProcessor
•@RequestParam RequestParamParameterProcessor
•@RequestHeader RequestHeaderParameterProcessor
•@SpringQueryMap QueryMapParameterProcessor

  • feign.Client 调用
    - org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient Ribbon客户端调用
    - org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient阻塞的Feign客户端,Spring Cloud LoadBalanced实现
  • feign.Retryer 重试
    - Retryer.NEVER_RETRY 不进行重试
  • feign.codec.Encoder 序列化,对象转成Http请求
    - new SpringEncoder(this.messageConverters) 普通的反序列化
    - new PageableSpringEncoder(new SpringEncoder(this.messageConverters))带有分页参数的反序列化

从上可以得知,这个序列是通过HttpMessageConverter组合去处理的

  • feign.codec.Decoder 反序列化,Http请求转换为对象
    - new OptionalDecoder(new ResponseEntityDecoder(new
    SpringDecoder(this.messageConverters)))

从上可以得知,这个反序列是通过HttpMessageConverter组合去处理的

原文:https://gper.club/articles/7e7e7f7ff4g5egc7g6c


版权声明:本文为csdnteach原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。