知其然
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的整合就完成了,调用结果如下:
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));
}
}
测试结果如下:
知其所以然
知道了如何将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
默认使用的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用以处理调用转发。