当前位置: 代码迷 >> 综合 >> springcloud openfeign原理-1-FeignClient初始化
  详细解决方案

springcloud openfeign原理-1-FeignClient初始化

热度:16   发布时间:2024-03-10 01:29:22.0

必备知识:ImportBeanDefinitionRegistrar方式实现Bean的动态装载

在ImportBeanDefinitionRegistrar方式实现Bean的动态装载 一文中,@EnableFeignClients 这个注解的作用是开启feignClient的扫描,注解定义如下:

package org.springframework.cloud.openfeign;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;@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.* @return the array of 'basePackages'.*/String[] basePackages() default {
    };/*** Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. * @return the array of 'basePackageClasses'.*/Class<?>[] basePackageClasses() default {
    };/*** A custom <code>@Configuration</code> for all feign clients* @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 {
    };
}

其中**@Import(FeignClientsRegistrar.class)** 导入了一个配置类FeignClientsRegistrar,这个类实现了ImportBeanDefinitionRegistrar接口,那么必然要重写接口中的registerBeanDefinitions 方法:

//AnnotationMetadata metadata:注解中的元数据
@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {
    registerDefaultConfiguration(metadata, registry);    // 注册默认的配置信息,registerFeignClients(metadata, registry);            // 注册不同包下所有的FeignClient}

这个方法里面又做了两件事:

  • registerDefaultConfiguration(metadata, registry)
  • registerFeignClients(metadata, registry)
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());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);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() {
    @Overrideprotected 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 interfaceAnnotatedBeanDefinition 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);}}}}

上面代码中的for循环里遍历了扫描到的每个包,找到加了@FeignClient注解的接口,然后迪用下面的方法去注册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 nullboolean 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);}

BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class) 这行代码,使用到了FeignClientFactoryBean, 这是一个工厂Bean (class FeignClientFactoryBean
implements FactoryBean
, InitializingBean, ApplicationContextAware )
FactoryBean有个特点,就是如果需要获得这个bean的真正的实例,只有一个方法,就是通过 getObject方法来实现


public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";@NullableT getObject() throws Exception;@NullableClass<?> getObjectType();default boolean isSingleton() {
    return true;}
}

因此,FeignClientFactoryBean中的getObject()方法一定会被调用到,它的实现如下,最终创建了一个复杂的bean进行返回:

@Overridepublic 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 context = this.applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.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));}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 unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {
    // not load balancing because we have a url, but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((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));}

本文示例代码:https://download.csdn.net/download/weixin_41300437/13068346