目录
3.5 将处理器参数方法解析器注入进WebMvcConfig
1. 引言
前面我们讲了
SpringBoot项目自定义统一响应体设计_附上完整测试代码-CSDN博客
今天来讲讲如何用自定义注解实现用户会话信息自动化注入。
这里并不是写切面类+注解这种传统方式注入会话信息,
而是实现HandlerMethodArgumentResolver , 并配置于
在某个用户在平台上面进行操作的时候, 会存在很多用户会话信息:包括用户id, 用户姓名, 用户邮箱等。
本期将会介绍如何通过自定义注解,并使用ThreadLocal自动根据请求头注入用户会话信息。
看完这篇博客, 宝宝们将会收获:
- 如何通过自定义注解注入会话信息
- 设计模式:建造者设计模式的实操使用
- 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 处理器方法参数解析器
关键步骤为:
-
先通过UserSessionInfo.builder()获取它的内部类:建造者builder。
-
获取HttpServlet请求对象。
-
后续就是根据请求对象来获取请求头各个参数值, 然后进行各种判断(判空, 判断是否为数字)。
-
判断合规, 则通过建造者builder.参数名函数, 设置值。
-
最终通过建造者.build()返回UserSessionInfo对象。
-
通过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荣光, 我辈义不容辞!