java版spring cloud微服務架構b2b2c電子商務平臺-Feign使用及源碼深度解析

SpringCloud Feign基於Netflix Feign實現,整合SpringCloud Ribbon和SpringCloud Hystrix

我們在使用微服務框架的時候,一般都會在項目中同時使用Ribbon和Hystrix,所以可以考慮直接使用Feign來整合

1.Feign的使用

我們現在需要創建一個服務消費者應用,消費服務提供者的服務

1)引入maven依賴

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

2)創建接口FeignService,提供消費服務

@FeignClient(name="part-1-sms-interface", fallback=FeginFallbackService.class)
public interface FeignService {
 
	@RequestMapping("/sms/test")
	String test();
}
 
// 服務降級類
@Component
public class FeginFallbackService {
 
	public String test(){
		return "fallback error";
	}
}

注意:

  • @FeignClient註解中的name爲服務提供者所在應用的spring.application.name,fallback所對應的類爲服務調用異常時服務降級的類

  • @RequestMapping("/sms/test")爲服務提供者應用中的方法路徑

  • @FeignClient還有一個關鍵的configuration參數,可以讓用戶自定義配置bean

  • 註解默認的配置bean所在類爲FeignClientsConfiguration,用戶可參考其中的bean實現來自定義

    3)創建Controller

@RestController
@RequestMapping("/feign")
public class FeignController {
 
	@Autowired
	private FeignService feignService;
	
	@GetMapping(value="/test")
	public String test(){
		return feignService.test();
	}
}

4)測試驗證
調用/feign/test方法,可以看到其調用了服務提供者part-1-sms-interface提供的/sms/test方法,驗證成功

2.寫在源碼分析之前
經過上面的關於Feign的使用分析,可以看到使用的時候還是非常簡單的,只需要兩個簡單的註解就實現了Ribbon+Hystrix的功能。
那具體是如何實現的呢?
關鍵的一步就是如何把對接口的請求映射爲對真正服務的請求(也就是一個HTTP請求),同時還要加上Hystrix的功能。
映射爲HTTP請求、Hystrix的功能都是每個服務共同的需求,所以肯定是被抽象出來的。而且我們沒有對接口有任何具體實現,那麼應該是用了反射的功能了,實現具體接口的實現類,並註冊到Spring容器中,這樣才能在Controller層中使用@Autowired

3.分析註解@EnableFeignClients

通過上面的使用過程分析,@EnableFeignClients和@FeignClient兩個註解就實現了Feign的功能,那我們重點分析下@EnableFeignClients註解

1)@EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)// 重點在這裏,注入FeignClientsRegistrar類
public @interface EnableFeignClients {}

2)FeignClientsRegistrar.java分析

通過其類結構可知,其實現了ImportBeanDefinitionRegistrar接口,那麼在registerBeanDefinitions()中就會註冊一些bean到Spring中

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
		ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware{
        
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
        // 1.針對那些在@EnableFeignClients中添加了defaultConfiguration屬性的進行操作
        // 將這些類定義的bean添加到容器中
		registerDefaultConfiguration(metadata, registry);
        
        // 2.註冊那些添加了@FeignClient的類或接口
        // 重點就在這裏
		registerFeignClients(metadata, registry);
	}
}

3)registerFeignClients(metadata, registry)方法分析

public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
 
    // 1.以下代碼的主要功能是掃描包下的所有帶有@FeignClient註解的類
    Set<String> basePackages;
 
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    // @FeignClient註解過濾器
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        // 在這裏獲取帶有@FeignClient註解的類,放在basePackages中
        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() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
            }
        };
        scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }
 
    // 2.針對所有帶有@FeignClient註解的類或接口分別封裝
    // 註冊其Configuration類(如果有的話)
    // 並將類或接口本身注入到Spring中
    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);
                // 2.1 註冊其Configuration類(如果有的話)
                // 本例中使用的是默認的Configuration,就不再分析該段代碼
                registerClientConfiguration(registry, name,
                                            attributes.get("configuration"));
 
                // 2.2 並將類或接口本身注入到Spring中
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

總結:從3)的分析可知,@EnableFeignClients註解的主要功能就是把帶有@FeignClient註解的類或接口註冊到Spring中
關於具體是如何註冊的、請求時如何轉換的,暫時還不清楚,但是代碼結構已經很清晰了。接下來我們繼續分析registerFeignClient()方法

4.registerFeignClient()方法的分析

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    // 1.獲取類名稱,也就是本例中的FeignService接口
    String className = annotationMetadata.getClassName();
   
    // 2.BeanDefinitionBuilder的主要作用就是構建一個AbstractBeanDefinition
    // AbstractBeanDefinition類最終被構建成一個BeanDefinitionHolder
    // 然後註冊到Spring中
    // 注意:beanDefinition類爲FeignClientFactoryBean,故在Spring獲取類的時候實際返回的是
    // FeignClientFactoryBean類
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    
    // 3.添加FeignClientFactoryBean的屬性,
    // 這些屬性也都是我們在@FeignClient中定義的屬性
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    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);
 
    // 4.設置別名 name就是我們在@FeignClient中定義的name屬性
    String alias = name + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
 
    boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
 
    beanDefinition.setPrimary(primary);
 
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }
 
    // 5.定義BeanDefinitionHolder,
    // 在本例中 名稱爲FeignService,類爲FeignClientFactoryBean
    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

總結4:通過分析可知:我們最終是向Spring中註冊了一個bean,bean的名稱就是類或接口的名稱(也就是本例中的FeignService),bean的實現類是FeignClientFactoryBean,其屬性設置就是我們在@FeignClient中定義的屬性

那麼下面我們在Controller中對FeignService的的引入,實際就是引入了FeignClientFactoryBean類

5.FeignClientFactoryBean的分析

這個類有什麼神通廣大的地方呢?

我們目前只知道,做了這麼多工作之後,就是爲了把這個類與我們的接口對應起來並註冊到容器中,@FeignClient的屬性也被添加到該類中,那麼具體的工作都應該是在這個類中實現了。

本文暫時先分析到這,在 下篇中我們會詳細分析下該類的具體功能實現

總結:

1)@EnableFeignClients註解將類FeignClientsRegistrar註冊到Spring中

2)FeignClientsRegistrar類主要是掃描包路徑下的所有類,將帶有@FeignClient註解的類或接口註冊到Spring中

3)如何註冊帶有@FeignClient的類或接口呢?就是生成一個BeanDefinitionHolder類,beanName爲@FeignClient所在接口的名稱,beanDefinition爲FeignClientFactoryBean,並將@FeignClient的屬性添加到FeignClientFactoryBean中

4)至此,我們已經把帶有@FeignClient註解的類或接口註冊到Spring中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章