java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > feign微服务之间传递请求头数据

feign微服务之间传递请求头数据方式

作者:一定不晚睡啊

这篇文章主要介绍了feign微服务之间传递请求头数据方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

直接在微服务远程调用中获取请求头数据不能直接获取到.为什么? 看源码

默认情况下feign远程调用的时候不会传递请求头!

远程调用源码:

每一次远程请求的时候都创建了一个新的Request Template对象,在该对象中不包含之前的请求头数据

解决方案

方案一

在feign接口上添加对应的形式参数即可

弊端:每一个接口想要获取参数都需要在接口方法上添加对应的形式参数.影响代码效率

方案二

使用OpenFeign中的拦截器(RequestInterceptor)来拦截请求,添加请求头。

方案一演示

FeignClient接口:

@FeignClient(value = "service-cart",fallback = FeignClientFallback.class)//降级方法
public interface FeignClient { 
public Result xxx( 
                  @RequestParam(value = "xx" )Long xx ,
                  @RequestParam(value = "xx" )String xx);
}

Controller远程接口:

@RestController
@RequestMapping("/x/x/x")
public class Controller {

    @GetMapping("/x/x/x")
    public Result aaa(
            @RequestParam(value = "xx" )Long xx ,
            @RequestParam(value = "xx" )String xx
             ) {
        return Result.ok() ;
    }
}

//@RequestParam 通过传参的方法传递过去,并非从请求头上拿
FeignClient.aaa(xx,xx); //远程调用时候传参

方案二演示

思路:在OpenFeign远程调用 定义一个拦截器类实现(RequestInterceptor)接口给RequestTemplate设置上请求头

核心源码

// feign.SynchronousMethodHandler#executeAndDecode
Request targetRequest(RequestTemplate template) {        
    // 在构建目标请求对象的时候,遍历所有的拦截器,
    //   然后调用了apply方法把RequestTemplate作为参数传递过去
    for (RequestInterceptor interceptor : requestInterceptors) {
        interceptor.apply(template);
    }
    return target.apply(template);
}

拦截器类如何获取Controller请求头数据呢?

原理:底层通过RequestContextListener监听器实现

思路1实现:

 //定义一个Map,作用是在tController和FeignClientInterceptor(拦截器)中共享HttpServletRequest
    public static final ConcurrentHashMap<Thread, HttpServletRequest> concurrentHashMap =new ConcurrentHashMap<>();

//给map中存request对象

@GetMapping("/xx")//required:传递过来的参数可以有可以无
    public String addCart(HttpServletRequest request, ) {

        concurrentHashMap.put(Thread.currentThread(),request);
}

拦截器类通过map获取请求头数据,设置给RequestTemplate

@Component
@Slf4j
public class FeignClientInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        log.info("FeignClientInterceptor..........");
        HttpServletRequest httpServletRequest = CartController.concurrentHashMap.get(Thread.currentThread());
            //给requestTemplate 添加上对应的请求头中的数据:aa,bbb
        requestTemplate.header("userId",httpServletRequest.getHeader("aa"));
        requestTemplate.header("userTempId",httpServletRequest.getHeader("bbb"));

    }
}

思路2实现:

在Controller中定义一个ThreadLocal对象

public static final  ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<>();

然后在方法中调用set方法添加数据(request)

threadLocal.set(request);

在拦截器中调用get方法就可以拿到HttpServletRequest对象,然后给requestTemplate 添加上对应的请求头中的数据:aa,bbb

 HttpServletRequest httpServletRequest = CartController.threadLocal.get();

 requestTemplate.header("userId",httpServletRequest.getHeader("aa"));
 requestTemplate.header("userTempId",httpServletRequest.getHeader("bbb"));

思路3实现:使用spring提供的对象RequestContextHolder直接在拦截器类获取request对象

//RequestContextHolder对象中获取一个线程内所共享的HttpServletRequest对象
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
//获取到了request对象剩下的同上

思路3的在Controller使用模块中通过RequestContextHolder隐式获取请求头数据

直接在使用模块获取ServletRequestAttributes对象,不需要在参数添加@RequestHeader(value = "xxx")
因为刚才已经用到了request对象
// 获取ServletRequestAttributes对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest requestAttributesRequest = requestAttributes.getRequest();
        String userId = requestAttributesRequest.getHeader("userId");
        String userTempId = requestAttributesRequest.getHeader("userTempId") ;

代码优化:

1.将从RequestContextHolder对象中读取xx和xx数据的代码封装到一个工具类(utilxx),并使用一个实体类返回相关数据:

public class Utils {

    public static UserInfoVo userInfo() {

        // 获取请求对象
        ServletRequestAttributes requestAttributes 
        = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

        // 判断requestAttributes是否为空
        if(requestAttributes != null) {

            // 获取request对象
            HttpServletRequest request = requestAttributes.getRequest();

            // 从请求对象中获取xx和xx数据
            String userid = request.getHeader("id");
            String username = request.getHeader("name");

            // 封装数据到xxx对象中
            User user = new user() ;
            if(!StringUtils.isEmpty(userId)) {
                userAuthInfoVo.setUserId(Long.parseLong(userId));
            }
            userAuthInfoVo.setUserTempId(username);

            // 返回
            return user ;
        }

        return null ;
    }

}

2.FeignClientInterceptor抽取成公共组件

为了实现FeignClientInterceptor的复用,那么此时可以将FeignClientInterceptor抽取成一个公共的组件

@Slf4j
@Component
public class FeignClientInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        
        log.info("FeignClientInterceptor拦截器执行了....");

        User user = User.getUser();
        if(user != null) {
            Long userId = user.getUserId();
            if(userId != null) {
                template.header("userId" , String.valueOf(userId)) ;
            }
            template.header("username" , userAuthInfo.getUsername()) ;
        }

    }

}

自定义注解:

因为抽取公共类后包路径不一样会导致扫描不到所以,可以使用自定义注解或@Import解决,这里使用自定义注解

@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
@Import(value = EnableFeignClientInterceptor.class)
public @interface EnableFeignClientInterceptor {
}

启动类加上@EnableFeignClientIntercepto即可使用

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文