docs/Spring全家桶/SpringCloud源码分析/SpringCloudOpenFeign源码分析.md
| ľľ Դ |ͷ
ѧϰĿ
˵ˣOpenFeignĵĿÿͻԶ̵ùвҪʲôIJֻҪõһȻøöķͺˣʣµIJOpenFeignȥɣʣһЩʲôأ
OKƵOpenFeignӦɵҪĿ꣬ôġ ֮ǰнһʲôֻҪǼspringspringbootĻһͨspringspringbootȥbeanĴģͨõ֮ȥöĺķOpenFeignڼspringbootʱҲӦ
2 Դ֤
2.1 EnableFeignClients Ǵע룬עFeignClientĽ̡
@EnableFeignClients(basePackages = "com.example.client")
ע£õһ@Importע⣬֪ImportһģȥһFeignClientsRegistrarĶ塣
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}
FeignClientsRegistrarʵ ImportBeanDefinitionRegistrarһ̬עbeanĽӿڣSpring BootʱȥеregisterBeanDefinitionsʵֶ̬BeanװءregisterBeanDefinitionsspringʱִinvokeBeanFactoryPostProcessorsȻӦнעᣬImportSelector
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
}
2.1.1 ImportBeanDefinitionRegistrar ʾһ ImportBeanDefinitionRegistrará
public class HelloService {
}
public class FeignImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(HelloService.class.getName());
registry.registerBeanDefinition("helloService",beanDefinition);
}
}
һע
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignImportBeanDefinitionRegistrar.class})
public @interface EnableFeignTest {
}
@EnableFeignClients(basePackages = "com.example.clients")
@EnableFeignTest
@SpringBootApplication
public class OpenfeignUserServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(OpenfeignUserServiceApplication.class, args);
System.out.println(context.getBean(HelloService.class));
}
}
2.1.2 FeignClientsRegistrar
registerDefaultConfiguration ڲ SpringBoot ϼǷ@EnableFeignClients, иעĻ Feign صһЩע
registerFeignClients ڲ classpath У ɨ @FeignClient ε࣬ ݽΪ BeanDefinition , ͨ Spring еBeanDefinitionReaderUtils.resgisterBeanDefinition FeignClientBeanDeifinition ӵ spring .
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//ע@EnableFeignClientsжdefaultConfigurationµ࣬װFeignClientSpecificationעᵽSpring
//@FeignClientһԣconfigurationDZʾFeignClientԶ࣬ҲͨregisterClientConfigurationעFeignClientSpecification
//ԣȫ@EnableFeignClientsõΪãڸ@FeignClientõľԶ
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
2.2 registerDefaultConfiguration
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// ȡmetadataйEnableFeignClientsֵֵԡ
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
//defaultConfiguration ,ûʹĬϵconfiguration
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
} else {
name = "default." + metadata.getClassName();
}
//ע
this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
//ʹBeanDefinitionBuilderBeanDefinition,ע
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
BeanDefinitionRegistryspringڶ̬עBeanDefinitionϢĽӿڣregisterBeanDefinitionԽBeanDefinitionעᵽSpringУnameԾעBeanDefinitionƣעһFeignClientSpecificationĶ
FeignClientSpecificationʵ NamedContextFactory.SpecificationӿڣFeignʵҪһķУԶõʵSpringCloudʹNamedContextFactoryһЩеApplicationContextöӦSpecificationЩдʵ
NamedContextFactory3ܣ
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
FeignContextﹹأ
ü pring-cloud-openfeign-core-2.2.3.RELEASE.jar!\META-INF\spring.factories2.2.1 FeignAutoConfiguration
ĬϵFeignClientsConfigurationΪݸ캯
FeignContextʱὫ֮ǰFeignClientSpecificationͨsetConfigurationsøcontextġ
2.2.2 createContext
org.springframework.cloud.context.named.NamedContextFactory#createContext
FeignContextĸcreateContextὫ AnnotationConfigApplicationContextʵʵΪǰĵģڹfeignIJͬʵڵFeignClientFactoryBeangetObjectʱácreateContextĻὲ⣩
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//ȡnameӦconfiguration,оעᵽcontext
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
//עdefaultConfiguration,Ҳ FeignClientsRegistrarregisterDefaultConfigurationעConfiguration
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
//עPropertyPlaceholderAutoConfiguration
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
//EnvironmentpropertySourcesԴ
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
NamedContextFactoryʵDisposableBeanԵʵʱ
@Override
public void destroy() {
Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
for (AnnotationConfigApplicationContext context : values) {
// This can fail, but it never throws an exception (you see stack traces
// logged as WARN).
context.close();
}
this.contexts.clear();
}
ܽNamedContextFactoryᴴ AnnotationConfigApplicationContextʵnameΪΨһʶȻÿAnnotationConfigApplicationContextʵעᲿ࣬ӶԸһϵеĻɵʵͿԻnameһϵеʵΪͬFeignClientͬʵ
2.3 registerFeignClients Ҫɨ·е@FeignClientע⣬Ȼж̬Beanע롣ջ registerFeignClient
public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
//ʡԴ...
registerFeignClient(registry, annotationMetadata, attributes);
}
УȥװBeanDefinitionҲBeanĶ壬ȻעᵽSpring IOC
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
//ʡԴ...
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition,className,new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
ǹעһ£BeanDefinitionBuilderһBeanDefinitionģͨgenericBeanDefinition ģҴһFeignClientFactoryBeanࡣ
ǿԷ֣FeignClient̬עһFactoryBean
Spring Cloud FengnClientʵSpringĴɴ࣬ŻеFeignClientBeanDefinitionΪFeignClientFactoryBeanͣFeignClientFactoryBean̳FactoryBeanһBean
SpringУFactoryBeanһBeanBean
Bean һ Bean, Bean ˵ Ǹ֪ Bean ͨ Bean ǹ Bean, ֻǰĻȡ Bean ʽȥã bean صʵǹBean ִй Bean getObject صʾҲʵBeanʱȥgetObject
public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
builder.beanDefinition.setBeanClass(beanClass);
return builder;
}
˵FeignClientעӿڣͨ FeignClientFactoryBean.getObject()һ
2.3.1 FeignClientFactoryBean.getObject getObjectõgetTargetapplicationContextȡFeignContextFeignContext̳NamedContextFactoryͳһάfeignиfeignͻġ
ţfeign.builderڹʱFeignContextȡõEncoderDecoderȸϢFeignContextƪѾᵽΪÿFeignͻ˷һǵĸspring
Feign.Builder֮жǷҪLoadBalanceҪͨLoadBalanceķáʵյõTarget.target()
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//ʵFeignĶFeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);//Builder
if (!StringUtils.hasText(this.url)) {//urlΪգ߸ؾ⣬иؾܵĴ
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
return (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name,this.url));
}
//ָurlĬϵĴ
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
//FeignContextgetInstanceȡClient
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<>(this.type, this.name,url));
}
2.3.2 loadBalance ɾ߱ؾfeignͻˣΪfeignͻ˹ؾͻ.
Client client = (Client)this.getOptional(context, Client.class); лȡһClientĬLoadBalancerFeignClient
FeignRibbonClientAutoConfigurationԶװУͨImportʵֵ
@Import({ HttpClientFeignLoadBalancedConfiguration.class,OkHttpFeignLoadBalancedConfiguration.class,DefaultFeignLoadBalancedConfiguration.class })
protected <T> T loadBalance(Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = (Client)this.getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = (Targeter)this.get(context, Targeter.class);
return targeter.target(this, builder, context, target);
} else {
throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
}
2.3.3 DefaultTarget.target
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {
return feign.target(target);
}
2.3.4 ReflectiveFeign.newInstance һ̬ķɶ̬֮ǰContractЭ飨ЭӿעϢڲMethodHandlerĴʽ
ʵֵĴпԿϤProxy.newProxyInstanceࡣҪÿĽӿڷضĴʵ֣һMethodHandlerĸǶӦInvocationHandler
public <T> T newInstance(Target<T> target) {
//ݽӿContractЭʽӿϵķע⣬תڲMethodHandlerʽ
Map<String, MethodHandler> nameToHandler = this.[targetToHandlersByName.apply(target)];
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
Method[] var5 = target.type().getMethods();
int var6 = var5.length;
for(int var7 = 0; var7 < var6; ++var7) {
Method method = var5[var7];
if (method.getDeclaringClass() != Object.class) {
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 = this.factory.create(target, methodToHandler);
// Proxy.newProxyInstance Ϊӿഴ̬ʵ֣еתInvocationHandler
T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
Iterator var12 = defaultMethodHandlers.iterator();
while(var12.hasNext()) {
DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
2.4 ӿڶIJ FeignClientӿڵӦݡ
2.4.1 targetToHandlersByName.apply(target) ContractЭӿעϢڲ֣
targetToHandlersByName.apply(target);ӿڷϵע⣬ӶȵضϢȻһSynchronousMethodHandler ȻҪάһ<methodMethodHandler>mapInvocationHandlerʵFeignInvocationHandlerС
public Map<String, MethodHandler> apply(Target target) {
List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());
Map<String, MethodHandler> result = new LinkedHashMap<String,MethodHandler>();
for (MethodMetadata md : metadata) {
BuildTemplateByResolvingArgs buildTemplate;
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null)
{
buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder,target);
} else if (md.bodyIndex() != null) {
buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder,queryMapEncoder, target);
} else {
buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder,target);
}
if (md.isIgnored()) {
result.put(md.configKey(), args -> {
throw new IllegalStateException(md.configKey() + " is not a method handled by feign");
});
} else {
result.put(md.configKey(),
factory.create(target, md, buildTemplate, options, decoder,errorDecoder));
}
}
return result;
}
2.4.2 SpringMvcContract ǰSpring Cloud УΪ˽ѧϰɱSpring MVCIJע Ҳ˵ дͻӿںд˴һͻ˺ͷ˿ͨSDKķʽԼͻֻҪ˷SDK APIͿʹӿڵı뷽ʽԽӷ
̳Contract.BaseContractʵResourceLoaderAwareӿڣ
þǶRequestMappingRequestParamRequestHeaderעнġ
2.5 OpenFeignù ǰķУ֪OpenFeignշصһ# ReflectiveFeign.FeignInvocationHandlerĶ
ôͻ˷ʱ뵽 FeignInvocationHandler.invokeУҶ֪һ̬ʵ֡
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!"equals".equals(method.getName())) {
if ("hashCode".equals(method.getName())) {
return this.hashCode();
} else {
return "toString".equals(method.getName()) ? this.toString() :
((MethodHandler)this.dispatch.get(method)).invoke(args);
}
} else {
try {
Object otherHandler = args.length > 0 && args[0] != null ?
Proxy.getInvocationHandler(args[0]) : null;
return this.equals(otherHandler);
} catch (IllegalArgumentException var5) {
return false;
}
}
}
ţinvokeУ this.dispatch.get(method)).invoke(args) this.dispatch.get(method) ᷵һSynchronousMethodHandler,ش
ݲɵRequestTemplateHttpģ棬¡
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = this.buildTemplateFromArgs.create(argv);
Options options = this.findOptions(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(template, options);
} catch (RetryableException var9) {
RetryableException e = var9;
try {
retryer.continueOrPropagate(e);
} catch (RetryableException var8) {
Throwable cause = var8.getCause();
if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP
&& cause != null) {
throw cause;
}
throw var8;
}
if (this.logLevel != Level.NONE) {
this.logger.logRetry(this.metadata.configKey(), this.logLevel);
}
}
}
}
2.5.1 executeAndDecode Ĵ룬ѾrestTemplateƴװɣĴһ executeAndDecode() ÷ͨRequestTemplateRequestȻHttp ClientȡresponseȡӦϢ
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
//תΪHttp
Request request = this.targetRequest(template);
if (this.logLevel != Level.NONE) {
this.logger.logRequest(this.metadata.configKey(), this.logLevel,request);
}
long start = System.nanoTime();
Response response;
try {
//Զͨ
response = this.client.execute(request, options);
//ȡؽ
response = response.toBuilder().request(request).requestTemplate(template).build();
} catch (IOException var16) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel,var16, this.elapsedTime(start));
}
throw FeignException.errorExecuting(request, var16);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
boolean shouldClose = true;
Response var10;
try {
if (this.logLevel != Level.NONE) {
response = this.logger.logAndRebufferResponse(this.metadata.configKey(), this.logLevel,response, elapsedTime);
}
if (Response.class != this.metadata.returnType()) {
Object result;
Object var21;
if (response.status() >= 200 && response.status() < 300) {
if (Void.TYPE == this.metadata.returnType()) {
var10 = null;
return var10;
}
result = this.decode(response);
shouldClose = this.closeAfterDecode;
var21 = result;
return var21;
}
if (this.decode404 && response.status() == 404 && Void.TYPE != this.metadata.returnType()) {
result = this.decode(response);
shouldClose = this.closeAfterDecode;
var21 = result;
return var21;
}
throw this.errorDecoder.decode(this.metadata.configKey(), response);
}
if (response.body() == null) {
var10 = response;
return var10;
}
if (response.body().length() != null && (long)response.body().length() <= 8192L) {
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
Response var11 = response.toBuilder().body(bodyData).build();
return var11;
}
shouldClose = false;
var10 = response;
} catch (IOException var17) {
if (this.logLevel != Level.NONE) {
this.logger.logIOException(this.metadata.configKey(), this.logLevel,var17, elapsedTime);
}
throw FeignException.errorReading(request, response, var17);
} finally {
if (shouldClose) {
Util.ensureClosed(response.body());
}
}
return var10;
}
2.5.2 Client.execute ĬϲJDK HttpURLConnection Զ̵á
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
int status = connection.getResponseCode();
String reason = connection.getResponseMessage();
if (status < 0) {
throw new IOException(format("Invalid status(%s) executing %s %s",status,connection.getRequestMethod(),connection.getURL()));
}
Map<String, Collection<String>> headers = new LinkedHashMap<>();
for (Map.Entry<String, List<String>> field :
connection.getHeaderFields().entrySet()) {
// response message
if (field.getKey() != null) {
headers.put(field.getKey(), field.getValue());
}
}
Integer length = connection.getContentLength();
if (length == -1) {
length = null;
}
InputStream stream;
if (status >= 400) {
stream = connection.getErrorStream();
} else {
stream = connection.getInputStream();
}
return Response.builder()
.status(status)
.reason(reason)
.headers(headers)
.request(request)
.body(stream, length)
.build();
}
https://lijunyi.xyz/docs/SpringCloud/SpringCloud.html#_2-2-x-%E5%88%86%E6%94%AF https://mp.weixin.qq.com/s/2jeovmj77O9Ux96v3A0NtA https://juejin.cn/post/6931922457741770760 https://github.com/D2C-Cai/herring http://c.biancheng.net/springcloud https://github.com/macrozheng/springcloud-learning