数据(上下文)传递问题

数据传递问题

0、前言

​  最近在开发的一个操作信息记录的接口,遇到了两个数据传递的问题,特此记录。

​  问题产生:该接口是一个异步接口且调用方和被调用方不在同一个服务内,在进行数据库保存操作时,出现了通用字段(创建人,修改人)未被正确赋值的问题。

​  初步排查,该问题的产生点有两个,1:服务间调用使用 OpenFeign,而 OpenFeign 在进行远程调用时会发起一个新的 http 请求,导致请求头中的信息丢失,2:异步接口是通过线程池的方式进行,而子线程通常情况不会持有父线程的上下文内容。

​  注:本文须进行传递的数据为 Cookie,进一步讲为存储在 LocalThread 的用户信息,该信息由 Cookie 解析而来。


1、OpenFeign 数据传递问题

​  处理这个问题相对简单,只需在新的请求中加入请求头信息即可。

    @Bean
    public RequestInterceptor requestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Cookie tokenCookie = CookieUtils.getCookie(ACCESS_TOKEN);
                if (tokenCookie != null) {
                    String cookie = String.format("%s=%s", ACCESS_TOKEN, tokenCookie.getValue());
                    requestTemplate.header("Cookie", cookie);
                }
            }
        };
    }

2、父子线程数据传递问题

​  该问题比较棘手,笔者尝试过许多种方式,最终决定使用 Alibaba 的 TransmittableThreadLocal 来解决问题,即将存储用户信息的 LocalThread 换为 TransmittableThreadLocal 。

// 前
private static final ThreadLocal<UserInfo> SYS_USER_THREAD_LOCAL = new ThreadLocal<>();
// 后
private static final TransmittableThreadLocal<UserInfo> SYS_USER_THREAD_LOCAL = new TransmittableThreadLocal<>();

​  以下篇幅则记录笔者的尝试方案。

​  大前提:笔者不希望存储用户信息的工具类直接对外提供 set 方法来设置用户信息。

​  第一种思路:类似于解决第一类问题,直接将 Cookie 信息传递到子线程,该尝试使用了线程池的任务装饰器(PS:线程池提供了 setTaskDecorator 方法,允许用户重新设置装饰器)。

​  该方法存在一个致命问题,即 RequestAttributes 会在父线程返回时清空,而此方法又是浅拷贝,子线程获取到信息又会重新丢失,并且 RequestAttributes 并没有实现序列化接口(Serializable)和可克隆接口(Cloneable),这又大大增加了重写深拷贝方法的复杂度,因而不使用。

    /**
     * 上下文拷贝装饰器
     */
    static class ContextCopyingDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            // 在此处获取你需要的线程上下文信息
            RequestAttributes context = RequestContextHolder.currentRequestAttributes();
            return () -> {
                // 在新的线程中设置线程上下文信息
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        }
    }

​  第二种思路,即将不能线程共享的 LocalThread 换成可以"线程共享"的 TransmittableThreadLocal,该方法成功解决了此问题(PS:TransmittableThreadLocal 是 Alibaba 开源的一个 Java 库,它是对 Java 的 InheritableThreadLocal 类的增强版本,主要用于在使用线程池等会复用线程的场景下,传递线程本地变量)。

​  补充:可能会有学者想到使用 InheritableThreadLocal 实现上下文信息在父子线程的传递,但该选择有个问题,即这里使用的是线程池,线程池会复用线程,而 InheritableThreadLocal 实现上下文传递的触发点在于父线程创建子线程时(PS:上下文会通过继承的方式,从父线程传递到子线程)。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Security的上下文是指在应用程序中管理和传递安全信息的机制。在Spring Security中,有几种策略可以用于管理安全上下文。 一种常用的策略是MODE_THREADLOCAL,它是Spring Security默认的上下文管理策略。使用这种策略,Spring Security使用ThreadLocal来管理上下文。ThreadLocal确保每个线程只能访问自己线程中的ThreadLocal中的数据,其他线程无法访问。在身份验证过程结束后,可以使用静态的SecurityContextHolder.getContext()方法从持有者请求安全上下文。从安全上下文中,可以进一步获取Authentication对象,该对象存储着已验证实体的详细信息。例如,可以通过以下代码获取已认证用户的用户名: @GetMapping("/hello") public String hello(){ SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); return "Hello," + authentication.getName() + "!"; } 另一种策略是DelegatingSecurityContextCallable,它可以将安全上下文传递给新线程。通过使用DelegatingSecurityContextCallable装饰任务,可以确保新线程也能够访问安全上下文。 总之,Spring Security提供了多种策略来管理和传递安全上下文,以确保安全信息在应用程序中得到正确处理和传递。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *2* *3* [SpringSecurity(八)--SecurityContext安全上下文](https://blog.csdn.net/qq_44754515/article/details/127406789)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值