问题背景
每个页面头部都要显示用户头像。
如果用户没有登录,页面最上方显示的是登录按钮;如果用户登录,显示的是头像、消息等按钮。
根据是否登录,调整页面内容。
拦截器的好处
如果每个页面都调用相同的方法来显示用户信息,耦合度高。
利用Spring拦截器来解决问题:拦截浏览器访问请求,在请求的开始和结束部分插入,批量解决多个请求共有的业务,低耦合度。
拦截器的示例
定义拦截器
实现 HandlerInterceptor。
有三个方法:请求前执行、请求后执行、模版引擎执行后执行。
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
|
@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, ModelAndView modelAndView) throws Exception { logger.debug("postHandle: " + handler.toString()); }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logger.debug("afterCompletion: " + handler.toString()); } }
|
配置拦截器
为它指定拦截、排除的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@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"); } }
|
拦截器的应用
示意图

1 2 3 4
| - 在请求开始时查询登录用户 - 在本次请求中持有用户数据 - 在模板视图上显示用户数据 - 在请求结束时清理用户数据
|
工具类
CookieUtil: 处理每次请求得到 Cookie 中的 Ticket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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; } }
|
HostHolder: 由于服务器是多线程环境,如果简单的将 User 信息存入一个容器中,很有可能产生冲突。此时用到了线程私有的 ThreadLocal。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@Component public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<User>();
public void setUser(User user) { users.set(user); }
public User getUser() { return users.get(); }
public void clear() { users.remove(); } }
|
定义拦截器
登录的拦截器
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 39 40 41 42 43 44 45 46
| @Component public class LoginTicketInterceptor implements HandlerInterceptor {
@Autowired private UserService userService;
@Autowired private HostHolder hostHolder;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 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.setUser(user); } }
return true; }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { User user = hostHolder.getUser(); if(user != null && modelAndView != null) { modelAndView.addObject("loginUser", user); } }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { hostHolder.clear(); } }
|
配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Configuration public class WebMvcConfig implements WebMvcConfigurer {
@Autowired private AlphaInterceptor alphaInterceptor;
@Autowired private LoginTicketInterceptor loginTicketInterceptor;
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(alphaInterceptor) .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg") .addPathPatterns("/register", "/login");
registry.addInterceptor(loginTicketInterceptor) .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); }
|
页面
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 39 40 41 42
|
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item ml-3 btn-group-vertical"> <a class="nav-link" th:href="@{/index}">首页</a> </li> <li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser!=null}"> <a class="nav-link position-relative" th:href="@{/letter/list}">消息 <span class="badge badge-danger" th:text="${allUnreadCount!=0?allUnreadCount:''}">12 </span> </a> </li> <li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}"> <a class="nav-link" th:href="@{/register}">注册</a> </li> <li class="nav-item ml-3 btn-group-vertical" th:if="${loginUser==null}"> <a class="nav-link" th:href="@{/login}">登录</a> </li> <li class="nav-item ml-3 btn-group-vertical dropdown" th:if="${loginUser!=null}"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <img th:src="${loginUser.headerUrl}" class="rounded-circle" style="width:30px;"/> </a> <div class="dropdown-menu" aria-labelledby="navbarDropdown"> <a class="dropdown-item text-center" th:href="@{|/user/profile/${loginUser.id}|}">个人主页</a> <a class="dropdown-item text-center" th:href="@{/user/setting}">账号设置</a> <a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a> <div class="dropdown-divider"></div> <span class="dropdown-item text-center text-secondary" th:utext="${loginUser.username}">nowcoder</span> </div> </li> </ul> <form class="form-inline my-2 my-lg-0" method="get" th:action="@{/search}"> <input class="form-control mr-sm-2" type="search" aria-label="Search" name="keyword" th:value="${keyword}"/> <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button> </form> </div>
|