在 spring boot 项目中一般如何存储上下文数据?

2024-07-15 09:22:19 浏览数 (2)

答:可以使用 ThreadLocal 在每个线程的上下文中存储数据,从而在同一线程内的任何地方对这些数据进行访问。

使用 ThreadLocal 可以确保每个线程都拥有独立的 userId 存储,避免了多线程环境下的并发问题。这种方法非常适合在整个请求生命周期中需要访问和使用 userId 的情况。

userId 为例,假如我们的 cookie 中包含 userId 信息。

当然一般的项目中 cookie 中仅存储用于鉴权的 token 令牌信息,不会直接存储 userId

1、创建一个 ThreadLocal 类

首先,创建一个 ThreadLocal 存储类,用于存储 userId

代码语言:javascript复制
public class UserContext {
    private static final ThreadLocal<String> userIdThreadLocal = new ThreadLocal();
    
    public static void setUserId(String userId) {
        userIdThreadLocal.set(userId);
    }
    
    public static String getUserId() {
        return userIdThreadLocal.get();
    }
    
    public static void clear() {
        userIdThreadLocal.remove();
    }
}

2、创建一个拦截器

创建一个拦截器,在请求处理之前解析 Cookie 并设置 userId , 在请求处理完成后清理 ThreadLocal

代码语言:javascript复制
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class UserIdInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = null;

        // 从请求中获取 Cookies
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("userId".equals(cookie.getName())) {
                    userId = cookie.getValue();
                    break;
                }
            }
        }

        // 如果找到了 userId,将其存储到 ThreadLocal 中
        if (userId != null) {
            UserContext.setUserId(userId);
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 清理 ThreadLocal
        UserContext.clear();
    }
}

3、配置拦截器

创建一个配置类来注册拦截器。

代码语言:javascript复制
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private UserIdInterceptor userIdInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userIdInterceptor)
                .addPathPatterns("/**"); // 拦截所有请求
    }
}

4、在需要使用 userId 的地方获取 userId

在需要使用 userId 的地方,通过 UserContext 获取 userId

代码语言:javascript复制
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/userinfo")
    public String getUserInfo() {
        // 从 UserContext 中获取 userId
        String userId = UserContext.getUserId();
        return "User ID is: "   userId;
    }
}

5、使用 AOP (可选)

如果需要在整个应用程序中自动处理 ThreadLocal 的设置和清理,可以使用 Spring AOP 创建一个切面。

代码语言:javascript复制
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class UserIdAspect {

    @Before("execution(* com.example..*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void beforeController() {
        // 从上下文中获取请求和用户信息
        // 设置 UserContext 中的 userId
    }

    @After("execution(* com.example..*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void afterController() {
        // 清理 UserContext 中的 userId
        UserContext.clear();
    }
}

0 人点赞