- 拦截器示例
- 定义拦截器,实现HandlerInterceptor
- 配置拦截器,为它指定拦截、排除的路径
- 拦截器应用
- 在请求开始时查询登录用户
- 在本次请求中持有用户数据
- 在模板视图上显示用户数据
- 在请求结束时清理用户数据
拦截器示例
当用户登录过后,之后的请求都应该以登录态去访问,也就是每次带上ticket,例如网站首页,登录和未登录的显示应该不同,如果我们按照正常逻辑,每个请求都得判断登录态,处理相关逻辑。而使用拦截器,则可以拦截浏览器的请求,再对齐进行统一的处理。
定义拦截器
首先再controller包下新建一个Interceptor包,在包下新建一个AlphaInterceptor做演示。
首先实现HandlerInterceptor接口,我们可以ctrl加鼠标左键查看HandlerInterceptor类。可以看到主要有三个方法。
![]()
我们重写这三个方法,并记录日志来检查是否正确拦截。preHandle在controller之前执行,postHandle在controller之后,afterCompletion在模板引擎之后。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.neu.langsam.community.controller.interceptor;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
@Component public class AlphaInterceptor implements HandlerInterceptor {
private static final Logger logger= LoggerFactory.getLogger(AlphaInterceptor.class);
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.debug("preHandle: "+handler.toString()); return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { logger.debug("postHandle: "+handler.toString());
}
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { logger.debug("afterCompletion: "+handler.toString()); } }
|
配置拦截器
定义好拦截器过后,我们还需要一个配置类对它进行配置。在config包下新建WebMvcConfig类,并实现WebMvcConfigurer接口。
注入拦截器,重写添加拦截器方法。
- .excludePathPatterns把静态资源排除在外,不进行拦截
- .addPathPatterns添加拦截路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.neu.langsam.community.config;
import com.neu.langsam.community.controller.interceptor.AlphaInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private AlphaInterceptor alphaInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(alphaInterceptor) .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg") .addPathPatterns("/register","/login"); } }
|
然后启动项目,查看拦截是否成功。可以看到控制台输出的日志。
![]()
拦截器应用
用户登录后拿到ticket存到cookie,之后每次请求都带上ticket,拦截器拦截请求,用ticket查询login_ticket,再查询user,将user添加到model中,模板引擎根据有无user及user的值去渲染页面。
![]()
封装两个小工具
我们要从cookie中拿到ticket的值,先封装一个cookieUtil。
比较简单,直接写静态方法,不交给容器管理。传入request和我们想要的cookie的值。首先判断空值的情况,然后遍历cookie,找到我们想要的那个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.neu.langsam.community.util;
import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest;
public class CookieUtil { public static String getValue(HttpServletRequest request,String name){ if(request==null || name==null){ throw new IllegalArgumentException("参数为空!"); }
Cookie[] cookies =request.getCookies(); if (cookies!=null){ for(Cookie cookie:cookies){ if (cookie.getName().equals(name)){ return cookie.getValue(); } } } return null; }
}
|
浏览器同时处理多个用户请求,对每个用户的user对象要做一个存储,可以使用session,但是session依赖servlet api,我们想要在方法里随用随取,anywhere!为了解决这个问题,我们就要采取一种新的方法来存储用户信息——ThreadLocal。
ThreadLocal,顾名思义,就是本地线程,可是这个名字实在容易让人误解,因为其实它是本地线程局部变量的意思,首先我们要知道,我们每个请求都会对应一个线程,这个ThreadLocal就是这个线程使用过程中的一个变量,该变量为其所属线程所有,各个线程互不影响。
我们封装一个工具HostHolder,用来持有用户信息,代替session对象。主要有添加查询和删除方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package com.neu.langsam.community.util;
import com.neu.langsam.community.entity.User; import org.springframework.stereotype.Component;
@Component public class HostHolder {
private ThreadLocal users=new ThreadLocal<>();
public void setUsers(User user){ users.set(user); }
public User getUser(){ return users.get(); }
public void clear(){ users.remove(); }
}
|
在请求开始时查询登录用户,在本次请求中持有用户数据
做好准备工作,开始正式写登录拦截器,在Interceptor包下新建LoginTicketInterceptor。
首先重写preHandler方法,利用前面封装的cookieUtil工具得到ticket,查到用户,并用hostHolder持有用户。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.debug("preHandle: "+handler.toString()); String ticket= CookieUtil.getValue(request,"ticket"); if(ticket!=null){ LoginTicket loginTicket=userService.findLoginTicket(ticket); if(loginTicket!=null && loginTicket.getStatus()==0 && loginTicket.getExpired().after(new Date())){ User user =userService.findUserById(loginTicket.getUserId()); hostHolder.setUsers(user); } }
return true; }
|
在模板视图上显示用户数据
然后重写postHandle方法,在modelAndView上加上user。
1 2 3 4 5 6 7 8
| @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { logger.debug("postHandle: "+handler.toString()); User user=hostHolder.getUser(); if (user!=null&&modelAndView!=null){ modelAndView.addObject("loginUser",user); } }
|
在请求结束时清理用户数据
请求结束后,清理掉不需要的用户数据。
1 2 3 4 5
| @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { logger.debug("afterHandle: "+handler.toString()); hostHolder.clear(); }
|
配置
添加这个拦截器,因为拦截所有路径,就不加addPathPatterns了。
1 2 3 4 5 6
| @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginTicketInterceptor) .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg");
}
|
启动项目,访问首页,登录
![]()
![]()