openFeign是SpringCloud组件,主要用于微服务中调用其他服务时可以让程序员简单的面向接口编程,避免重复代码中使用RestTemplate
请求的困扰。
引入maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
引导类上增加自动装配注解
@SpringBootApplication
@RestController
@EnableFeignClients// 开启OpenFeign
@RibbonClients
public class OrderWebApplication {
public static void main(String[] args) {
SpringApplication.run(OrderWebApplication.class, args);
}
}
编写接口
@FeignClient(name = "order-service")
public interface OrderFeignService {
@GetMapping("/orders")
Object getOrders();
}
这样就可以直接使用OrderFeignService
这个接口了,在应用启动过程中会替OrderFeignService创建代理对象。
@EnableFeignclients
作为一个负责任的程序员,我们看看其实现原理,就从@EnableFeignclients
注解开始
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// 通过Import注解实现
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
// 默认配置
Class<?>[] defaultConfiguration() default {};
// @FeignClient数组
Class<?>[] clients() default {};
}
我们看到了一个非常非常熟悉的注解@Import
,这是Spring中非常基础的一个引入配置类的方式,FeignClientsRegistrar
就是该注解语义的实现。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private Environment environment;
// 回调方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
// 默认配置解析
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 解析EnableFeignClients#defaultConfiguration
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
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());
// 根据@FeignClient注解过滤
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);
// 解析要扫描的包,也就是EnableFeignClients上配置的,及该类的包路径
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");
// 解析@FeignClient注解
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
// 注册定制配置
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册接口Bean
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 注册定制配置FeignClientSpecification
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
// 注册客户端接口
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 注册为工厂Bean FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
// BeanDefinition设置
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 });
// 注册到Web容器
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
}
FeignClientsRegistrar主要完成@EnableFeignClients
注解的解析,扫描出指定包下面所有的标注了@Feignclient
注解的接口,将接口注册到容器中。
可以看出和Ribbon的套路如出一辙,一模一样。我们知道这种方式的还有Mybatis中Mapper接口的注册也是这样。可以看出外部集成Spring的套路都差不多,一通百通。
FeignClientFactoryBean
继续看看其注册的FeignClientFactoryBean
实现,这是一个典型的工厂Bean模式,其getObject()
方法就是获取具体的代理对象,getObjectType()
就是返回接口类型。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
// @FeignClient中的配置信息
private Class<?> type;
private String name;
private String url;
private String path;
private boolean decode404;
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
// web上下文
private ApplicationContext applicationContext;
protected Feign.Builder feign(FeignContext context) {
// 日志工厂
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// 构建Feign对象
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
// get(context, xxx.class)是比填配置,不能为null,会从上下文中去查找bean
.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()) {
// 优先级:代码中配置的@FeignClient(configurations=XX.class) < 配置文件中defaut.开头 < 配置文件中 order-service开头
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);
}
}
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) {
builder.logLevel(level);
}
Retryer retryer = getOptional(context, Retryer.class);
if (retryer != null) {
builder.retryer(retryer);
}
ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
if (errorDecoder != null) {
builder.errorDecoder(errorDecoder);
}
Request.Options options = getOptional(context, Request.Options.class);
if (options != null) {
builder.options(options);
}
Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
this.name, RequestInterceptor.class);
if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values());
}
if (decode404) {
builder.decode404();
}
}
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
if (config == null) {
return;
}
if (config.getLoggerLevel() != null) {
builder.logLevel(config.getLoggerLevel());
}
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
}
if (config.getRetryer() != null) {
Retryer retryer = getOrInstantiate(config.getRetryer());
builder.retryer(retryer);
}
if (config.getErrorDecoder() != null) {
ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
builder.errorDecoder(errorDecoder);
}
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
// this will add request interceptor to builder, not replace existing
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean);
builder.requestInterceptor(interceptor);
}
}
if (config.getDecode404() != null) {
if (config.getDecode404()) {
builder.decode404();
}
}
if (Objects.nonNull(config.getEncoder())) {
builder.encoder(getOrInstantiate(config.getEncoder()));
}
if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder()));
}
if (Objects.nonNull(config.getContract())) {
builder.contract(getOrInstantiate(config.getContract()));
}
}
private <T> T getOrInstantiate(Class<T> tClass) {
try {
return applicationContext.getBean(tClass);
} catch (NoSuchBeanDefinitionException e) {
return BeanUtils.instantiateClass(tClass);
}
}
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.name, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + this.name);
}
return instance;
}
protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.name, type);
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> 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);
}
// 强依赖ribbon
throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
@Override
public Object getObject() throws Exception {
return getTarget();
}
// 获取代理对象
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
// 配置feign
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));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
}
这个工厂Bean的主要作用就是创建标注了@FeignClient
的接口的代理对象,这里会解析Feign的相关配置,
从上面的代码中我们发现Feign客户端有些是必选配置:
get(context, xxx.class) 是比填配置,不能为null,会从上下文中去查找bean getOptional(context, XXX.Level.class); 是可选配置,有默认值
配置各个Client时有几种方
- 配置类
```java
@FeignClient(name = “order-service”, decode404 = true,
public interface OrderFeignService { @GetMapping(“/orders”) Object getOrders(); }// 引用配置类 configuration = OrderServiceConfig.class)
// 配置类 public class OrderServiceConfig { @Bean public Logger.Level logLevel() { return Logger.Level.BASIC; } @Bean public Request.Options options() { return new Request.Options(6000, 6000); } }
2. 配置文件中配置
```properties
feign.client.default-to-properties=true
feign.client.config.default.logger-level=BASIC
feign.client.config.order-service.logger-level=FULL
feign.client.config.order-service.read-timeout=1500
feign.client.config.order-service.decode404=false
对于order-service
,当feign.client.default-to-properties
为true时,优先级从高到低为
- feign.client.config.order-service.xxx
- feign.client.config.default.xxx
- 配置类中对应配置
同时,我们看到有集成Ribbon负载均衡的代码
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> 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);
}
// 强依赖ribbon
throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
Client仅有两个实现
最终执行到
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public <T> T newInstance(Target<T> target) {
// 封装解析接口方法为 SynchronousMethodHandler
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)) {
// 接口中default 方法
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;
}
这里的接口方法有两种,一种是默认方法,一种是普通方法,解析完方法后就是封装为一个InvocationHandler实例,在创建代理对象时使用的回调对象。
继续看看InvocationHandler实现FeignInvocationHandler
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;// 目标接口
private final Map<Method, MethodHandler> dispatch;// 方法封装的信息
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
// Proxy调用时执行
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
// ...
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// 接口方法调用
return dispatch.get(method).invoke(args);
}
}
因为接口方法封装的是SynchronousMethodHandler,继续看SynchronousMethodHandler中的invoke方法。
final class SynchronousMethodHandler implements MethodHandler {
private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;
private final MethodMetadata metadata;
private final Target<?> target;
private final Client client;
private final Retryer retryer;
private final List<RequestInterceptor> requestInterceptors;
private final Logger logger;
private final Logger.Level logLevel;
private final RequestTemplate.Factory buildTemplateFromArgs;
private final Options options;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
private final boolean decode404;
private final boolean closeAfterDecode;
// 省略构造方法及get/set
@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);
Response response;
long start = System.nanoTime();
try {
// 这里的client就是LoadBalancerFeignClient集成了ribbon
response = client.execute(request, options);
//
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 (Response.class == metadata.returnType()) {
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// 解析响应内容
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 {
// 反序列化
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
Object result = decode(response);
shouldClose = closeAfterDecode;
return result;
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
// 执行拦截器
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(new RequestTemplate(template));
}
Object decode(Response response) throws Throwable {
try {
return decoder.decode(response, metadata.returnType());
} catch (Exception e) {
throw e;
}
}
}
response = client.execute(request, options);
就是链接Ribbon的客户端,打通了feign和ribbon。
@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);
// 根据服务id获取负载均衡器,然后执行请求,进入ribbon逻辑
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
这里通过LoadBalancerFeignClient就链接了OpenFeign
和Ribbon
,从而实现了客户端的面向接口编程及客户端负载均衡。