SpringBoot项目通过自定义注解根据请求头注入用户会话信息_企业级方案

目录

1. 引言

2. 示例:

3. 代码

3.1 注解定义@UserSession

3.2 用户会话信息定义UserSessionInfo 

3.3 处理器方法参数解析器

3.4 SessionThreadLocalUtil

3.5 将处理器参数方法解析器注入进WebMvcConfig

4. 测试

4.1 Controller

4.2 ServiceImpl

4.3 控制台

4.4 数据库表

5. 致谢


1. 引言

前面我们讲了

SpringBoot项目自定义统一响应体设计_附上完整测试代码-CSDN博客

SpringBoot项目企业级全局异常处理-CSDN博客

SpringBoot项目配置国际化-CSDN博客

今天来讲讲如何用自定义注解实现用户会话信息自动化注入

这里并不是写切面类+注解这种传统方式注入会话信息,

而是实现HandlerMethodArgumentResolver , 并配置于

在某个用户在平台上面进行操作的时候, 会存在很多用户会话信息:包括用户id, 用户姓名, 用户邮箱等。

本期将会介绍如何通过自定义注解,并使用ThreadLocal自动根据请求头注入用户会话信息。

看完这篇博客, 宝宝们将会收获:

  1. 如何通过自定义注解注入会话信息
  2. 设计模式:建造者设计模式的实操使用
  3. JUC:会通过ThreadLocal实现企业级实操应用

好处:

一句话:把“从哪来”的横切关注点一次性解决,让业务代码只关心“做什么”。

比如说, 前面的博客在中, 保存订单时候, 需要传入用户的id, 这个是属于会话信息, 实际场景无需传入的:

2. 示例:

在Controller的接口的传参处加上一个注解@UserSession即可:

 @PostMapping("/saveOrder")
    public Result<Boolean> saveOrder(@UserSession UserSessionInfo userSessionInfo, @RequestBody Order order){
        return Result.success(orderService.saveOrder(order));
    }

效果:

前端Header设置:

  public Boolean saveOrder(Order order) {
        UserSessionInfo userSessionInfo = SessionThreadLocalUtil.getCurrentUserInfo();
        System.out.println("当前的UserSessionInfo为 : " + userSessionInfo);
        // 后续逻辑
}

控制台:

3. 代码

3.1 注解定义@UserSession

package com.rehse.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author: Rehse
 * @description: UserSession
 * @date: 2025/8/14
 */
@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserSession {
    boolean required() default true;
}

3.2 用户会话信息定义UserSessionInfo 

里面有一个建造者内部类, 方便快速通过链式注入用户会话信息, 

懂得原理之后, 直接在类上面加一个lombok的@Builder注解即可, 就是自动用来生成建造器的。

package com.rehse.po.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author: Rehse
 * @description: UserSessionInfo 
 * @date: 2025/8/13
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserSessionInfo {
    private Long userId;
    private String userName;
    private Long orgId;
    private String orgName;
    private String email;

    public static UserSessionInfoBuilder builder() {
        return new UserSessionInfoBuilder();
    }

    public static class UserSessionInfoBuilder {
        private Long userId;
        private String userName;
        private Long orgId;
        private String orgName;
        private String email;

        public UserSessionInfoBuilder userId(Long userId) {
            this.userId = userId;
            return this;
        }

        public UserSessionInfoBuilder userName(String userName) {
            this.userName = userName;
            return this;
        }

        public UserSessionInfoBuilder orgId(Long orgId) {
            this.orgId = orgId;
            return this;
        }

        public UserSessionInfoBuilder orgName(String orgName) {
            this.orgName = orgName;
            return this;
        }

        public UserSessionInfoBuilder email(String email) {
            this.email = email;
            return this;
        }

        public UserSessionInfo build() {
            return new UserSessionInfo(this.userId, this.userName, this.orgId, this.orgName, this.email);
        }
    }
}

3.3 处理器方法参数解析器

关键步骤为:

  1. 先通过UserSessionInfo.builder()获取它的内部类:建造者builder。

  2. 获取HttpServlet请求对象。

  3. 后续就是根据请求对象来获取请求头各个参数值, 然后进行各种判断(判空, 判断是否为数字)。

  4. 判断合规, 则通过建造者builder.参数名函数, 设置值。

  5. 最终通过建造者.build()返回UserSessionInfo对象。

  6. 通过ThreadLocal设置UserSessionInfo为线程变量并返回UserSessionInfo。

package com.rehse.interceptor;

import com.rehse.po.dto.UserSessionInfo;
import com.rehse.util.SessionThreadLocalUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;

/**
 * @author: Rehse
 * @description: MyUserSessionResolver
 * @date: 2025/8/13
 */
@Slf4j
@Component
public class MyUserSessionResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(UserSessionInfo.class)
                && parameter.hasParameterAnnotation(com.rehse.annotation.UserSession.class);
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter,
                                  ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest,
                                  WebDataBinderFactory webDataBinderFactory) throws UnsupportedEncodingException {
        return buildByRequest(nativeWebRequest);
    }

    private UserSessionInfo buildByRequest(NativeWebRequest nativeWebRequest) throws UnsupportedEncodingException {
        UserSessionInfo.UserSessionInfoBuilder builder = UserSessionInfo.builder();
        HttpServletRequest httpServletRequest = (HttpServletRequest) nativeWebRequest.getNativeRequest();

        if (StringUtils.isNotBlank(httpServletRequest.getHeader("userId"))) {
            builder.userId(Long.valueOf(httpServletRequest.getHeader("userId")));
        }

        if (StringUtils.isNotBlank(httpServletRequest.getHeader("userName"))) {
            builder.userName(httpServletRequest.getHeader("userName"));
        }

        String orgId = httpServletRequest.getHeader("orgId");
        if (NumberUtils.isDigits(orgId)) {
            builder.orgId(Long.valueOf(orgId));
        }

        if (StringUtils.isNotBlank(httpServletRequest.getHeader("orgName"))) {
            builder.orgName(httpServletRequest.getHeader("orgName"));
        }


        if (StringUtils.isNotBlank(httpServletRequest.getHeader("email"))) {
            builder.email(httpServletRequest.getHeader("email"));
        }

        UserSessionInfo userSessionInfo = builder.build();
        SessionThreadLocalUtil.userSessionInfo.set(userSessionInfo);
        return userSessionInfo;
    }
}

其实现的接口是HandlerMethodArgumentResolver

package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodArgumentResolver {
    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

3.4 SessionThreadLocalUtil

package com.rehse.util;

import com.rehse.exception.BusinessException;
import com.rehse.exception.SystemCommonErrorCode;
import com.rehse.po.dto.UserSessionInfo;

/**
 * @author: Rehse
 * @description: SessionThreadLocalUtil
 * @date: 2025/8/14
 */
public class SessionThreadLocalUtil {
    public static final ThreadLocal<UserSessionInfo> userSessionInfo = new ThreadLocal();

    public SessionThreadLocalUtil() {
    }

    public static UserSessionInfo getCurrentUserInfo() {
        UserSessionInfo userInfo = (UserSessionInfo) userSessionInfo.get();
        if (userInfo == null) {
            throw new BusinessException(SystemCommonErrorCode.UN_ACCESS_USERINFO_FROM_SESSION);
        } else {
            return userInfo;
        }
    }
}

3.5 将处理器参数方法解析器注入进WebMvcConfig

看下面两个方法即可, 上面那个区域解析器是上一篇文章用来实现国际化的。

package com.rehse.config;

import com.rehse.interceptor.MyUserSessionResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.List;

/**
 * @author: Rehse
 * @description: WebMvcConfig
 * @date: 2025/8/13
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    /**
     * 解析请求中的区域设置:从HTTP请求中提取用户的区域设置信息。
     * 设置响应中的区域设置:在响应中设置适当的区域设置,以便返回用户偏好语言的内容。
     * @return LocaleResolver
     */
    @Bean
    public LocaleResolver localeResolver() {
        return new MyLocalResolver();
    }

    // new一个会话信息参数解析器
    @Bean
    public MyUserSessionResolver headerArgumentResolver() {
        return new MyUserSessionResolver();
    }

    // 将会话信息参数解析器添加进参数解析器里面
    // WebMuvConfigurer中也有该同名方法
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(headerArgumentResolver());
    }
}

4. 测试

4.1 Controller

在接口里面添加了注解@UserSession

  @PostMapping("/saveOrder")
    public Result<Boolean> saveOrder(@UserSession UserSessionInfo userSessionInfo, @RequestBody Order order){
        return Result.success(orderService.saveOrder(order));
    }

4.2 ServiceImpl

Order表建表语句, 以及实体类代码等等, 所有的代码都可以在引言提到的前面几篇博客找到。

SpringBoot项目自定义统一响应体设计_附上完整测试代码-CSDN博客

@Override
    public Boolean saveOrder(Order order) {
        UserSessionInfo userSessionInfo = SessionThreadLocalUtil.getCurrentUserInfo();
        System.out.println("当前的UserSessionInfo为 : " + userSessionInfo);
        Order orderNew = new Order();
        BeanUtils.copyProperties(order, orderNew);
        orderNew.setUserId(String.valueOf(userSessionInfo.getUserId()));
        orderNew.setOrderId(String.valueOf(UUID.randomUUID()).replace("-", ""));
        orderNew.setCreateTime(new Date());
        orderNew.setUpdateTime(new Date());
        orderNew.setStatus(0);
        return save(orderNew);
    }

4.3 控制台

4.4 数据库表

业务代码中, 根据userSession自动注入了user_id。

数据库表中也成功注入:

5. 致谢

感谢宝宝们看完这篇博客, 感觉有帮助的话一键三连么么哒!

25届Java小登已入职, 入职后也要继续提升哇,猪咪将会不定期分享学习笔记!

重铸Java荣光, 我辈义不容辞!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值