SpringCloud + Zookeeper + 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: 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,如果需要只需加入如下依赖和配置:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.7</version>
</dependency>
@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依赖
<?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: 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<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 clent
ClassPathScanningCandidateComponentProvider 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 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<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");
//获取@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构建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<Object>, InitializingBean, ApplicationContextAware {
@Override
public 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属性拼接成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> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> 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> 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);
//根据相关配置,构建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> 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)) {
//对于缺省的方法使用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用以处理调用转发。