OpenFeign---常用功能及源码解析
先手总结一波:
(1)首先通过@EnableFeignClients注解开启FeignClient。只有这个注解存在,才会在程序启动的时候开启@FeignClient注解的包扫描。
(2)根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
(3)程序启动之后,会进行包扫描,并扫描所有的@FeignClient的注解的类,并将这些信息注入的IOC容器中。
(4)当接口的方法被调用的时候,通过JDK的代理来生成具体的RequestTemplate模板对象。
(5)根据RequestTemplate再生成Http请求的Request对象。
(6)Request对象交给Client对象去处理,其中Client的网络请求框架可以是HttpURLConnection,HttpClient和OkHttp。
(7)最后Client被封装到LoadBalanceClient。该类结合了Ribbon做到了负载均衡。
1.1 FeignClient的配置
该配置是在
@FeignClient(configuration="XXXXX.class")
Feign Client默认的配置类是FeignClientsConfiguration,该类注入了很多的Feign的相关配置Bean,包括FeignReTryer(重试机制), FeignLoggerFactory和FormattingConversionService等。另外,Decoder,Encoder,Contract这三个类在没有Bean被注入的情况下,会自动注入默认配置的Bean,即ResponseEntityDecoder,SpringEncoder,SpringMvcContract。默认注入的配置:
- Decoder feignDecoder: ResponseEntityDecoder\
- Encoder feignEncoder: SpringEncoder
- Logger feignLogger: Slf4jLogger
- Contract feignContract:SpringMvcContract
- Feign.Builder feignBuilder: HystrixFeign.Builder
源码:
@Configuration
public class FeignClientsConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Autowired(required = false)
private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>();
@Autowired(required = false)
private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>();
@Autowired(required = false)
private Logger logger;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
}
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
}
@Bean
public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService);
}
return conversionService;
}
@Bean
@ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
@Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(this.logger);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public Module pageJacksonModule() {
return new PageJacksonModule();
}
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
通过上面的代码可以看到我们在decoder/encoder/contract中使用了@ConditionOnMissingBean
举例说明:
我们自己生成一个bean,自定义配置。该配置的作用是在调用服务失败的时候可以进行重试机制。
@Configuration
public class FeignConfig{
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, SECONDS.toMills(1), 5)
}
}
上面大的代码更改了FeignClient的请求重试机制,最大间隔100ns, 最大重试1s,重试次数5次。
1.2 从源码的角度讲解Feign
- Feign是一个伪Java Http客户端,Feign不做任何的请求处理。Feign通过处理注解生成Request模板,从而简化Http API的开发。开发人员可以使用注解的方式定制Request API模板。在发送Http Request请求之前,Feign通过注解的方式将请求拼接完成,用参数替换掉模板中的参数,生成真正的Request,并将其交给Java Http客户端处理。
Feign通过扫描注入FeignClient的Bean,该源码在FeignClientsRegister类中。
首先在启动的时候会检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描@FeignClient注解的接口
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 得到@FeignClients注解中的属性,包括value,basePackages,basePackageClasses,defaultConfiguration,clients
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
// 得到@FeignClients注解在哪个类上
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
- 当程序的启动类上有@EnableFeignClients注解。在程序启动后,程序就会通过包扫描将所有的@FeignClient注解修饰的接口连同接口名和注解的信息一起取出,赋值给BeanDefinitionBuilder。然后根据此得到BeanDefinition,最后将BeanDefinition注入到IOC容器中,程序如下
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
/**
* 开始处理@FeignClient包下的所有的注解信息
**/
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 得到的是@FeignClient的类名
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 验证各个属性(即@FeignClient注解中的属性)
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);
}
- 注入BeanDefinition之后,通过jdk的代理,当调用Feign Client接口里面的方法的时候,该方法会被拦截,源码如下:
@Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
- 在SynchronousMethodHandler类进行拦截处理,即调用@FeignClient注解中的方法时具体执行的处理。该类会进行拦截处理。会根据参数生成完整的RequestTemplate对象,该对象是Http请求的模板,代码如下:
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
- 经过上述的代码,我们已经将restTemplate拼装完成,上面的代码中有一个==executeAndDecode()==方法,该方法通过RequestTemplate生成Request请求对象,然后利用Http Client获取response,来获取响应信息。
代码:(这里只讲解最重要的一行代码)
response = client.execute(request, options);
注意: 这里的client对象是个重点,他负责发送之前方法一步一步拼装好的restTemplate生成的request请求,client在后面会详细讲解,该对象也是Feign实现负载均衡的关键
1.3 在Feign中使用HttpClient和OkHttp
在Feign中,Client是一个非常重要的组件(上面源码已经介绍到了Client,这里将详细介绍),Feign最终发送Request请求以及接受Response响应都是由Client组件完成的。Client在Feign中是一个接口,在默认的情况下,Client的实现类是Client.Default,Client.Default是由HttpURLConnection来实现网络请求的。另外,Client还支持HttpClient和OkHttp来进行网络请求的。
- 首先查看FeignRibbonClient的自动配置类FeignRibbonClientAutoConfiguration,该类在工程启动的时候注入了一些Bean,其中BeanName是FeignClient的Client类型的Bean。代码如下
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(...) {
return new LoadBalancerFeignClient(...);
}
上面的类省略方法的参数和返回的参数,这里主要观察的是其返回的是一个loadBalancerFeignClient
-
在缺省配置BeanName为FeignClient的Bean的情况下,会自动注入Client.Default这个对象,跟踪Client.Default源码,发现其使用的网络请求框架为HttpURLConnection,他是用HttpURLConnection的convertResponse方法得到response的。
-
我们如何更改Feign中的网络框架为HttpClient呢?
(1)在pom文件中加入HttpClient的依赖
(2)在application.yml中配置feign.httpclient.enabled修改为true -
我们如何更改Feign中的网络框架为OkHttp呢?
(1)只需要在pom文件中导入其相关依赖就行,其他的都不用改变
1.4 Feign是如何实现负载均衡的
FeignRibbonClientAutoConfiguration类配置了Client的类型(包括了HttpURLConnection,OkHttp和HttpClient),最终向容器注入的是Client的实现类LoadBalancerFeignClient,该类就是实现负载均衡的关键,即负载均衡客户端。
- 查看LoadBalancerFeignClient类中的execute方法,即执行请求的方法,代码如下:
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
- 其中executeWithLoadBalancer方法,即通过负载均衡的方法执行网络的请求,代码如下:
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
其submit方法是LoadBalancerCommand类中的方法,其submit方法中有一个selectServer的方法,该方法的作用就是选择服务器进行负载均衡的操作,代码如下:
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
从上面的代码可以看出,最终请求交给了LoadBalancerContext来进行处理,其会先选择一个合适的server进行返回,然后会用Feign的Client对象调用其request请求,得到响应。