Spring Cloud进阶之路之OpenFeign源码解析

本文详细介绍了Spring Cloud OpenFeign的使用、源码解析及其配置优化。OpenFeign是一个声明式Web服务客户端,简化了服务调用客户端的开发。在源码解析部分,文章探讨了Feign Bean的创建过程,包括@FeignClient注解的初始化、Feign动态代理的实现,以及请求的发送逻辑。同时,文章还提到了配置优化,如超时时间和请求重试的设置。
摘要由CSDN通过智能技术生成

1. OpenFeign 简介

Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

Feign 灵感来源于 Retrofit , JAXRS-2.0 和 WebSocket,Feign 最初是为了降低统一绑定 Denominator 到 HTTP APIs 的复杂度,不管是否是 Restful。

Feign旨在使编写Java Http客户端变得更容易,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring Cloud时,自动封装服务调用客户端的开发量。

Feign OpenFeign
Feign是spring cloud组件中的一个轻量级Restful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。 OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如@RequestMapping等等。OpenFeign的@FiegnClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务

2. OpenFiegn的使用

  1. 在Spring Cloud应用中使用Feign组件,首先需要在依赖包中加入以下依赖:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 再定义一个可供调用的Feign组件示例如下:
@FeignClient(value = "servicename")
public interface IUserService {
	/**
	 * 获取用户信息
	 *
	 * @param username 用户名
	 * @return 用户信息
	 */
    @RequestMapping(value = "getUserDetailInfo", method = RequestMethod.GET)
  ApiResult<UserDetailInfo> getUserDetailInfo(@RequestParam("username") String username);
}

上述代码中,每一个方法都代表通过 Feign 请求的一个接口, @RequestMapping 指定请求地址和请求方法。 @FeignClient 则用于指定调用的微服务,结合负载均衡框架在注册中心注册的服务列表中选择一个合适的服务地址。 目前对FeignClient的应用主要有2种方式:

  • 服务提供方定义好 Feign 组件,自己的 Controller 实现定义好的Feign组件接口,然后把Feign组件打包成SDK提供给调用方,这样的好处是便于后期服务提供方统一升级组件,比如更换调用路径和参数等;
  • 服务调用方自己封装 Feign 组件,这样的好处是调用方可以灵活的自定义Feign组件;

注意:目前Feign调用对基本类型参数传值支持不是很好,所以参数最好封装成一个 DTO对象 进行传输。如果是JSON参数,最好再定义Feign组件时加上 @RequestBody 注解。

  1. 启用Feign组件是使用Feign的第一步,使用如下注解开启:
//启用Feign组件并配置扫描包路径
@EnableFeignClients(basePackages = {"com.xxx.service.api"})
  1. 接下来就是在需要调用接口的地方用Spring Bean一样调用其他服务了,示例如下:
@Autowired
private IUserService userservice;

3. OpenFeign源码解析

3.1. Feign Bean创建

Feign组件初始化是从 @EnableFeignClients 注解开始的,注解源码截图如下:

@EnableFeignClients 核心有2个方法, basePackages 与 defaultConfiguration ,前者用于定义扫描包路径,后者用于定义@FeignClient组件的配置类,在配置类中可以自己定义Feign请求的 Decoder 解码器、 Encoder 编码器、 Contract 组件扫描构造器。 在注解上有一个关键注解 @Import(FeignClientsRegistrar.class) ,导入了Feign组件的注册器,用于扫描Feign组件与初始化Feign组件的Bean定义信息,各阶段作用建下述源码注释。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	// patterned after Spring Integration IntegrationComponentScanRegistrar
	// and RibbonClientsConfigurationRegistgrar

	private ResourceLoader resourceLoader;

	private Environment environment;

	FeignClientsRegistrar() {
	}

	static void validateFallback(final Class clazz) {
		Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient");
	}

	static void validateFallbackFactory(final Class clazz) {
		Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
				+ "of fallback classes that implement the interface annotated by @FeignClient");
	}

	static String getName(String name) {
		if (!StringUtils.hasText(name)) {
			return "";
		}

		String host = null;
		try {
			String url;
			if (!name.startsWith("http://") && !name.startsWith("https://")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			host = new URI(url).getHost();

		}
		catch (URISyntaxException e) {
		}
		Assert.state(host != null, "Service id not legal hostname (" + name + ")");
		return name;
	}

	static String getUrl(String url) {
		if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
			if (!url.contains("://")) {
				url = "http://" + url;
			}
			try {
				new URL(url);
			}
			catch (MalformedURLException e) {
				throw new IllegalArgumentException(url + " is malformed", e);
			}
		}
		return url;
	}

	static String getPath(String path) {
		if (StringUtils.hasText(path)) {
			path = path.trim();
			if (!path.startsWith("/")) {
				path = "/" + path;
			}
			if (path.endsWith("/")) {
				path = path.substring(0, path.length() - 1);
			}
		}
		return path;
	}

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

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
		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) {

		LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
		Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
		final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
				candidateComponents.addAll(scanne
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值