前言:
此刻小伙伴们对“设置请求头信息的方法”大约比较关怀,姐妹们都想要分析一些“设置请求头信息的方法”的相关知识。那么小编同时在网摘上汇集了一些关于“设置请求头信息的方法””的相关知识,希望同学们能喜欢,大家一起来学习一下吧!前言:
上一篇文章说到被FeignClient注解修饰的接口会被加载成FeignClientFactoryBean类型,在Spring实例化时候会调用其getObject方法最后创建一个接口的代理类,然后封装了一个FeignInvocationHandler类,这个类实现了InvocationHandler,由动态原理可知在请求的时候会调用该对象的invoke方法,下面我会详细分析这个方法到底做了些什么事情。
源码分析:
先看看FeignInvocationHandler类的invoke方法,该方法的代码如下:
@Overridepublic 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);}
dispatch.get(method)得到的类型是SynchronousMethodHandler,回调用其invoke方法,该方法代码如下:
@Overridepublic Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Options options = findOptions(argv); Retryer retryer = this.retryer.clone(); while (true) { try { 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; } }}
首先创建了一个RequestTemplate对象,这个对象包含请求头信息以及请求方法等属性,接着获取一个Retryer对象,它是负载在远程接口不可调用时候进行重试的类,稍后我分析这个类的源码,接着一个while的死循环,try代码块中调用executeAndDecode方法,这个方法代码如下:
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { response = client.execute(request, options); } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) { response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); } if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } if (response.status() >= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null; } else { Object result = decode(response); shouldClose = closeAfterDecode; return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { Object result = decode(response); shouldClose = closeAfterDecode; return result; } else { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } }}
这个方法又调用了targetRequest方法,该方法主要是获取所有的请求拦截器,然后循环遍历调用其apply方法,传入RequestTemplate对象,这个拦截器可以给RequestTemplate对象添加请求头信息,例如我的项目里面加入了一个自定义的拦截器,截图如下:
由我的截图可以看出我添加了一个自定义的请求拦截器,往RequestTemplate对象添加一个头信息,这就是拦截器的作用。
接着在targetRequest方法中创建了一个Request对象,成员属性主要有请求方式是GET/POST、请求的url、头信息、请求体参数,这些参数都来源于RequestTemplate对象。
好了targetRequest方法执行完成以后回到executeAndDecode方法,会调用client.execute(request, options),这个client实现类是LoadBalancerFeignClient,这里面的原理是Ribbon相关的源码,这个已经分析过了,可以看看我的Ribbon源码相关专题,现在我们只考虑返回Response对象以后如何处理的。
接着判断返回的类型是不是Response类型的,如果是直接返回里面的内容,然后判断是否是响应200的响应码,这里假定是200响应码,会调用decode方法进行解码,这个方法调用层次比较深我就不列出代码了,就说一下这里的逻辑是循环调用HttpMessageConverter类,这个类大概有10个截图如下:
这个类是处理我们返回的类比如我们返回的类型是一个Car对象类型,根据返回的媒体类型(text/html,applicaiton/json等)判断是否能处理这个Car类型,下面是我的一个测试Feign接口是这样定义的:
@FeignClient(value = "exercise1")public interface TestFeign { @GetMapping(value = "/testFeign1") @ResponseBody public Car testFeign();}
我的接口提供方是这样定义的:
public class TestFeignController { @RequestMapping("testFeign1") public Car testFeign() { Car car =new Car("tom",11); return car; }}
可以看出接口提供方返回的是json类型,那么response中的媒体类型是application/json,我的Feign接口返回是Car类型,那么根据这两个参数能否判断出哪个HttpMessageConverter能处理吗?经过研究可知MappingJackson2HttpMessageConverter这个类可以处理返回的json数据,他可以把json数据转换为Car对象返回。
好了,decode方法执行完成以后就相当于把远程服务返回的数据封装成为我们Feign接口返回类型的对象,那么正常的逻辑就是执行完成了。
前面所说的都是能正常访问远程服务的情况下,还记得我前面截图的SynchronousMethodHandler的invoke方法中有catch代码块的逻辑吗?假如我们的远程服务挂了,会走到 retryer.continueOrPropagate(e)这段代码,这段代码是一个重试机制,我们看看里面的代码是什么样子的:
public void continueOrPropagate(RetryableException e) { if (attempt++ >= maxAttempts) { throw e; } long interval; if (e.retryAfter() != null) { interval = e.retryAfter().getTime() - currentTimeMillis(); if (interval > maxPeriod) { interval = maxPeriod; } if (interval < 0) { return; } } else { interval = nextMaxInterval(); } try { Thread.sleep(interval); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); throw e; } sleptForMillis += interval;}long nextMaxInterval() { long interval = (long) (period * Math.pow(1.5, attempt - 1)); return interval > maxPeriod ? maxPeriod : interval;}
这段代码的逻辑是:
定义一个attempt重试次数的变量,每次都加1,检查是否达到最大重试次数 maxAttempts,如果没有指定最大重试次数,默认是5次,如果达到最大重试次数,则直接抛出 RetryableException 异常。
计算下一次重试的时间间隔 interval:如果 RetryableException 中有设置 retryAfter,则计算 interval 为 retryAfter 时间减去当前时间的毫秒数。如果计算出的 interval 大于 maxPeriod,则将其截断为 maxPeriod。如果 interval 小于 0,则表示不需要进行重试,直接返回。
如果 RetryableException 中没有设置 retryAfter,则调用 nextMaxInterval() 方法计算默认的时间间隔。
使用 Thread.sleep(interval) 方法暂停当前线程,使得程序等待一段时间后再进行重试。如果在等待过程中被中断,则会抛出 InterruptedException 异常,并继续抛出 RetryableException 异常。
总体来说,这段代码的作用是在发生可重试异常时,判断是否需要进行重试,如果需要,计算下一次重试的时间间隔,并在间隔后进行重试。在重试过程中,如果线程被中断,则会继续抛出 RetryableException 异常。
总结:
以上就是我分析的Spring Cloud OpenFeign源码请求原理解析过程,下面我做个总结:
请求到达时候会调用FeignInvocationHandler的invoke方法,创建一个RequestTemplate对象设置请求头参数等信息,这些可能是在请求拦截器中设置的,接着调用远程服务获取响应对象,把响应对象解码成为我们Feign接口返回的类型,当服务不可用时候还可以进行重试机制,默认最大为5次,如果5次过后还是请求不到会抛出异常。
我是爱编程的程序员老徐,如果大家喜欢我的文章请点赞关注,我会持续更新源码的解析,我们下期见。
标签: #设置请求头信息的方法