feign使用在实现的效果上来说Feign = RestTemplate+Ribbon+Hystrix
Feign实现RestTemplate+Ribbon效果Feign实现RestTemplate+Ribbon效果 , 只需要以下几步
在springcloud项目调用方的pom文件中加入openFeign的配置
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>在启动类中加入@EnableFeignClients
同时使用接口声明的方式来实现接口调用
@FeignClient(name = "zhao-service-resume")public interface ResumeFeignClient {@GetMapping("/resume/openstate/{userId}")public Integer findDefaultResumeState(@PathVariable Long userId) ;}这个接口的声明与被调用方的实现完全一样 , 我们需要在声明时@FeignClient(name = "zhao-service-resume")声明被调用的服务 , 即可按照默认的方式进行调用
使用单元测试测试即可测试到负载均衡的效果,访问两次 , 分别访问到8081和8082端口的服务
@RunWith(SpringRunner.class)@SpringBootTest(classes = DeliverApplication8091.class)public class FeignTest {@AutowiredResumeFeignClient feignClient;@Testpublic void feignTest(){Integer port = feignClient.findDefaultResumeState(2195321L);System.out.println("测试的结果"+port);}}那么如何在配置类中配置负载均衡呢?格式如下,同时我们还配置了请求的超时时间 , 在没有配置hystrix的情况下 , 会出现超时的情况 ,
zhao-service-resume:ribbon: #请求连接超时时间ConnectTimeout: 2000#请求处理超时时间ReadTimeout: 5000#对所有操作都进?重试OkToRetryOnAllOperations: true####根据如上配置 , 当访问到故障请求的时候 , 它会再尝试访问?次当前实例(次数由MaxAutoRetries配置) , ####如果不? , 就换?个实例进?访问 , 如果还不? , 再换?次实例访问(更换次数由MaxAutoRetriesNextServer配置) , ####如果依然不? , 返回失败信息 。MaxAutoRetries: 0 #对当前选中实例重试次数 , 不包括第?次调?MaxAutoRetriesNextServer: 0 #切换实例的重试次数NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载策略调整超时的报错feign.RetryableException: Read timed out executing GET http://zhao-service-resume/resume/openstate/2195321
即是没有配置重试的这几个参数也是同样的效果
Feign实现Hystrix效果首先是先开启熔断器
feign:hystrix:enabled: true接着增加超时处理逻辑的相关配置
hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 15000但是即使我在被调用方只线程休眠了十秒 , 程序依然被熔断了 。查阅资料表明 , Hystrix将采用feign和hystrix超时时间中较小的那个进行超时判定

文章插图
增加降级兜底方法
【Feign的使用 Feign使用分析】
@Componentpublic class FeignClientFallBack implements ResumeFeignClient {@Overridepublic Integer findDefaultResumeState(Long userId) {return -1;}}在调用方增加降级配置@FeignClient(name = "zhao-service-resume",fallback = FeignClientFallBack.class)public interface ResumeFeignClient {@GetMapping("/resume/openstate/{userId}")public Integer findDefaultResumeState(@PathVariable Long userId) ;}同时可以在@FeignClient中增加path属性 , 将方法上的公共路径提取到类中Feign使用上的其他特性Feign请求压缩和响应压缩配置配置属性如下
feign:compression:request:enabled: truemin-request-size: 2048mime-types: text/html,application/xml,application/json # 设置压缩的数据类型response:enabled: true以上配置包含的两个属性 , min-request-size: 2048表示开启压缩的最小值为2048字节 , mime-types为支持压缩的数据类型 , 当前的这几种类型未默认值Feign请求日志配置首先在yml中设置具体的类的日志响应级别
logging: level:# Feign?志只会对?志级别为debug的做出响应 com.lagou.edu.controller.service.ResumeServiceFeignClient:debug然后就是针对feign的log的配置import feign.Logger;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class FeignConfig {@BeanLogger.Level feignLevel(){return Logger.Level.FULL;}}需要注意的是 , 此处引入的是feign.Logger,此处表示的含义是feign将会打印请求的所有信息如下
文章插图
Feign源码简要分析还是依据前文 , 依照启动类注解和spring.factories中配置的自动配置类来进行分析 , 首先我们看@EnableFeignClients注解中的FeignClientsRegistrar的具体内容,实现的依然是Spring中的注入beanDefinition的内容
@Override public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry); }registerDefaultConfiguration注入默认配置我们基本可以确定就是加入一些默认配置 , 而registerFeignClients就是最终实现逻辑的地方 。而最终实现逻辑的地方是在该方法下的private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);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();boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }而这个类都依赖于BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);几乎可以确定注入的时候就是FeignClientFactoryBean这个工厂Bean在起作用 , 那么我们在进入里面看一看,工厂Bean最重要的就是getObject返回的类型情况@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() {FeignContext context = applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);if (!StringUtils.hasText(this.url)) {if (!this.name.startsWith("http")) {url = "http://" + this.name;}else {url = this.name;}url += cleanPath();return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,this.name, url));}if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {this.url = "http://" + this.url;}String url = this.url + cleanPath();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 unwrapclient = ((LoadBalancerFeignClient)client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url)); }在上述代码中 , 基本上就是构造客户端并调用的过程 , 那么最关键的就是实现了Ribbon功能的负载均衡的loadBalance操作中内容 protected <T> T loadBalance(Feign.Builder builder, FeignContext context,HardCodedTarget<T> target) {Client client = getOptional(context, Client.class);if (client != null) {builder.client(client);Targeter targeter = get(context, Targeter.class);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.target这段最后都会执行到feign类中的这个方法中public <T> T target(Target<T> target) {return build().newInstance(target);}关注到newInstance方法发现最终实现时在ReflectiveFeign类中@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)) {DefaultMethodHandler handler = new DefaultMethodHandler(method);defaultMethodHandlers.add(handler);methodToHandler.put(method, handler);} else {methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));}}InvocationHandler handler = factory.create(target, methodToHandler);T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {defaultMethodHandler.bindTo(proxy);}return proxy;}上述可见 , 最终生成的类实际上一个代理类完成了最终的调用 , 而在代理对象就完成了最后的负载均衡等处理,生成代理对象使用的死FeignInvocationHandler的invoke方法static final class Default implements InvocationHandlerFactory {@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);}}最后执行了相关的编解码操作@Overridepublic Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);Retryer retryer = this.retryer.clone();while (true) {try {return executeAndDecode(template);} 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;}}}而执行并解码的操作executeAndDecode中最重要的就是client.execute方法 , 点进去之后发现 , 居然最终调用的就是LoadBalancerFeignClient.execute方法
文章插图
最终在该方法中实现了远程调用和负载均衡
欢迎搜索关注本人与朋友共同开发的微信面经小程序【大厂面试助手】和公众号【微瞰技术】 , 以及总结的分类面试题https://github.com/zhendiao/JavaInterview

文章插图

文章插图
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
