SpringCloud + Zookeeper + Feign整合及Feign原理
知其然
SpringCloud + Zookeeper
Spring Cloud 与 Zookeeper的整合只需要添加相关的starter依赖和增加相关注解即可完成。
pom.xml
如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>FeignDemo</artifactId><groupId>com.hui</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>feignHello-service</artifactId><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper-discovery</artifactId><!-- 需要特别主题spring cloud 和zookeeper 的版本笔者本地使用了3.4.11的zk,因此在此处排除了默认的zk,单独引入--><exclusions><exclusion><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.11</version><exclusions><exclusion><groupId>log4j</groupId><artifactId>log4j</artifactId></exclusion><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!--笔者将api与service分为了独立的module,所以这里加入引用api--><dependency><groupId>com.hui</groupId><artifactId>feignHello-api</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
bootstrap.yml
如下:
server:port: 8000
spring:application:name: feignHelloServicecloud: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 {@AutowiredHelloService helloService;@Override@ApiOperation(value = "say hello 带默认参数")@GetMapping("/hello")//http://localhost:8000/hello?name=qqqq&age=22public 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,如果需要只需加入如下依赖和配置:
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>2.0.7</version></dependency>
@Configuration
@EnableSwagger2WebMvc
public class SwaggerAutoConfiguration {@Beanpublic 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的整合就完成了,调用结果如下:

SpringCloud + Zookeeper + Feign
为了测试与Feign的整合,再构建一个消费者:与上述构建的过程类似。
pom.xml 增加spring-cloud-starter-openfeign依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>FeignDemo</artifactId><groupId>com.hui</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>feignHello-test</artifactId><dependencies><dependency><groupId>com.hui</groupId><artifactId>feignHello-api</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper-discovery</artifactId><exclusions><exclusion><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.11</version><exclusions><exclusion><groupId>log4j</groupId><artifactId>log4j</artifactId></exclusion><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.4.RELEASE</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
bootstrap.yaml:
server:port: 8001
spring:application:name: feignHelloTestcloud:zookeeper:connect-string: 192.168.4.192:2181main: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 {@ResourceRemoteServiceClient remoteService;@AutowiredRemoteServiceRpcClient 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));}}
测试结果如下:

知其所以然
知道了如何将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(AnnotationMetadataimportingClassMetadata, BeanDefinitionRegistry registry) {}
}
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {@Overridepublic 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<String, Object> 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 clentClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);Set<String> basePackages;//获取@EnableFeignClients注解的属性Map<String, Object> 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<String> clientClasses = new HashSet<>();basePackages = new HashSet<>();for (Class<?> clazz : clients) {//遍历client,获取器包路径和类名basePackages.add(ClassUtils.getPackageName(clazz));clientClasses.add(clazz.getCanonicalName());}//定义过滤器,只获取在clientClasses集合中的feign clientAbstractClassTestingTypeFilter 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)));}//使用扫描器scanner扫描每一个basePackage,获取被@FeignClient标注的客户端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");//获取@FeignClient注解的属性Map<String, Object> 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<String, Object> attributes) {String className = annotationMetadata.getClassName();//通过FeignClientFactoryBean工厂bean构建BeanDefinitionBuilderBeanDefinitionBuilder 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 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 });//注册feign client到spring容器BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
至此,我们知道了通过@EnableFeignClients和@FeignClient两个注解以及其相关属性,在服务启动时,将每个feign client 以及其对应的配置和每个客户端通用的配置以bean的方式注册完到spring容器中。
FeignClient的自动注入
当使用@Autowired注解自动注入FeignClient时,Spring容器会使用注册FeignClient用到的FeignClientFactoryBean为其生成FeignClient实例。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {@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() {//从应用上下文中获取FeignClient的上下文FeignContext context = applicationContext.getBean(FeignContext.class);//通过FeignClient的上下文构建feignClient构造器Feign.Builder builder = feign(context);if (!StringUtils.hasText(url)) {//@FeignClient没有配置url的情况,根据name和path属性拼接成urlif (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();//没有配置url属性,需要在多个服务节点之间进行负载均衡,生产Feign clientreturn (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:LoadBalancerFeignClientClient 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);}//从上下文中获取targeterTargeter targeter = get(context, Targeter.class);//通过targeter、builder生成Feign clientreturn (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));}protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {//上下文获取Feign clientClient client = getOptional(context, Client.class);if (client != null) {//将builder与client关联builder.client(client);Targeter targeter = get(context, Targeter.class);//生产Feign clientreturn 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 {@Overridepublic <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,FeignContext context, Target.HardCodedTarget<T> 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> T target(Target<T> target) {return build().newInstance(target);}public Feign build() {Client client = Capability.enrich(this.client, capabilities);Retryer retryer = Capability.enrich(this.retryer, capabilities);List<RequestInterceptor> 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);//根据相关配置,构建ReflectiveFeignreturn 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;}@Overridepublic <T> T newInstance(Target<T> target) {Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();for (Method method : target.type().getMethods()) {if (method.getDeclaringClass() == Object.class) {continue;} else if (Util.isDefault(method)) {//对于缺省的方法使用DefaultMethodHandlerDefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {//对于每个对应服务端的方法,使用nameToHandler获取methodHandlermethodToHandler.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用以处理调用转发。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务

喜欢的朋友记得点赞、收藏、关注哦!!!