为了在需求中更好的传递数据,减少重复请求数据库获取数据的操作,先在后端服务的请求中关于拦截器和过滤器有以下的生命周期:
1.Filter Pre (过滤器) chain.doFilter(request, response) 前的逻辑
2.service (Servlet) spring mvc的doService()方法,也是servlet的service()方法
3.dispatcher (SpringMVC) 请求分发
4.preHandle (拦截器) 进入interceptor
5.controller (控制器) 处理业务需求
6.postHandle (拦截器) 在controller逻辑后return ModelAndView前调整ModelAndView的内容
7.afterCompletion (过滤器) 在filter返回前执行
8.Filter After (过滤器) 过滤器执行后
在这整个流程中,其实都处于网络请求的同一个线程,在这个流程中可以在ThreadLocal中共享一些数据库查询的结果。
创建holder类,为它增加一个静态属性ThreadLocal,泛型T设置为map,方便键值存储。对外提供set、get方法,并且在get方法中对holder.get进行初始化:
package app.filter;
import java.util.HashMap;
import java.util.Map;
public class RequestHolder {
private final static ThreadLocal<Map<String, Object>> holder = new ThreadLocal<>();
public static void remove() {
holder.remove();
}
public static void set(String key, Object value) {
getContext().put(key, value);
}
public static Object get(String key) {
return getContext().get(key);
}
static private Map<String, Object> getContext() {
Map map = holder.get();
if (map == null) {
map = new HashMap();
holder.set(map);
}
return map;
}
}
在前面已经实现过interceptor,修改preHandle的代码:
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
private EUserMapper mapper;
static EUser authUser;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (!(handler instanceof HandlerMethod)) {
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
//skip pass token type
if (method.isAnnotationPresent(PassToken.class)) {
if (method.getAnnotation(PassToken.class).required()) {
return true;
}
}
//check the validity of the token
if (method.isAnnotationPresent(RequiredToken.class)) {
if (!method.getAnnotation(RequiredToken.class).required()) {
return true;
}
if (token == null) {
throw new RuntimeException("请先登录");
}
EUser user = getUser(token);
authUser = user;//record
RequestHolder.set("user", user);
}
return true;
}
把验证过的User对象存储在ThreadLocal的holder类中
增加一个新的Controller,给它增加一个测试方法,在里面通过holder获取当前线程的验证过的user对象(因为@RequiredToken这个自定义的注解,通过Interceptor的时候已经进行过用户验证,自定义注解的方法在前一篇),简单把object设置进返回的response中
@RestController
public class ContentController {
@Autowired
private EProjectMapper mapper;
@RequiredToken
@RequestMapping("projects")
public Map<String, Object>projects(@RequestBody Map<String, Object> params) throws Exception {
Map<String, Object> map = ETools.responseMap();
EUser user = (EUser) RequestHolder.get("user");
map.put("data", user);
return map;
}
}
用postman测试结果,发现holder共享的数据已经传到了controller中,不用在controller中再次根据token寻找用户信息了: