SpringCloud + Zookeeper + Feign整合及Feign原理

知其然

SpringCloud + Zookeeper

Spring Cloud 与 Zookeeper的整合只需要添加相关的starter依赖和增加相关注解即可完成。

pom.xml 如下:



    
        FeignDemo
        com.hui
        1.0-SNAPSHOT
    
    4.0.0

    feignHello-service

    
        
            org.springframework.cloud
            spring-cloud-starter-zookeeper-discovery
            
            
                
                    org.apache.zookeeper
                    zookeeper
                
            
        
        
            org.apache.zookeeper
            zookeeper
            3.4.11
            
                
                    log4j
                    log4j
                
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
        
        
        
            com.hui
            feignHello-api
            1.0-SNAPSHOT
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

bootstrap.yml如下:

server:
  port: 8000
spring:
  application:
    name: feignHelloService
  cloud:
    zookeeper:
      connect-string: 192.168.4.192:2181 #zk地址

最后开启服务的注册与发现

@SpringBootApplication
@EnableDiscoveryClient
public class HelloServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloServiceApplication.class, args);
    }
}

service 和controller实现

@Service
public class HelloService{
    public HelloResponse sayHello(HelloRequest request) {
        return new HelloResponse(request.toString());
    }
}
@Api(value = "Hello 服务", tags = "Hello 服务")
@RestController
public class HelloController implements IHelloServiceClient {
    @Autowired
    HelloService helloService;

    @Override
    @ApiOperation(value = "say hello 带默认参数")
    @GetMapping("/hello")
    //http://localhost:8000/hello?name=qqqq&age=22
    public String sayHello(@RequestParam(name = "name", defaultValue = "tony") String name,
                           @RequestParam(name = "age", defaultValue = "18") int age) {
        HelloRequest request = new HelloRequest(name, age);
        return helloService.sayHello(request).toString();
    }

    @Override
    @GetMapping("/hello1")
    @PostMapping(value = "say hello 不带参数")
    public String sayHello() {
        HelloRequest request = new HelloRequest("tony", 19);
        return helloService.sayHello(request).toString();
    }

    @Override
    @PostMapping("/hello2")
    @ApiOperation(value = "say hello 带请求体")
    public HelloResponse sayHello(@RequestBody HelloRequest request) {
        return helloService.sayHello(request);
    }
}

笔者加入了swagger,如果需要只需加入如下依赖和配置:

    
        com.github.xiaoymin
        knife4j-spring-boot-starter
        2.0.7
    
@Configuration
@EnableSwagger2WebMvc
public class SwaggerAutoConfiguration {

    @Bean
    public Docket defaultApi(){
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder().title("helloService").description("RpcDemo").version("1.0").build())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

}

至此,spring cloud与zookeeper的整合就完成了,调用结果如下:


helloService.jpg

SpringCloud + Zookeeper + Feign

为了测试与Feign的整合,再构建一个消费者:与上述构建的过程类似。

pom.xml 增加spring-cloud-starter-openfeign依赖



    
        FeignDemo
        com.hui
        1.0-SNAPSHOT
    
    4.0.0

    feignHello-test

    
        
            com.hui
            feignHello-api
            1.0-SNAPSHOT
        
        
            org.springframework.cloud
            spring-cloud-starter-zookeeper-discovery
            
                
                    org.apache.zookeeper
                    zookeeper
                
            
        
        
            org.apache.zookeeper
            zookeeper
            3.4.11
            
                
                    log4j
                    log4j
                
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
            2.2.4.RELEASE
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    

bootstrap.yaml:

server:
  port: 8001
spring:
  application:
    name: feignHelloTest
  cloud:
    zookeeper:
      connect-string: 192.168.4.192:2181
  main:
    allow-bean-definition-overriding: true #笔者定义了两个相同FeignClient,所以bean相同

开启服务注册与发现,@EnableFeignClients注解注册FeignClient

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class HelloServiceTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloServiceTestApplication.class, args);
    }
}

@FeignClient注册声明定义FeignClient,笔者以两种方式定义了两个FeignClient:

1.通过请求路径定义FeignClient

@FeignClient(value = "feignHelloService", path = "/hello")
public interface RemoteServiceClient {
    @RequestMapping("/")
    String testHello();
}

2.通过生产者(即上述构建的helloService)暴露出来的接口定义FeignClient

@Component
@FeignClient(name = "feignHelloService")
public interface RemoteServiceRpcClient extends IHelloServiceClient {
}

controller 测试:

@Api(value = "Hello 测试 服务", tags = "Hello 测试 服务")
@RestController
public class HelloTestController {

    @Resource
    RemoteServiceClient remoteService;

    @Autowired
    RemoteServiceRpcClient remoteServiceRpcClient;

    @ApiOperation(value = "远程调用方式测试1")
    @GetMapping("/remote")
    public String remoteHello(){
        return remoteService.testHello();
    }

    @ApiOperation(value = "本地调用方式测试")
    @GetMapping("/local")
    public String localHello(){
        return "hello" + UUID.randomUUID().toString();
    }

    @ApiOperation(value = "远程调用方式测试2,带请求参数")
    @PostMapping("/remoteRpc")
    public String remoteRpcHello(){
        return remoteServiceRpcClient.sayHello("tony",110);
//        return remoteServiceRpcClient.sayHello();
    }

    @ApiOperation(value = "远程调用方式测试3, 带请求体")
    @PostMapping("/remoteRpc1")
    public HelloResponse remoteRpcHello1(){
        return remoteServiceRpcClient.sayHello(new HelloRequest("YYY",122));
    }

}

测试结果如下:


helloServiceTest.jpg

知其所以然

知道了如何将SpringCloud, Zookeeper 和Feign进行整合,我们知道了怎么使用,更重要的是要知道里面的原理,做到知其然更要知其所以然。

通过上述对整合过程的描述中可以发现,@EnableFeignClients和@FeignClient两个注解是将Feign整合进Spring Cloud的重要组成部分,因此,从这两个注解入手来了解Feign。

@EnableFeignClients:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
    /**
    * basePackages属性的别名
    */
    String[] value() default {};
    /**
    * 包扫描路径,扫描该路径下被@FeignClient标记的类
    */
    String[] basePackages() default {};
    /**
    * 指明包扫描的类
    */
    Class[] basePackageClasses() default {};
    /**
    * 对所有feign client定制的配置类
    */
    Class[] defaultConfiguration() default {};
    /**
    * 所有被@FeignClient标记的client,如果不为空,就不会基于classpath进行扫描
    */
    Class[] clients() default {};
}

@EnableFeignClients -> FeignClientsRegistrar

@EnableFeignClients注解通过@Import引入了FeignClientsRegistrar进行feign客户端的注册, 同时FeignClientsRegistrar通过实现ImportBeanDefinitionRegistrar来将bean注册spring容器中:

public interface ImportBeanDefinitionRegistrar {
    /**
     * 根据使用者的配置类的注解元数据来注册bean的定义
     * @param importingClassMetadata 配置类的注解元数据
     * @param registry 当前bean定义的注册器,一般指spring容器
     */
    default void registerBeanDefinitions(AnnotationMetadata
    importingClassMetadata, BeanDefinitionRegistry registry) {}
}
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
        
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //注册默认的配置到spring容器中
        registerDefaultConfiguration(metadata, registry);
        //注册发现的feign client到spring容器中
        registerFeignClients(metadata, registry);
    }
}

@EnableFeignClients -> FeignClientsRegistrar —> registerDefaultConfiguration

    private void registerDefaultConfiguration(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //获取@EnableFeignClients注解的属性
        Map defaultAttrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

        if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
            String name;
            //拼接默认配置名
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            }
            else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    defaultAttrs.get("defaultConfiguration"));
        }
    }
    
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
            Object configuration) {
        //将feign client 配置构建成一个bean注册到spring容器中
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(
                name + "." + FeignClientSpecification.class.getSimpleName(),
                builder.getBeanDefinition());
    }

@EnableFeignClients -> FeignClientsRegistrar —> registerFeignClients

    public void registerFeignClients(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //定义一个基于classpath的扫描器,用来获取被@FeignClient注解标注的feign clent
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        Set basePackages;
        
        //获取@EnableFeignClients注解的属性
        Map attrs = metadata
                .getAnnotationAttributes(EnableFeignClients.class.getName());
        //@FeignClient注解过滤器
        AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
                FeignClient.class);
        //获取@EnableFeignClient注解的clients属性的值
        final Class[] clients = attrs == null ? null
                : (Class[]) attrs.get("clients");
        if (clients == null || clients.length == 0) {
            //@EnableFeignClients注解没有配置clients属性的情况
            //扫描器中加入注解过滤器
            scanner.addIncludeFilter(annotationTypeFilter);
            //获取@EnableFeignClients注解中的basePackages属性
            basePackages = getBasePackages(metadata);
        }
        else {
            //@EnableFeignClients注解配置了clients属性的情况
            final Set clientClasses = new HashSet<>();
            basePackages = new HashSet<>();
            for (Class clazz : clients) {
                //遍历client,获取器包路径和类名
                basePackages.add(ClassUtils.getPackageName(clazz));
                clientClasses.add(clazz.getCanonicalName());
            }
            //定义过滤器,只获取在clientClasses集合中的feign client
            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)));
        }
        //使用扫描器scanner扫描每一个basePackage,获取被@FeignClient标注的客户端
        for (String basePackage : basePackages) {
            Set 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");

                    //获取@FeignClient注解的属性
                    Map attributes = annotationMetadata
                            .getAnnotationAttributes(
                                    FeignClient.class.getCanonicalName());

                    String name = getClientName(attributes);
                    //将针对特定feign client的配置注册到spring容器
                    registerClientConfiguration(registry, name, attributes.get("configuration"));
                    //注册feign client到spring容器       
                    registerFeignClient(registry, annotationMetadata, attributes);
                }
            }
        }
    }
    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map attributes) {
        String className = annotationMetadata.getClassName();
        //通过FeignClientFactoryBean工厂bean构建BeanDefinitionBuilder
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        validate(attributes);
        //@FeignClient注解的属性作为bean的属性
        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 null
        boolean 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 });
        //注册feign client到spring容器
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

至此,我们知道了通过@EnableFeignClients和@FeignClient两个注解以及其相关属性,在服务启动时,将每个feign client 以及其对应的配置和每个客户端通用的配置以bean的方式注册完到spring容器中。

FeignClient的自动注入

当使用@Autowired注解自动注入FeignClient时,Spring容器会使用注册FeignClient用到的FeignClientFactoryBean为其生成FeignClient实例。

class FeignClientFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware {

    @Override
    public Object getObject() throws Exception {
        return getTarget();
    }

    /**
     * @param  the target type of the Feign client
     * @return a {@link Feign} client created with the specified data and the context
     * information
     */
     T getTarget() {
        //从应用上下文中获取FeignClient的上下文
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //通过FeignClient的上下文构建feignClient构造器
        Feign.Builder builder = feign(context);

        
        if (!StringUtils.hasText(url)) {
            //@FeignClient没有配置url的情况,根据name和path属性拼接成url
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            //没有配置url属性,需要在多个服务节点之间进行负载均衡,生产Feign client
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        //配置了url属性的情况
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
        //从上下文获取FeignClien:LoadBalancerFeignClient
        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
                client = ((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
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            builder.client(client);
        }
        //从上下文中获取targeter
        Targeter targeter = get(context, Targeter.class);
        //通过targeter、builder生成Feign client
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(type, name, url));
    }
    
    
    protected  T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget target) {
        //上下文获取Feign client
        Client client = getOptional(context, Client.class);
        if (client != null) {
            //将builder与client关联
            builder.client(client);
            Targeter targeter = get(context, Targeter.class);
            //生产Feign client
            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?");
    }
}
 
 

默认使用的targeter是HystrixTargeter,根据builder的类型设置不同的属性,并生产Feign client

class HystrixTargeter implements Targeter {

    @Override
    public  T target(FeignClientFactoryBean factory, Feign.Builder feign,
            FeignContext context, Target.HardCodedTarget target) {
        if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
            return feign.target(target);
        }
        //....省略
        return feign.target(target);
    }
}
public abstract class Feign {
      public static class Builder {
            //构建客户端并创建feign client实例
            public  T target(Target target) {
                return build().newInstance(target);
            }
            public Feign build() {
                  Client client = Capability.enrich(this.client, capabilities);
                  Retryer retryer = Capability.enrich(this.retryer, capabilities);
                  List requestInterceptors = this.requestInterceptors.stream()
                      .map(ri -> Capability.enrich(ri, capabilities))
                      .collect(Collectors.toList());
                  Logger logger = Capability.enrich(this.logger, capabilities);
                  Contract contract = Capability.enrich(this.contract, capabilities);
                  Options options = Capability.enrich(this.options, capabilities);
                  Encoder encoder = Capability.enrich(this.encoder, capabilities);
                  Decoder decoder = Capability.enrich(this.decoder, capabilities);
                  InvocationHandlerFactory invocationHandlerFactory =
                      Capability.enrich(this.invocationHandlerFactory, capabilities);
                  QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
            
                  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
                      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                          logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
                  ParseHandlersByName handlersByName =
                      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
                          errorDecoder, synchronousMethodHandlerFactory);
                  //根据相关配置,构建ReflectiveFeign
                  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
           }
      }
}
public class ReflectiveFeign extends Feign {
    private final ParseHandlersByName targetToHandlersByName;
    private final InvocationHandlerFactory factory;
    private final QueryMapEncoder queryMapEncoder;
    
    ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
          QueryMapEncoder queryMapEncoder) {
        this.targetToHandlersByName = targetToHandlersByName;
        this.factory = factory;
        this.queryMapEncoder = queryMapEncoder;
    }
    @Override
    public  T newInstance(Target target) {
    Map nameToHandler = targetToHandlersByName.apply(target);
    Map methodToHandler = new LinkedHashMap();
    List defaultMethodHandlers = new LinkedList();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if (Util.isDefault(method)) {
        //对于缺省的方法使用DefaultMethodHandler
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        //对于每个对应服务端的方法,使用nameToHandler获取methodHandler
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //通过InvocationHandlerFactory创建FeignInvocationHandler,该handler包含了上面创建的methodTohHandler
    //构成dispatch,用于对应@FeignClient标注的接口方法,当调用时进行转发处理
    InvocationHandler handler = factory.create(target, methodToHandler);
    //为feign客户端实例创建动态代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
        new Class[] {target.type()}, handler);
    //将缺省的methodHander绑定到动态代理对象上
    for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }
}

总结

从上面的分析可以得出,当服务启动时,通过@EnableFeignClients注解,启动对标注了@FeignClient注解的类进行扫描和注册,通过FeignClientFactoryBean将FeignClient注册到Spring容器中。当使用@Autowired注解进行自动注入时,注册到Spring容器中FeignClient会以动态代理的形式注入,这些动态代理中包含了接口方法的methodHandler用以处理调用转发。

你可能感兴趣的:(SpringCloud + Zookeeper + Feign整合及Feign原理)