SpringCloud Feign基于Netflix Feign實現(xiàn),整合SpringCloud Ribbon和SpringCloud Hystrix
我們在使用微服務(wù)框架的時候,一般都會在項目中同時使用Ribbon和Hystrix,所以可以考慮直接使用Feign來整合
1.Feign的使用
我們現(xiàn)在需要創(chuàng)建一個服務(wù)消費者應(yīng)用,消費服務(wù)提供者的服務(wù)
1)引入maven依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2)創(chuàng)建接口FeignService,提供消費服務(wù)
@FeignClient(name="part-1-sms-interface", fallback=FeginFallbackService.class)
public interface FeignService {
@RequestMapping("/sms/test")
String test();
}
// 服務(wù)降級類
@Component
public class FeginFallbackService {
public String test(){
return "fallback error";
}
}
注意:
@FeignClient注解中的name為服務(wù)提供者所在應(yīng)用的spring.application.name,fallback所對應(yīng)的類為服務(wù)調(diào)用異常時服務(wù)降級的類
@RequestMapping("/sms/test")為服務(wù)提供者應(yīng)用中的方法路徑
@FeignClient還有一個關(guān)鍵的configuration參數(shù),可以讓用戶自定義配置bean
注解默認(rèn)的配置bean所在類為FeignClientsConfiguration,用戶可參考其中的bean實現(xiàn)來自定義
3)創(chuàng)建Controller
@RestController
@RequestMapping("/feign")
public class FeignController {
@Autowired
private FeignService feignService;
@GetMapping(value="/test")
public String test(){
return feignService.test();
}
}
4)測試驗證
調(diào)用/feign/test方法,可以看到其調(diào)用了服務(wù)提供者part-1-sms-interface提供的/sms/test方法,驗證成功
2.寫在源碼分析之前
經(jīng)過上面的關(guān)于Feign的使用分析,可以看到使用的時候還是非常簡單的,只需要兩個簡單的注解就實現(xiàn)了Ribbon+Hystrix的功能。
那具體是如何實現(xiàn)的呢?
關(guān)鍵的一步就是如何把對接口的請求映射為對真正服務(wù)的請求(也就是一個HTTP請求),同時還要加上Hystrix的功能。
映射為HTTP請求、Hystrix的功能都是每個服務(wù)共同的需求,所以肯定是被抽象出來的。而且我們沒有對接口有任何具體實現(xiàn),那么應(yīng)該是用了反射的功能了,實現(xiàn)具體接口的實現(xiàn)類,并注冊到Spring容器中,這樣才能在Controller層中使用@Autowired
3.分析注解@EnableFeignClients
通過上面的使用過程分析,@EnableFeignClients和@FeignClient兩個注解就實現(xiàn)了Feign的功能,那我們重點分析下@EnableFeignClients注解
1)@EnableFeignClients
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)// 重點在這里,注入FeignClientsRegistrar類
public @interface EnableFeignClients {}
2)FeignClientsRegistrar.java分析
通過其類結(jié)構(gòu)可知,其實現(xiàn)了ImportBeanDefinitionRegistrar接口,那么在registerBeanDefinitions()中就會注冊一些bean到Spring中
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware{
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 1.針對那些在@EnableFeignClients中添加了defaultConfiguration屬性的進(jìn)行操作
// 將這些類定義的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類(如果有的話)
// 本例中使用的是默認(rèn)的Configuration,就不再分析該段代碼
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 2.2 并將類或接口本身注入到Spring中
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
總結(jié):從3)的分析可知,@EnableFeignClients注解的主要功能就是把帶有@FeignClient注解的類或接口注冊到Spring中
關(guān)于具體是如何注冊的、請求時如何轉(zhuǎn)換的,暫時還不清楚,但是代碼結(jié)構(gòu)已經(jīng)很清晰了。接下來我們繼續(xù)分析registerFeignClient()方法
電子商務(wù)社交平臺源碼請加企鵝求求:三五三六二四七二五九