用户注册



邮箱:

密码:

用户登录


邮箱:

密码:
记住登录一个月忘记密码?

发表随想


还能输入:200字

it绿萝    -  云代码空间

——

java B2B2C Springcloud仿淘宝电子商城系统-Feign使用及源码深度解析

2019-01-28|318阅||

摘要: SpringCloud Feign基于Netflix Feign实现,整合SpringCloud Ribbon和SpringCloud Hystrix 需要JAVA Spring Cloud大型企业分布式微服务云构建的B2B2C电子商务平台源码 817983997     我

 SpringCloud Feign基于Netflix Feign实现,整合SpringCloud Ribbon和SpringCloud Hystrix
需要JAVA Spring Cloud大型企业分布式微服务云构建的B2B2C电子商务平台源码 yuncode.net
    我们在使用微服务框架的时候,一般都会在项目中同时使用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中。 java B2B2C Springcloud仿淘宝电子商城系统 

顶 0踩 0收藏
文章评论
    发表评论

    个人资料

    • 昵称: it绿萝
    • 等级: 高级设计师
    • 积分: 5730
    • 代码: 0 个
    • 文章: 192 篇
    • 随想: 0 条
    • 访问: 15 次
    • 关注

    人气代码

      最新提问

        站长推荐