描述
该实现方法是利用了ThreadLocal隔离线程的作用。主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,防止变量被其它线程篡改。需要注意的事,因为ThreadLocal的key基础了WeakReference弱引用,所以在使用完之后,尽量进行手动释放,避免内存泄露的问题。
maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.11.5</version>
<scope>compile</scope>
</dependency>
自定义注解
/**
* 自定义用户注解
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
}
用户实体
/**
* 实体
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@Data
public class UserInfo {
private String userId;
private String username;
}
定义获取用户工具类
/**
* 定义使用ThreadLocal获取当前线程用户的信息
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
public class UserInfoUtil {
/**
* ThreadLocal的作用主要是做数据隔离,
* 填充的数据只属于当前线程,
* 变量的数据对别的线程而言是相对隔离的,
* 在多线程环境下,如何防止自己的变量被其它线程篡改。
*/
private static ThreadLocal<UserInfo> local = new TransmittableThreadLocal<>();
public static void set(UserInfo userInfo) {
local.set(userInfo);
}
public static UserInfo get() {
return local.get();
}
/**
* key被设计成弱引用
* ThreadLocal在没有外部强引用时,发生GC时会被回收
* 线程就会一直持续运行,value就得不到回收,发生内存泄露
*/
public static void remove() {
local.remove();
}
}
配置mvc拦截器
参数解析器
/**
* 创建参数解析器
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@Component
public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 当返回值为true时,调用resolveArgument()方法
*
* @param methodParameter
* @return
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(CurrentUser.class) && UserInfo.class.isAssignableFrom(methodParameter.getParameterType());
}
@Override
public UserInfo resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
assert request != null;
// 获取token,根据业务对token的校验
String token = request.getHeader("token");
// 也可从隔离线程中获取
// UserInfo userInfo = UserInfoUtil.get();
// 模拟解析token,获取用户信息
return userInfo(token);
}
/**
* 模拟数据库用户数据
*
* @return
*/
private UserInfo userInfo(String token) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("张三");
userInfo.setId("123456");
return userInfo;
}
}
定义拦截器
/**
* 定义拦截器
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@Component
public class UserInfoInterceptor implements HandlerInterceptor {
/**
* 预处理回调
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// cookie或者header获取到token
String token = request.getHeader("token");
// 解析用户信息
UserInfo userInfo = userInfo(token);
// 存放到隔离线程
UserInfoUtil.set(userInfo);
// 放行
return true;
}
/**
* 后处理回调,实现处理器后,但在渲染视图前
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 整个请求处理完毕,即在视图渲染完毕回调
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 关闭threadLocal,防止内存泄露
UserInfoUtil.remove();
}
/**
* 模拟数据库用户数据
*
* @return
*/
private UserInfo userInfo(String token) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("张三");
userInfo.setId("123456");
return userInfo;
}
}
配置mvc
/**
* 配置mvc,将定义好的拦截器和参数解析器配置到mvc中
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private UserInfoArgumentResolver userInfoArgumentResolver;
@Autowired
private UserInfoInterceptor userInfoInterceptor;
/**
* 将自定义的参数解析器添加到MVC解析器集合中,使其生效
*
* @param resolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(userInfoArgumentResolver);
}
/**
* 添加拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInfoInterceptor).addPathPatterns("/**");
}
}
实践,在controller获取用户
/**
* 用户管理控制层
*
* @author 苦瓜不苦
* @date 2021/8/4
*/
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/get")
public UserInfo get(@CurrentUser UserInfo userInfo) {
// 通过注解获取用户信息
// 通过ThreadLocal隔离线程获取用户信息
userInfo = UserInfoUtil.get()
return userInfo;
}
}