1.系列文章
2.前言
在了解服务注册
与反注册
后,就该到服务调用环节了。
进行服务调用之前,需要组装请求头、请求体,构建客户端对象,通过服务提供方url地址调用远程服务。此方式虽然可以实现远程调用,但是需要使用者了解底层调用,并且还会给开发人员带来重复编码的问题。
为此,今天就来了解下SpringCloud
的OpenFeign
是如何实现远程调用的
3.服务调用
3.1 开启Feign远程调用
当我们需要使用feign
进行远程调用时,只需要在入口处加上@EnableFeignClients
注解即可
3.2 扫描FeignClient注解标注的接口
@EnableFeignClients
注解引入FeignClientsRegistrar
配置类,该配置类会执行扫描动作,扫描项目中被@FeignClient
注解标注的接口
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
复制代码
关键代码:
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
3.3 创建并注册FeignClientFactoryBean
遍历3.2
中扫描到的组件,每个组件创建一个FeignClientFactoryBean
并注入到IOC
容器中,FeignClientFactoryBean
的类型就是使用@FeignClient
标注的接口
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 1. 创建FeignClientFactoryBean
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
// 2. 设置属性
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if (fallback != null) {
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
: ClassUtils.resolveClassName(fallback.toString(), null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if (fallbackFactory != null) {
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 3.注入到IOC容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
复制代码
3.4 注入FeignClientFactoryBean
当我们调用远程服务接口时,往往需要通过@Autowired
注解的方式注入服务,然后调用对应的方法
@RestController
public class OpenfeignConsumerController {
@Autowired
private IndexClient indexClient;
@GetMapping("/index/{name}")
public String index(@PathVariable(name = "name") String name) {
return indexClient.index(name);
}
}
@FeignClient(value = "openfeign-provider-service")
public interface IndexClient {
@GetMapping("/index/{name}")
String index(@PathVariable(name = "name") String name);
}
复制代码
IndexClient
被@FeignClient
注解标注,根据3.3
章节可以了解到其实际上是一个FeignClientFactoryBean
FeignClientFactoryBean
实现了FactoryBean
接口,因此当使用@Autowired
注解进行注入的时候,注入的是FeignClientFactoryBean
中getObject()
方法返回的对象
3.5 FeignContext
从命名可以得知FeignContext
是feign
的上下文,其存在着一个Map
类型的context
属性
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
复制代码
也就是说,每个
@FeignClient
对应一个AnnotationConfigApplicationContext
上下文,本身应用也存在这一个AnnotationConfigApplicationContext
因此就形成了父子上下文
3.6 构建Feign Builder
Builder builder = this.feign(context);
复制代码
protected Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
this.configureFeign(context, builder);
this.applyBuildCustomizers(context, builder);
return builder;
}
复制代码
构建
Feign Builder
就是从容器中获取相关组件进行设置,然后在对Feign Builder
进行个性化配置
3.7 从容器获取组件
3.6
章节中this.get(context, xxxx.class))
这个方法频繁出现,需要了解一下该方法具体实现,此处以this.get(context, FeignLoggerFactory.class)
为例
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
} else {
return instance;
}
}
复制代码
在
3.5
章节中介绍过FeignContext
,其维护了一个子容器集合,因此首先会先从子容器集合中获取指定名称的子容器
既然FeignContext
维护了子容器集合,那么就必须了解子容器是如何创建的
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
复制代码
每次先从集合中取,如果集合中没有对应的子容器则进行创建,然后让容器中注册了
PropertyPlaceholderAutoConfiguration
和this.defaultConfigType
通过
FeignContext
的构造函数,我们可以了解到this.defaultConfigType
就是FeignClientsConfiguration
打开
FeignClientsConfiguration
可以看到里面声明了Encoder
、Decoder
、Contract
等Bean
3.8 小结
到这里脑海中应该有个大概的总结:
@FeignClient
对应FeignClientFactoryBean
- 注入
@FeignClient
标注的接口,实际上注入的是FeignClientFactoryBean
中getObject()
返回的对象 FeignContext
做为feign
的上下文,为每个@FeignClient
创建一个子容器,子容器中声明所需要的Bean
- 之所以为每个
@FeignClient
声明一个子容器,是会了让@FeignClient
与@FeignClient
的配置进行隔离
3.9 创建Feign实例
之前通过Feign Builder
链式方式设置了相关属性,现在就可以通过Feign Builder
来创建Feign
实例
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);
// 1.InvocationHandlerFactory用于创建InvocationHandler
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
// 2.SynchronousMethodHandler用于创建SynchronousMethodHandler
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);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
复制代码
3.10 创建代理
@Override
public <T> T newInstance(Target<T> target) {
// 1.遍历@FeignClient标注接口中的方法,通过SynchronousMethodHandler.Factory创建 MethodHandler
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 handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// 2. 构建Method与MethodHandler的映射关系,用于方法路由
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// 3. 通过InvocationHandlerFactory创建InvocationHandler
InvocationHandler handler = factory.create(target, methodToHandler);
// 4. 生成代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
复制代码
到此我们可以得知
@Autowired private IndexClient indexClient; 复制代码
注入的是一个代理对象,当调用
IndexClient
方法的时候,会回调ReflectiveFeign.FeignInvocationHandler
的invoke
方法
3.11 方法调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
复制代码
如上根据方法路由到对应的
SynchronousMethodHandler
,然后调用其invoke()
@Override
public Object invoke(Object[] argv) throws Throwable {
// 1.构建请求模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 2.通过http方式发起远程调用并返回结果
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
复制代码
到此就实现了远程服务调用,可以看到使用
Openfeign
进行远程调用十分简单,只需搭配相关注解就可以像调用本地方法一样调用远程服务
4.结语
通过本文Openfeign
讲解后,你也可以动手尝试使用HttpClient
配合动态代理
实现一个RPC
框架,满足自己的成就感
近期评论