@EnableFeignClients
/*** Scans for interfaces that declare they are feign clients (via* {@link org.springframework.cloud.openfeign.FeignClient} <code>@FeignClient</code>).* Configures component scanning directives for use with* {@link org.springframework.context.annotation.Configuration}* <code>@Configuration</code> classes.* 扫描所有@FeignClient标注的接口* 扫描所有@Configuration标注的接口** @author Spencer Gibb* @author Dave Syer* @since 1.0*/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented//引入了FeignClientsRegistrar@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** 所有 FeignClient接口的默认配置*/Class<?>[] defaultConfiguration() default {};/*** List of classes annotated with @FeignClient. If not empty, disables classpath* scanning.* @return list of FeignClient classes** 若此项不为空,则不会去扫描所有的FeignClient*/Class<?>[] clients() default {};}
FeignClientsRegistrar
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry);}
registerDefaultConfiguration方法
registerFeignClients方法
- 若在@EnableFeignClients中指定了clients属性,只会注册指定的
- @FeignCient注解只允许标注在接口上
每个@FeignCient接口都会在这个方法里做如下动作:
- 注册client的配置类为Bean
-
registerFeignClient
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();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.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);}
为每个
FeignClient接口注册一个FeignClientFactoryBean的BeanDefinition
beanName默认为className
-
注册FeignClientBean
在分析
@EnableFeginClients方法后,发现其为每个Feign接口提供了一个FeignClientFactoryBean,让我们来看看这个FactoryBean```java class FeignClientFactoryBeanimplements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
/*
- WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
lifecycle race condition. */
private Class<?> type;
private String name;
private String url;
private String contextId;
private String path;
private boolean decode404;
private ApplicationContext applicationContext;
private Class<?> fallback = void.class;
private Class<?> fallbackFactory = void.class;
@Override public void afterPropertiesSet() throws Exception { Assert.hasText(this.contextId, “Context id must be set”); Assert.hasText(this.name, “Name must be set”); }
//通过context 构建FeignBuilderprotected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);Logger logger = loggerFactory.create(this.type);// @formatter:offFeign.Builder builder =//获取Feign.Builder的beanget(context, Feign.Builder.class)// required values.logger(logger)// 获取Encoder.encoder(get(context, Encoder.class)).decoder(get(context, Decoder.class)).contract(get(context, Contract.class));// @formatter:onconfigureFeign(context, builder);return builder;}protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class);if (properties != null) {is_default_to_properties,则先Configuration、后propetiesif (properties.isDefaultToProperties()) {//自定义的ConfigurationconfigureUsingConfiguration(context, builder);//默认configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);//自己的configureUsingProperties(properties.getConfig().get(this.contextId),builder);}else {//默认的configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);//自己的configureUsingProperties(properties.getConfig().get(this.contextId),builder);//configurationconfigureUsingConfiguration(context, builder);}}else {//ConfigurationconfigureUsingConfiguration(context, builder);}}private <T> T getOrInstantiate(Class<T> tClass) {try {return this.applicationContext.getBean(tClass);}catch (NoSuchBeanDefinitionException e) {return BeanUtils.instantiateClass(tClass);}}protected <T> T get(FeignContext context, Class<T> type) {T instance = context.getInstance(this.contextId, type);if (instance == null) {throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);}return instance;}protected <T> T getOptional(FeignContext context, Class<T> type) {return context.getInstance(this.contextId, 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);}throw new IllegalStateException("No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");}@Overridepublic Object getObject() throws Exception {//调用getTarget方法return getTarget();}/*** @param <T> the target type of the Feign client* T是Feign接口的类* @return a {@link Feign} client created with the specified data and the context* information*/<T> T getTarget() {//获取FeignContextFeignContext context = this.applicationContext.getBean(FeignContext.class);//通过context构建 FeignFeign.Builder builder = feign(context);//若不包含 url属性,则自动生成if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {this.url = "http://" + this.name;}else {this.url = this.name;}this.url += cleanPath();//loadBalance(); 可能是个普通DefaultTargeter、或hystrixreturn (T) loadBalance(builder, context,new HardCodedTarget<>(this.type, this.name, this.url));}//若有url但是 不是 http开头则加入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) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrap//当ribbonclient = ((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// 当 Spring Cloud LoadBalancer 在的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));}private String cleanPath() {String path = this.path.trim();if (StringUtils.hasLength(path)) {if (!path.startsWith("/")) {path = "/" + path;}if (path.endsWith("/")) {path = path.substring(0, path.length() - 1);}}return path;}
}
```java@ConfigurationProperties("feign.client")public class FeignClientProperties {private boolean defaultToProperties = true;private String defaultConfig = "default";/*** client1 --> 配置* client2 --> 配置*/private Map<String, FeignClientConfiguration> config = new HashMap<>();public static class FeignClientConfiguration {private Logger.Level loggerLevel;private Integer connectTimeout;private Integer readTimeout;private Class<Retryer> retryer;private Class<ErrorDecoder> errorDecoder;private List<Class<RequestInterceptor>> requestInterceptors;private Boolean decode404;private Class<Decoder> decoder;private Class<Encoder> encoder;private Class<Contract> contract;private ExceptionPropagationPolicy exceptionPropagationPolicy;}}
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 existingfor (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()));}if (Objects.nonNull(config.getExceptionPropagationPolicy())) {builder.exceptionPropagationPolicy(config.getExceptionPropagationPolicy());}}
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.contextId, RequestInterceptor.class);if (requestInterceptors != null) {builder.requestInterceptors(requestInterceptors.values());}QueryMapEncoder queryMapEncoder = getOptional(context, QueryMapEncoder.class);if (queryMapEncoder != null) {builder.queryMapEncoder(queryMapEncoder);}if (this.decode404) {builder.decode404();}ExceptionPropagationPolicy exceptionPropagationPolicy = getOptional(context,ExceptionPropagationPolicy.class);if (exceptionPropagationPolicy != null) {builder.exceptionPropagationPolicy(exceptionPropagationPolicy);}}
FeignContext
这里存放的是各个feignclient的配置项,是通过自动配置类注册成bean的
@Configuration(proxyBeanMethods = false)@ConditionalOnClass(Feign.class)@EnableConfigurationProperties({ FeignClientProperties.class,FeignHttpClientProperties.class })@Import(DefaultGzipDecoderConfiguration.class)public class FeignAutoConfiguration {@Autowired(required = false)private List<FeignClientSpecification> configurations = new ArrayList<>();@Beanpublic HasFeatures feignFeature() {return HasFeatures.namedFeature("Feign", Feign.class);}@Beanpublic FeignContext feignContext() {FeignContext context = new FeignContext();//所有的配置context.setConfigurations(this.configurations);return context;}....}
代理对象创建
@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> target) {return feign.target(target);}public <T> T target(Target<T> target) {return build().newInstance(target);}
public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}
@Overridepublic <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;}
