在上篇 011 Spring Boot 在拦截器中操作数据库 中,我们在拦截器中解析了 token 并进行了数据库的操作来验证用户是否存在。
此外,还有这样一种需求:我们已在拦截器中通过连接数据库,获取了用户信息,我们能否把用户信息共享给 controller、service 等其他层来使用?
因为在每个层里面都可能会用到该用户信息,如果每次用的时候,都去操作数据库,这未免有些资源的浪费,所以既然在拦截器中已做了查询用户信息的这种,那这个数据是否可以共享给其他模块使用?
为了实现这种操作,我们应用到了 spring boot 的上下文:
1. 上下文简介
顾名思义,上下文是存放了当前进程/线程当前栈的数据。
SpringBoot在响应一个请求时,会创建一个线程,而实现上下文的管理,可以通过创建线程本地变量来实现,这样的话,多个请求的响应之间就不会发生上下文的冲突。
管理的线程变量的方式有很多,比较常见的有:synchronized(锁)
ThreadLocal
这里我们使用的是ThreadLocal,因为synchronized(锁)是采用时间换空间的方式,多个线程(请求响应)之间共用一份变量,当一个线程访问时,其他线程进行等待,这样不仅会造成多线程的效率下降,而且一个线程改变了变量的值,其他线程读取到的就是改变后的值。
而ThreadLocal是为每一个线程提供了变量副本,每个线程使用自己的副本,无论对变量进行什么操作,都不会影响其他线程的变量。
所以我们的上下文管理的实现方式就是,将上下文注册到ThreadLocal中,通过我们的工具类进行赋值和读取。
关于 ThreadLocal
2. 上下文的应用
2.1 新建上下文接口类
package org.example.frame.context;
import java.io.Serializable;
import java.util.Map;
public interface IBaseContext<T> extends Serializable {
T getCurrentUser();
void setCurrentUser(T currentUser);
Map<Object, Object> getProperties();
public void setProperties(Map<Object, Object> properties);
}
2.2 实现上下文接口
package org.example.frame.controller;
import org.example.entity.User;
import org.example.frame.context.IBaseContext;
import java.util.HashMap;
import java.util.Map;
public class JDZContext implements IBaseContext<User> {
private User currentUser;
private Map<Object, Object> properties = new HashMap<>();
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User currentUser) {
this.currentUser = currentUser;
}
public Map<Object, Object> getProperties() {
return properties;
}
public void setProperties(Map<Object, Object> properties) {
this.properties = properties;
}
}
2.3 新建上下文的管理
package org.example.frame.context;
public class JDZBaseContextHolder {
private static final ThreadLocal<JDZContext> contextHolder = new ThreadLocal<>();
public JDZBaseContextHolder() {
}
public static void setContext(JDZContext context) {
contextHolder.set(context);
}
public static JDZContext getContext() {
JDZContext obj = contextHolder.get();
if(obj == null) {
obj = new JDZContext();
setContext(obj);
}
return obj;
}
}
2.4 调用
这时就可以在拦截器中把获取的用户信息放入到上下文环境中:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("==========preHandle================");
// 从 http header 中获取 token
String token = request.getHeader("x-token");
if(token == null) { // 判断是否有 token 如果没有则抛出异常,终止运行
throw new JDZException(RtCode.ERR_NEED_TOKEN);
}
// 解析 token
TokenBuilder tokenBuilder = new TokenBuilder();
RtCode rtCode = tokenBuilder.parse(token);
if(rtCode != RtCode.SUCCESS) { // token 解析失败
throw new JDZException(rtCode);
}
// token 解析成功,继续往下执行
User user = userService.getInfo(tokenBuilder.getUid());
if(user == null) { // 用户不在数据库中
throw new JDZException(RtCode.ERR_USER_NOT_EXIST);
}
// 放入上下文
JDZContext context = new JDZContext();
context.setCurrentUser(user);
JDZBaseContextHolder.setContext(context);
return true;
}
为了避免在使用 ThreadLocal 时内存泄露,我们还需要手动释放该变量:
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("==========afterCompletion================");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
// 删除 ThreadLocal 内容
JDZBaseContextHolder.removeContext();
}
在 controller 类中获取上下文用户信息:
@RequestMapping("/user/info")
public User info(Integer uid) {
JDZContext jdzContext = JDZBaseContextHolder.getContext();
User user = jdzContext.getCurrentUser();
System.out.println(user);
return userService.getInfo(user.getUid()); // uid
}
源码:
012 Spring boot 上下文context – 闲鹤
fthqifowrb http://www.ge971708s1rr6e1jnkj82e454y1vuf9qs.org/
afthqifowrb
[url=http://www.ge971708s1rr6e1jnkj82e454y1vuf9qs.org/]ufthqifowrb[/url]