Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。
@FeignClient(value = "qrcodepay-dike-service")public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get();
}我们只需要在相应的接口上添加@FeignClient注解即可将他声明为一个web客户端。这其中的原理我们后续分析。我们首先先关注下feign暴露的几个配置。
value: 目标服务名,一般都是 application.name
fallback : 服务降级策略
@FeignClient(value = "qrcodepay-dike-service",fallback = TestRoute.TestRouteFaback.class)
public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get();
}
@Component class TestRouteFaback implements TestRoute{
@Override public HdResult get() { return HdResult.makeFail("服务降级");
}
}
fallbackFactory :fallback的升级版,可以获取更加详细的异常信息
@FeignClient(value = "qrcodepay-dike-service",fallbackFactory = TestRoute.TestRouteFallbackFactory.class)public interface TestRoute {
@RequestMapping(value = "/dike/get", method = RequestMethod.GET)
HdResult get();
@Component class TestRouteFallbackFactory implements FallbackFactory<TestRoute>{ private static final Logger logger = LoggerFactory.getLogger(TestRouteFallbackFactory.class);
@Override public TestRoute create(Throwable throwable) {
String msg = throwable == null ? "" : throwable.getMessage(); if (!StringUtils.isEmpty(msg)) {
logger.error("异常信息打印:{}",msg);
} return new TestRoute() {
@Override public HdResult get() { return HdResult.makeFail(msg);
}
};
}
}
}configuration:重写feign的配置
具体哪些内容可以配置我们可以看 FeignClientsConfiguration和feign.Feign.Builder。
下面用两种方式重写feign的配置
覆盖原有的配置bean达到重写目的
@Configurationpublic class FeignBreakerConfiguration {
@Bean public ErrorDecoder errorDecoder() { return new UserErrorDecoder();
} /**
* 自定义错误解码器 只有返回http status 非200才会进入 */
public class UserErrorDecoder implements ErrorDecoder { private Logger logger = LoggerFactory.getLogger(getClass());
@Override public Exception decode(String methodKey, Response response) {
Exception exception = null; try {
String json = Util.toString(response.body().asReader());
System.out.println("自定义解码:"+json);
exception = new RuntimeException(json);
HdResult result=HdResult.makeFail(json); // 业务异常包装成 HystrixBadRequestException,不进入熔断逻辑// if (!result.isSuccess()) {// exception = new HystrixBadRequestException(result.getMessage());// }
} catch (IOException ex) {
logger.error(ex.getMessage(), ex);
} return exception;
}
}
}自定义客户端达到重写的目的
@Import(FeignClientsConfiguration.class)
@RestControllerpublic class DefaultController { private FeignClientService feignClientService; public DefaultController(Decoder decoder, Encoder encoder, Client client){ this.feignClientService = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user","password"))
.target(FeignClientService.class,"http://eureka-client");
}
@RequestMapping(name = "/default",method = RequestMethod.GET) public String getInfo(){ return feignClientService.getValue("hello world!");
}
}feignclient最常用的配置大致如上,接下来介绍下feign实现的原理。
先说结论,feign是通过动态代理的技术将一个interface变为Web Service客户端。那我们应该从哪里入手呢。在使用feign的时候,我们应该关注两个注解,一个就是我们上文所说的feignClient,但是仅仅只用这个注解feign是不会生效的,必须要在启动类上加上EnableFeignClients,feign才会自动扫描feignClient。所以我们的入口应该是 EnableFeignClients
EnableFeignClients 导入了FeignClientsRegistrar,这个注解真正的逻辑就在FeignClientsRegistrar中
这个类实现了三个接口,我们先关注 ImportBeanDefinitionRegistrar,这是spring动态注册bean的接口。所以spring在启动的时候会调用以下方法
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
将配置类纳入beandefinationMap管理 ,这一块更为详细的内容可以看 SpringIoc分析
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"));
}
}
扫描FeignClient注解,将interface纳入beanDefination
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"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}接下来,我们需要找到jdk代理的地方
我们在构建feign的地方发现如下方法
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory); return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
最终我们在SynchronousMethodHandler类中发现了真正拦截的代码
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;
}
}
}
真正执行的逻辑如下,这里也是feign最为关键的地方。这里我们主要关注下真正请求的那一行。如果想对feign做debug或者重写一些配置,参考这里会是一个很好的入口。
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);
} long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build();
} if (Response.class == metadata.returnType()) { if (response.body() == null) { return response;
} if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false; return response;
} // Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build();
} if (response.status() >= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null;
} else { return decode(response);
}
} else if (decode404 && response.status() == 404) { return decoder.decode(response, metadata.returnType());
} else { throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) { if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
} throw errorReading(request, response, e);
} finally { if (shouldClose) {
ensureClosed(response.body());
}
}
}这里的client是请求客户端,feign统一封装为LoadBalancerFeignClient
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)public class FeignRibbonClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) { return new LoadBalancerFeignClient(new Client.Default(null, null),
cachingFactory, clientFactory);
}
}
默认的Client 是HttpURLConnection,同时 feign也支持httpclient和okhhtp
@Configuration
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) protected static class HttpClientFeignConfiguration {
@Autowired(required = false) private HttpClient httpClient;
@Bean
@ConditionalOnMissingBean(Client.class) public Client feignClient() { if (this.httpClient != null) { return new ApacheHttpClient(this.httpClient);
} return new ApacheHttpClient();
}
}
@Configuration @ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnProperty(value = "feign.okhttp.enabled", matchIfMissing = true) protected static class OkHttpFeignConfiguration {
@Autowired(required = false) private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(Client.class) public Client feignClient() { if (this.okHttpClient != null) { return new OkHttpClient(this.okHttpClient);
} return new OkHttpClient();
}
}只要满足 配置条件,就可以将httpclient或okhhtp引入,这里举例说明怎么使用httpclient
在pom文件加上:
<dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-httpclient</artifactId> <version>RELEASE</version> </dependency>
在配置文件上加上feign.httpclient.enabled为true(默认为true,可不写)
最后,我们再看看feign是怎么使用ribbon的,上文我们说过feign统一将client封装为LoadBalancerFeignClient,fein的请求最终都会到以下代码
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 {
RequestSpecificRetryHandler handler = getRequestSpecificRetryHandler(request, requestConfig);
LoadBalancerCommand<T> command = LoadBalancerCommand.<T>builder()
.withLoadBalancerContext(this)
.withRetryHandler(handler)
.withLoadBalancerURI(request.getUri())
.build(); 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方法里,发现了如下代码
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
}
这里的selectServer 最终会调用 ILoadBalancer 选择一个server
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);关于这方面的具体内容,请参考 SpringCloud Ribbon的分析
以上,就是对feign的具体分析
原文出处:https://www.cnblogs.com/xmzJava/p/9612988.html
共同學習,寫下你的評論
評論加載中...
作者其他優質文章


