1.先提出问题的解决方案 通过实现Feign的RequestInterceptor(拦截器)接口,重写里面的apply方法,为RequestTemplate添加自定义的请求头的参数及数据的传入。请求最开始进来的时候,就会进入到这个拦截器中,业务代码中进行远程调用的时候还会进入到这个请求里面来。
- @Component
- public class FeignClientsExtendInterceptor implements RequestInterceptor {
- @Override
- public void apply(RequestTemplate requestTemplate) {
- // 此种方式是线程安全的
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
- .getRequestAttributes();
- // 不为空时取出请求中的header 原封不动的设置到feign请求中
- if (null != attributes) {
- HttpServletRequest request = attributes.getRequest();
- if (null != request) {
- // 遍历设置 也可从request取出整个Header 写到RequestTemplate 中
- Enumeration
headerNames = request.getHeaderNames(); - if (headerNames != null) {
- while (headerNames.hasMoreElements()) {
- String name = headerNames.nextElement();
- String values = request.getHeader(name);
- requestTemplate.header(name, values);
- }
- }
- }
- }
-
- // 处理请求体参数信息
- // feign调用get接口的时候,会报出405 Request method ‘POST‘ not supported的错误
- // 当你不需要将请求体内的信息也传输过去的,可以通过注释处理请求体信息的代码来解决
- // 原因是feign底层发请求的时候,发现请求体带有参数会自动转成post请求。
-
- /*
- Enumeration
bodyNames = request.getParameterNames(); - StringBuilder body = new StringBuilder();
- if (bodyNames != null) {
- while (bodyNames.hasMoreElements()) {
- String name = bodyNames.nextElement();
- String values = request.getParameter(name);
- body.append(name).append("=").append(values).append("&");
- }
- }
- if (body.length() != 0) {
- body.deleteCharAt(body.length() - 1);
- requestTemplate.body(body.toString());
- }
- */
-
- }
- }
2.业务场景(保证后面的请求中,调用者的信息一致)
(1)消费了MQ消息,发起了远程调用。
也就是这个时候是没有请求相关的信息的,但是在feign发起请求的时候,需要保证请求头里的信息是当前线程的相关信息,这个时候可以把需要的相关信息放入一个线程副本中去,通过继承RequestContextHolder或者重写一个相似的类,将当前线程的信息放置进去,然后在feign发起远程调用的时候,将所需信息取出来,放入请求头中。
(2)同一线程
前端请求时,会在请求头中带上一系列的校验参数,调用我的服务,需要再去调用其他服务。在其他服务中, 需要对请求头中的信息做提取或者校验。
feign远程调用的时候,底层是对参数进行了封装然后进行请求,是一次新的请求,那么前端传入请求中的头文件信息自然不会被传过去,所以可以通过重写RequestInterceptor,将请求头的信息原封不动的复制进去。
(3)多线程异步调用其他微服务
上述重写的代码可能就获取不到主线程的请求信息了,主要原因是:RequestContextHolder来获取request信息,发现异步调用时,主线程结束后,子线程就获取不到request,会报出错误信息。
解决方式1:
通过获取到主线程的请求,在前端调用的时候,前端请求会进来,这个时候开启子线程共享,那么在后面的业务执行时,发现异步调用时,主线程结束后,子线程是可以获取request的相关信息的。
- ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
- // 在上面代码后面开启子线程共享
- RequestContextHolder.setRequestAttributes(servletRequestAttributes,true);//设置子线程共享
解决方式2:
与(2)同一线程的处理方式类似,将主线程的request和session信息放进ThreadLocal里,透传到子线程再去获取。