feign的基本使用
引入openfeign依赖
org.springframework.cloud
spring-cloud-starter-openfeign
创建服务调用接口
@FeignClient(value = "user-service",path = "/user")
public interface IUserService {
@PostMapping("/name")
String getName(@RequestParam(name = "uid",defaultValue = "0") Integer uid);
}
启动类上开启feign功能,并指定调用接口所在的包
@EnableFeignClients(basePackages = "spring.cloud.user.feign.api")
就可以直接注入使用
@Autowired
private IUserService iUserService;
@GetMapping("/userinfo/{uid}")
public String getUserInfo(@PathVariable("uid") Integer uid){
return iUserService.getName(uid);
}
feign相关配置
日志配置
# 调用日志级别
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
日志等级有 4 种
- NONE:不输出日志。
- BASIC:只输出请求方法的 URL 和响应的状态码以及接口执行的时间。
- HEADERS:将 BASIC 信息和请求头信息输出。
- FULL:输出完整的请求信息。
还需要在application.properties中额外指定
logging.level.spring.cloud.user.feign.api=debug
这样在调用的时候就能展示详细的信息
拦截器配置
public class FeignRequestInteceptor implements RequestInterceptor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("platform","app");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (this.applicationContext == null) {
this.applicationContext = applicationContext;
}
}
}
@Configuration
public class FeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
RequestInterceptor requestInterceptor() {
return new FeignRequestInteceptor();
}
}
在调用前我们可以提前设置服务端需要的参数
连接和请求超时时间配置
@Bean
public Request.Options options() {
return new Request.Options(6000, 60000);
}
http通信框架配置
添加依赖
io.github.openfeign
feign-okhttp
application.properties中添加配置
feign.httpclient.enabled=false
feign.okhttp.enabled=true
这样就会使用okhttp通信
压缩配置 当没有配置okhttp的时候才会生效
# 数据传输的时候做压缩,可以提高请求效率
feign.compression.request.enabled=true
feign.compression.response.enabled=true
# 可以配置压缩类型和最小压缩标准
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
上面的配置依然可以用配置文件的方式配置
# 链接超时时间
feign.client.config.user-service.connectTimeout=5000
# 读取超时时间
feign.client.config.user-service.readTimeout=5000
# 日志等级
feign.client.config.user-service.loggerLevel=full
# 重试
feign.client.config.user-service.retryer=
feign.client.config.user-service.requestInterceptors[0]=
feign.client.config.user-service.requestInterceptors[1]=
feign源码
feign源码一切起源于@EnableFeignClients
该注解装配了@Import(FeignClientsRegistrar.class)
所以我们要看FeignClientsRegistrar
这个类
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
#注册默认的配置
registerDefaultConfiguration(metadata, registry);
#扫描并注入带有FeignClient注解的类
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set basePackages;
Map 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 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 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 attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map attributes) {
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);
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 = name + "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);
}
我们发现@FeignClient注解的接口都会被装载成FeignClientFactoryBean对象,而该对象又是一个FactoryBean对象,所以最终ioc中Bean就是该对象的getObeject方法的返回值
@Override
public Object getObject() throws Exception {
return getTarget();
}
T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
openfeign自动装配了FeignRibbonClientAutoConfiguration
FeignAutoConfiguration
两个
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
在FeignAutoConfiguration
类中,又帮我们转载了一些默认类
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
public class FeignAutoConfiguration {
@Autowired(required = false)
private List configurations = new ArrayList<>();
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
因此,我们回过头来看
T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
}
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 = 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.name), builder);
} else {
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
configureUsingProperties(properties.getConfig().get(this.name), builder);
configureUsingConfiguration(context, builder);
}
} else {
configureUsingConfiguration(context, builder);
}
}
在这里我们看到了feignclient配置的装载,先装在@EnableFeignClients注解中配置的defaultConfiguration,然后在加载@FeignClient注解中配置的configuration,最后加载application.properties中配置的。
同时还将url组装成了http://user-service/user
protected T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget 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?");
}
这里的Client
为LoadBalancerFeignClient
,在自动装配中FeignRibbonClientAutoConfiguration
类中import
进来的,而Targeter
则是DefaultTargeter
@Configuration
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
public T target(Target target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
public T newInstance(Target target) {
Map nameToHandler = targetToHandlersByName.apply(target);
Map methodToHandler = new LinkedHashMap();
List defaultMethodHandlers = new LinkedList();
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;
}
最终我们看到这些@FeignClient注解的接口被定义成了MethodHandler
并且保存到了Map
中,而该MethodHandler
则是SynchronousMethodHandler
,
最终又将该信息存入到代理类中,并且返回代理类FeignInvocationHandler
。
@Override
public InvocationHandler create(Target target, Map dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
到这里,我们已经知道了我们注入使用的接口实际上是FeignInvocationHandler
,而我们的@FeignClient的定义接口实际上是为了通过该类获取我们一个http请求所需的信息,包括方法,调用接口路径,请求方式,返回值,参数等等这些信息,并且组装成了一个个的SynchronousMethodHandler
,等我们调用接口方法的时候,就会获取对应的SynchronousMethodHandler
处理请求。
继续来到这个代理类
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object
otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
@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) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
这里创建了一个RequestTemplate
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(new RequestTemplate(template));
}
可以看到,我们的拦截器实际上就是在这里被调用了。
@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);
}
}
最终由RequestTemplate
创建了Request
请求对象来到LoadBalancerFeignClient.execute方法
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName);
if(client != null) {
return client;
}
IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
这里我们可以看到,由于feign包中默认引入了ribbon自动装配包,所以ILoadBalancer
就是ZoneAwareLoadBalancer
,并且被封装了成FeignLoadBalancer
返回。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation() {
@Override
public Observable 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);
}
}
}
Observable o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1>() {
private Observable selectServer() {
return Observable.create(new OnSubscribe() {
@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);
}
}
});
}
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
Server svc = lb.chooseServer(loadBalancerKey);
可以看到Server就是用ribbon负载均衡器获取的,所以到这里又进入了ribbon的处理逻辑了。
总结
feign 实际上就是通过接口定义的形式,帮我们生成了一个代理类,该代理类中包含每个方法对应的http请求信息,然后通过调用方法获取对应的信息并且生成一个http://user-service/name 对应的url,然后集成ribbon将服务名解析成具体的host地址,最后执行http请求