介绍
Feign是一个声明式Web Service客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
使用
首先在启动类上加上注解@EnableFeignClients,这个注解是加载feign的关键
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
然后需要定义interface,并指定调用的服务,在发起调用时feign就会真正调用到指定的服务接口,非常方便简洁。
@FeignClient(name = "feign-provider")
public interface HelloClient {
@GetMapping(value = "/test/feign/hello")
BizResponse test();
}
feign解决了什么问题
封装了Http调用流程,更适合面向接口化的变成习惯
feign注册源码
从@EnableFeignClients开始,会发现引入了FeignClientsRegistrar,通过名字可以直观的发现FeignClientsRegistrar会完成FeignClientde注册,那我们探究一下是如何注册的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
* @return list of default configurations
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath
* scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
进入FeignClientsRegistrar,会发现该类实现了ImportBeanDefinitionRegistrar接口,该接口的功能是可以动态的注册额外的bean到spring容器中,一般用于启动类或者配置类,通常和ResourceLoaderAware, EnvironmentAware搭配使用,下面我们具体看FeignClientsRegistrar是如何注册的
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
FeignClientsRegistrar() {
}
// 省略非必要代码
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册默认配置
registerDefaultConfiguration(metadata, registry);
// 注册feign cliients
registerFeignClients(metadata, registry);
}
}
我们先看一个registerDefaultConfiguration(metadata, registry)的代码registry是DefaultListableBeanFactory,熟悉spring源码的同学知道这是beanFactory的默认实现类,有一系列获取bean的方法
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 获取@EnableFeignClients注解的配置信息
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"));
}
}
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());
}
回过头来咱们看registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry)方法,也就是注册FeignClients的具体步骤
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
// 获取注解@EnableFeignClients的信息
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// A simple {@link TypeFilter} which matches classes with a given annotation,
checking inherited annotations as well.
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
// @EnableFeignClients的属性clients为空
if (clients == null || clients.length == 0) {
// 获取扫描classpath下component组件的扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// 扫描器增加要扫描的过滤器(扫描被@FeignClient注解修饰的类)
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
// 获取配置的扫描包的路径,如果没配置,默认为启动类的包路径
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
// 将扫描出来的@FeignClient注解修饰的类放入candidateComponents中
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
}
else {
for (Class<?> clazz : clients) {
// 如果clients不为空,直接将定义好的FeignClient放入candidateComponents
candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
}
}
// 遍历candidateComponents,完成FeignClient的注册
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"));
// 注册FeignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
接下来就是完成注册了
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> 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);
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();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
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的属性,最终调用registry.registerBeanDefinition的方法完成register
feign实例化源码
FeignClientFactoryBean实现了FactoryBean接口来完成对象创建,
在spring容器启动时会调用FeignClientFactoryBean的getObject()方法(只有在其他bean注入feign client时才会调用),看下FeignClientFactoryBean的getObject()方法做了哪些处理。
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
// 这个FeignContext在FeignAutoConfiguration配置中已经声明了,所以可以直接用applicationContext获取bean
FeignContext context = applicationContext.getBean(FeignContext.class);
//配置feign 的decoder、encoder、retryer、contract、RequestInterceptor等
//这些有默认配置,在FeignAutoConfiguration及FeignClientsConfiguration中有默认配置
Feign.Builder builder = feign(context);
// 如果feign client 没有指定url
if (!StringUtils.hasText(url)) {
if (!name.startsWith("http")) {
url = "http://" + name;
}
else {
url = name;
}
url += cleanPath();
// 使用负载均衡处理feign 请求
return (T) loadBalance(builder, context,
new HardCodedTarget<>(type, name, url));
}
// 如果制定了url则会直接调用url
if (StringUtils.hasText(url) && !url.startsWith("http")) {
url = "http://" + url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
if (client instanceof FeignBlockingLoadBalancerClient) {
// not load balancing because we have a url,
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url));
}
继续进入targeter.target(this, builder, context,
new HardCodedTarget<>(type, name, url))方法
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
FeignContext context, Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
: factory.getContextId();
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(name, context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(name, context, target, builder,
fallbackFactory);
}
return feign.target(target);
}
在这个方法中会判断是不是HystrixFeign.Builder,如果是的话会添加一个降级的逻辑,感兴趣的朋友可以了解一下Hystrix。Feign是默认关闭Hystrix的,如需要打开Hystrix,需要添加配置feign.hystrix.enabled=true。之后我们看一下 feign.target(target)
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public <T> T newInstance(Target<T> target) {
// 这个apply方法就是ReflectiveFeign中的apply方法,返回了每个方法的调用包装类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)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 返回了ReflectiveFeign.FeignInvocationHandler对象,这个对象的invoke方法其实就是调用了SynchronousMethodHandler.invoke方法
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;
}
在这里会为每个feign client里的方法生成一个SynchronousMethodHandler对象,然后通过动态代理实现对请求拦截,并发起调用
feign调用
那feign client多个方法我们怎么知道调用哪个SynchronousMethodHandler呢。这里很巧妙的使用了一个Map结构,feign client的Method作为key,对应的SynchronousMethodHandler作为value,当调用Method时,就可以方便的找到它的代理方法执行了
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);
}
@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);
}
}
默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,可以替换为Apache 的HttpClient或者OkHttp提高性能
总结
以上就介绍完了Feign的主要核心源码,代码量不多,但设计非常精妙。
网友评论