目录
前言
Feign作为调用http请求的工具,在java后端编程中有广泛的应用,能够简化代码书写,但同时也隐藏了大量的处理细节,作为开发者,应当适当了解其源码,解决开发中的问题。
一、EnableFeignClients
@EnableFeignClients注解的主要作用是引入FeignClientsRegistrar类。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
二、FeignClientsRegistrar
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,能够手动往容器中导入bean定义,其主要逻辑位于registerFeignClients方法中。
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
registerFeignClients方法中,对文件夹中的类进行扫描,对于有@FeignClient注解的类,作为FeignClient注册到容器中。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
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);
}
}
}
}
三、FeignClientFactoryBean
容器中注入的Bean定义为FeignClientFactoryBean,该类实现了FactoryBean接口,所以getBean方法最终调用的是getObject方法,实际就是getTargrt方法。
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
@Override
public Object getObject() throws Exception {
return getTarget();
}
}
getTargrt方法中,为所有的FeignClient接口创建代理对象。
<T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
创建代调用到ReflectiveFeign类中的newInstance方法,在该方法中对被代理接口的所有方法创建DefaultMethodHandler对象。
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;
}
四、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);
}
@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);
}
代理对象执行方法的时候执行的是方法对应代理对象的invoke方法,基本就是构造了一个request请求,此处不再过多陈述。
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;
}
}
}
五、动态URI参数
在有些情况下,我们希望能够动态更改feign请求的地址,而不是固定写死,此时,我们只需要在方法中添加一个URI类型的参数,并且在使用时传入地址,就可以使用,接下来主要从源码角度分析其中的原因。
ReflectiveFeign类中的apply方法会调用parseAndValidateMetadata方法,该方法会对方法的参数进行解析,保存到MethodMetadata中,并对参数类型为URI的参数进行了特殊处理。
protected MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
MethodMetadata data = new MethodMetadata();
Class<?>[] parameterTypes = method.getParameterTypes();
Type[] genericParameterTypes = method.getGenericParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int count = parameterAnnotations.length;
for (int i = 0; i < count; i++) {
boolean isHttpAnnotation = false;
if (parameterAnnotations[i] != null) {
isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);
}
//解析设置uri
if (parameterTypes[i] == URI.class) {
data.urlIndex(i);
} else if (!isHttpAnnotation) {
checkState(data.formParams().isEmpty(),
"Body parameters cannot be used with form parameters.");
checkState(data.bodyIndex() == null, "Method has too many Body parameters: %s", method);
data.bodyIndex(i);
data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));
}
}
return data;
}
SynchronousMethodHandler中的create方法中,需要创建RequestTemplateRest,在create方法中,实际会调用到ReflectiveFeign的create方法,基于上文中的MethodMetadata,判断当前method参数中有没有URI类型的,如果有直接将参数的值设置为target。
@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
//判断有没有URI参数,有这个参数直接设为target,用于动态设置URI
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
return template;
}
target方法中会对uri参数的值进行校验,uri参数的值为空或者不以“http”开头的时候,会报参数异常。
public RequestTemplate target(String target) {
/* target can be empty */
if (Util.isBlank(target)) {
return this;
}
/* verify that the target contains the scheme, host and port */
if (!UriUtils.isAbsolute(target)) {
throw new IllegalArgumentException("target values must be absolute.");
}
if (target.endsWith("/")) {
target = target.substring(0, target.length() - 1);
}
return this;
}
public static boolean isAbsolute(String uri) {
return uri != null && !uri.isEmpty() && uri.startsWith("http");
}
如果传入的url不以“http”开头,不使用RequestTemplate传入的uri,使用Target自带的url,如果传入的url满足要求,直接使用传入的url。
@Override
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
input.target(url());
}
return input.request();
}
总结
本文对Feign的动态代理创建和调用流程进行源码分析,重点分析了URI参数能够实现动态路由的原因,如有不当之处,欢迎大家交流。