Feign应用及源码剖析

Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。

以下示例全部摘自官网源码 

简单应用

服务端代码,简单定义一个restful风格的接口,启动spring boot应用:

@EnableDiscoveryClient
@SpringBootApplication
public class ProviderApplication {

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

	@RestController
	class EchoController {

		@GetMapping("/echo/{string}")
		public String echo(@PathVariable String string) {
			System.out.println("hello Nacos Discovery " + string);
			return "hello Nacos Discovery " + string;
		}
	}
}

客户端源代码:

@SpringBootApplication
@EnableDiscoveryClient(autoRegister = true)
@EnableFeignClients
public class ConsumerApplication {

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

	@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class,
			configuration = FeignConfiguration.class)
	public interface EchoService {

		@GetMapping("/echo/{str}")
		String echo(@PathVariable("str") String str);
	}
}

class FeignConfiguration {

	@Bean
	public EchoServiceFallback echoServiceFallback() {
		return new EchoServiceFallback();
	}
}

class EchoServiceFallback implements EchoService {

	@Override
	public String echo(@PathVariable("str") String str) {
		return "echo fallback";
	}
}

客户端定义了Feign相关的核心接口: EchoService接口类,这个接口没有具体的实现类,可以把这个接口理解为Mybatis的Mapper接口,最终调用接口方法实际上是调用这个接口的代理类,向服务端发起调用的,另外spring在解析这个接口也是跟Mybatis用的相同的技术,通过FactoryBean.getObject()调用FeignInvocationHandler生成代理对象注册到spring容器。EchoService的注解@FeignClient定义了服务的应用名称,以及服务降级的措施。

@RestController
public class TestController {

	@Autowired
	private EchoService echoService;

	@GetMapping("/echo-feign/{str}")
	public String feign(@PathVariable String str) {
		return echoService.echo(str);
	}
}

最后定义一个restful接口向外提供调用路口,通过spring的@Autowired注入EchoService代理对象,在方法体内调用echoService.echo()方法就会向服务端发起restful调用。

这样一个完整的Feign调用就完成了:

Feign相关的拓展机制

Feign日志配置 

在application.properties中设置Feign所在包路径的日志级别为Debug,只有这样接下来的Feign配置才能生效:

logging.level.com.alibaba.cloud.examples=debug

在Feign使用中的FeignConfiguration中注册Bean

    /**
	 * 日志级别
	 * NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
	 * BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
	 * HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
	 * FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。
	 * @return
	 */
	@Bean
	public Logger.Level feignLoggerLevel() {
		return Logger.Level.FULL;
	}

 配置为FULL结果:

配置为BASIC:

作用:帮助程序员开发时查看调用信息。

额外的,我们也可以通过定义一个配置类将全局生效,上面的只是局部定义了EchoService接口。

@Configuration
public class FeignConfig {
 
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

Feign拦截器实现参数传递

定义FeignAuthRequestInterceptor类,并实现RequestInterceptor接口的apply()方法,向RequestTemplate中设置token来实现下游服务的身份认证。

public class FeignAuthRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        // 业务逻辑
        String access_token = UUID.randomUUID().toString();
        template.header("Authorization",access_token);
    }
}

 并在FeignConfiguration中定义Bean

    /**
	 * 自定义拦截器
	 * @return
	 */
	@Bean
	public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
		return new FeignAuthRequestInterceptor();
	}

另外,Feign给我们提供了一个基础认证类BasicAuthRequestInterceptor,可直接在配置类中定义这个Bean,

    @Bean
	public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
		return new BasicAuthRequestInterceptor("rick", "123456");
	}

测试:可以看到请求中添加一个请求头kay-value对。

超时时间配置

超时时间包括请求连接超时时间和请求读取(处理)数据超时时间,直接在application.properties中配置

#请求连接超时时间,默认2s
feign.client.config.service-provider.connect-timeout=3000
#请求读取数据超时时间,默认5s
feign.client.config.service-provider.read-timeout=6000

将service-provider中服务睡眠7s,发起调用后

        @GetMapping("/echo/{string}")
		public String echo(@PathVariable String string) {
			System.out.println("hello Nacos Discovery " + string);
			try {
				Thread.sleep(7000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return "hello Nacos Discovery " + string;
		}

look look...Read timed out 了。 

客户端组件拓展

Feign发起调用后默认是使用JDK的HttpURLConnection发起http请求: 

 通过引入Apache httpclient 组件即可替换默认的jdk的HttpURLConnection

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.7</version>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
            <version>10.1.0</version>
        </dependency>

 当然还可以引入其他的http client组件:如

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

GZIP压缩

使用GZIP压缩功能就是将发送的数据进行压缩,提高网络传输效率,通过在客户端配置

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true

发起调用结果: 

注意:GZIP不支持okhttp,在解析时只有不存在okhttp3,配置才会生效

引入okhttp3,并配置使用okhttp3 发起http请求:

feign.httpclient.enabled=false
feign.okhttp.enabled=true

当使用okhttp3时测试结果:没有使用GZIP压缩

编解码器

配置编解码器,不使用默认的编解码,使用配置的:

@Bean
public Decoder decoder() {
    return new JacksonDecoder();
}
@Bean
public Encoder encoder() {
    return new JacksonEncoder();
}

Feign源码解析

@EnableFeignClients

在启动类添加@EnableFeignClients表示开启Feign,该注解导入FeignClientsRegistrar类,其实现了ImportBeanDefinitionRegistrar接口

 spring在解析@Import注解时,会将FeignClientsRegistrar解析为Bean注入到spring容器,并执行其实现的registerBeanDefinitions()方法。

第一个registerDefaultConfiguration() 方法是解析@EnableFeignClients的配置,一般情况都没有。我们重点关注registerFeignClients()方法:

 

首先,解析@EnableFeignClients注解类的clients属性,这里为null,所以执行else的逻辑;创建扫描器,获取扫描的包路径,紧接着开始遍历包路径下符合要求的类(即被@FeignClients注解的接口类),将他们添加candidateComponents这个集合中; 

再遍历candidateComponents集合,调用registerFeignClient():

该方法会创建FeignClientFactoryBean对象,并设置了factoryBean的属性,它实现了spring框架的FactoryBean接口,最后将factoryBean封装为BeanDefinition,注册spring容器中

FeignClientFactoryBean实现了该接口的两个核心方法getObject()和getObjectType(),其中变量definition持有的lambda表达式会调用getObject(),最终会在spring中执行这个方法

 target():1、拼接URL。2.调用loadBalance通过targeter.target()方法创建代理对象并返回

 

最终会创建ReflectiveFeign实例,并调用其newInstance()创建FeignInvocationHandler(实现了InvocationHandler)对象,被代理的对象是HystrixTargeter(从spring容器获取的),还维护了dispatch集合,它封装了所有被代理对象的方法,最后通过JDK动态代理创建代理对象并返回。至此,整个过程就结束了。

总结

spring cloud解析Feign接口,然后通过spring容器获取Targeter,最后生成Targeter的代理对象通过FactoryBean注册到spring容器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值