你必须懂也可以懂的微服务系列三:服务调用
在了解服務注冊與反注冊后,就該到服務調(diào)用環(huán)節(jié)了。
進行服務調(diào)用之前,需要組裝請求頭、請求體,構(gòu)建客戶端對象,通過服務提供方url地址調(diào)用遠程服務。此方式雖然可以實現(xiàn)遠程調(diào)用,但是需要使用者了解底層調(diào)用,并且還會給開發(fā)人員帶來重復編碼的問題。
為此,今天就來了解下SpringCloud的OpenFeign是如何實現(xiàn)遠程調(diào)用的
3.服務調(diào)用
3.1 開啟Feign遠程調(diào)用
當我們需要使用feign進行遠程調(diào)用時,只需要在入口處加上@EnableFeignClients注解即可
3.2 掃描FeignClient注解標注的接口
@EnableFeignClients注解引入FeignClientsRegistrar配置類,該配置類會執(zhí)行掃描動作,掃描項目中被@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 創(chuàng)建并注冊FeignClientFactoryBean
遍歷3.2中掃描到的組件,每個組件創(chuàng)建一個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. 創(chuàng)建FeignClientFactoryBeanFeignClientFactoryBean 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 nullboolean 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
當我們調(diào)用遠程服務接口時,往往需要通過@Autowired注解的方式注入服務,然后調(diào)用對應的方法
@RestController public class OpenfeignConsumerController {@Autowiredprivate 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注解標注,根據(jù)3.3章節(jié)可以了解到其實際上是一個FeignClientFactoryBean
FeignClientFactoryBean實現(xiàn)了FactoryBean接口,因此當使用@Autowired注解進行注入的時候,注入的是FeignClientFactoryBean中getObject()方法返回的對象
3.5 FeignContext
從命名可以得知FeignContext是feign的上下文,其存在著一個Map類型的context屬性
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>(); 復制代碼也就是說,每個@FeignClient對應一個AnnotationConfigApplicationContext上下文,本身應用也存在這一個AnnotationConfigApplicationContext
因此就形成了父子上下文
3.6 構(gòu)建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; } 復制代碼構(gòu)建Feign Builder就是從容器中獲取相關組件進行設置,然后在對Feign Builder進行個性化配置
3.7 從容器獲取組件
3.6章節(jié)中this.get(context, xxxx.class))這個方法頻繁出現(xiàn),需要了解一下該方法具體實現(xiàn),此處以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章節(jié)中介紹過 FeignContext,其維護了一個子容器集合,因此首先會先從子容器集合中獲取指定名稱的子容器
既然FeignContext維護了子容器集合,那么就必須了解子容器是如何創(chuàng)建的
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 beanscontext.setParent(this.parent);// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));context.refresh();return context; } 復制代碼每次先從集合中取,如果集合中沒有對應的子容器則進行創(chuàng)建,然后讓容器中注冊了PropertyPlaceholderAutoConfiguration和this.defaultConfigType
通過FeignContext的構(gòu)造函數(shù),我們可以了解到this.defaultConfigType就是FeignClientsConfiguration
打開FeignClientsConfiguration可以看到里面聲明了Encoder、Decoder、Contract等Bean
3.8 小結(jié)
到這里腦海中應該有個大概的總結(jié):
- @FeignClient對應FeignClientFactoryBean
- 注入@FeignClient標注的接口,實際上注入的是FeignClientFactoryBean中getObject()返回的對象
- FeignContext做為feign的上下文,為每個@FeignClient創(chuàng)建一個子容器,子容器中聲明所需要的Bean
- 之所以為每個@FeignClient聲明一個子容器,是會了讓@FeignClient與@FeignClient的配置進行隔離
3.9 創(chuàng)建Feign實例
之前通過Feign Builder鏈式方式設置了相關屬性,現(xiàn)在就可以通過Feign Builder來創(chuàng)建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用于創(chuàng)建InvocationHandlerInvocationHandlerFactory invocationHandlerFactory =Capability.enrich(this.invocationHandlerFactory, capabilities);QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);// 2.SynchronousMethodHandler用于創(chuàng)建SynchronousMethodHandlerSynchronousMethodHandler.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 創(chuàng)建代理
@Override public <T> T newInstance(Target<T> target) {// 1.遍歷@FeignClient標注接口中的方法,通過SynchronousMethodHandler.Factory創(chuàng)建 MethodHandlerMap<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. 構(gòu)建Method與MethodHandler的映射關系,用于方法路由methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}// 3. 通過InvocationHandlerFactory創(chuàng)建InvocationHandlerInvocationHandler 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; 復制代碼注入的是一個代理對象,當調(diào)用IndexClient方法的時候,會回調(diào)ReflectiveFeign.FeignInvocationHandler的invoke方法
3.11 方法調(diào)用
@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); } 復制代碼如上根據(jù)方法路由到對應的SynchronousMethodHandler,然后調(diào)用其invoke()
@Override public Object invoke(Object[] argv) throws Throwable {// 1.構(gòu)建請求模板RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);Retryer retryer = this.retryer.clone();while (true) {try {// 2.通過http方式發(fā)起遠程調(diào)用并返回結(jié)果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;}} } 復制代碼到此就實現(xiàn)了遠程服務調(diào)用,可以看到使用Openfeign進行遠程調(diào)用十分簡單,只需搭配相關注解就可以像調(diào)用本地方法一樣調(diào)用遠程服務
4.結(jié)語
通過本文Openfeign講解后,你也可以動手嘗試使用HttpClient配合動態(tài)代理實現(xiàn)一個RPC框架,滿足自己的成就感
總結(jié)
以上是生活随笔為你收集整理的你必须懂也可以懂的微服务系列三:服务调用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你居然还去服务器上捞日志,搭个日志收集系
- 下一篇: Redis的持久化机制与内存管理机制