7.3 拦截器 Interceptor拦截器是SpringMVC中相当重要的功能,它的主要作用是拦截用户的请求并进行相应的处理 。比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录 等。Spring MVC拦截器是可插拔式的设计。如果需要使用某个拦截器,只需要在配置文件中应用该拦截器即可 ;如果不需要使用该拦截器,只需要在配置文件中取消应用该拦截器。不管是否应用某个拦截器,对Spring MVC框架不会有任何影响。
7.3.1 HandlerInterceptor接口 Spring MVC中的Interceptor拦截器拦截请求是通过实现HandlerInterceptor接口来完成的 。在Spring MVC中定义一个Interceptor拦截器非常简单,通常在要定义的Interceptor拦截器类中实现Spring的HandlerInterceptor接口,或者继承抽象类HandlerInterceptorAdapter。HandlerInterceptor接口中定义了三个方法,SpringMVC就是通过这三个方法来对用户的请求进行拦截处理的。
preHandle方法 boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle)。顾名思义,该方法将在请求处理之前被调用 。SpringMVC中的Interceptor实行的是链式调用 ,即在一个应用中或者说在一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的是Interceptor中的preHandle方法,所以可以在这个方法中进行一些前置的初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是boolean类型的,
当返回值为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;
当返回值为true时就会继续调用下一个Interceptor的preHandle方法;
如果已经是最后一个Interceptor,就会调用当前请求的Controller方法。
postHandle方法 void postHandle (HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView mv)。该方法和之后的afterCompletion方法都只能在当前所属的Interceptor的preHandle方法的返回值为true时才能被调用。
postHandle方法,顾名思义,就是在当前请求被处理之后,也就是在Controller的请求处理方法被调用之后执行 ,但是它会在DispatcherServlet进行视图返回渲染之前被调用 ,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作。postHandle方法被调用的方向跟preHandle是相反的,也就是说先声明的Interceptor的postHandle方法反而会后执行,这和Struts2里面的Interceptor的执行过程类似。
afterCompletion方法 void afterCompletion(HttpServletRequest request,HttpServletResponsere sponse,Object handler,Exception exception) 该方法也是在当前所属的Interceptor的preHandle方法的返回值为true时才会执行。
顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行 。这个方法的主要作用是进行资源清理 。
示例 拦截器实现用户权限验证 本小节通过拦截器完成一个用户权限验证的功能。即用户必须登录之后才可以访问网站首页 ,如果没有登录就直接访问网站首页,则拦截器会拦截请求,并将请求重新转发到登录页面,同时提示用户需要先登录再访问网站。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Controller public class FormController { @GetMapping (value = "/loginForm" ) public String loginForm () { System.out.println("请求处理方法 loginForm" ); return "loginForm" ; } }
登录界面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <form action="login" method="post" > <!-- 提示信息 --> <font color="red">${requestScope.message }</font> <table> <tr> <td><label>登录名: </label></td> <td><input type="text" id="loginname" name="loginname"></td> </tr> <tr> <td><label>密码: </label></td> <td><input type="password" id="password" name="password"></td> </tr> <tr> <td><input type="submit" value="登录"></td> </tr> </table> </form>
UserController 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 @Controller public class UserController { @PostMapping (value = "/login" ) public ModelAndView login (String loginname, String password, ModelAndView mv, HttpSession session) { System.out.println("请求处理方法 login" ); if (loginname != null && loginname.equals("xiaoming" ) && password != null && password.equals("xiaoming" )) { User user = new User(); user.setLoginname(loginname); user.setPassword(password); user.setUsername("管理员" ); session.setAttribute("user" , user); mv.setViewName("redirect:main" ); System.out.println(" |----->登陆验证通过,转发到main请求" ); } else { mv.addObject("message" , "登录名或密码错误,请重新输入!" ); mv.setViewName("loginForm" ); System.out.println(" |----->登陆验证失败,转发到loginForm请求" ); } return mv; } }
UserController类的login方法用来处理登录请求,本示例没有使用数据库存储数据,只是简单地模拟了用户登录,只要用户输入的登录名是”xiaoming“,密码是”xiaoming”,则验证通过。
如果验证通过,创建一个User对象保存到HttpSession当中,同时将请求使用客户端跳转到main请求;
如果验证失败,则设置失败提示信息到ModelAndView对象,同时将请求使用客户端跳转到loginForm请求,即跳回登录页面。
BookController BookController类的main方法用来处理网站首页的请求,该方法获得所有图书信息,并将它们设置到Model当中,然后传递到main页面。本示例没有使用数据库存储数据,只是简单地创建了一个集合模拟从数据库获取图书信息。
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 @Controller public class BookController { @RequestMapping (value = "/main" ) public String main (Model model) { System.out.println("请求处理方法 main" ); List<Book> book_list = new ArrayList<Book>(); book_list.add(new Book("java.jpg" , "疯狂Java讲义(附光盘)" , "李刚 编著" , 74.2 )); book_list.add(new Book("ee.jpg" , "轻量级Java EE企业应用实战" , "李刚 编著" , 59.2 )); book_list.add( new Book("android.jpg" , "疯狂Android讲义(附光盘)" , "李刚 编著" , 60.6 )); book_list.add(new Book("ajax.jpg" , "疯狂Ajax讲义(附光盘)" , "李刚 编著" , 66.6 )); model.addAttribute("book_list" , book_list); return "main" ; } }
main.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <table border="1" > <tr> <th>封面</th> <th>书名</th> <th>作者</th> <th>价格</th> </tr> <c:forEach items="${requestScope.book_list }" var ="book" > <tr> <td><img src="images/${book.image }" height="60"></td> <td>${book.name }</td> <td>${book.author }</td> <td>${book.price }</td> </tr> </c:forEach> </table>
拦截器 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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 public class AuthorizationInterceptor implements HandlerInterceptor { private static final String[] IGNORE_URI = {"/loginForm" , "/login" }; @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception { System.out.println("拦截器的 afterCompletion方法\n-----------------------------" ); } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception { System.out.println("拦截器的 postHandle方法" ); } @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器的 preHandle方法" ); boolean flag = false ; String servletPath = request.getServletPath(); for (String s : IGNORE_URI) { if (servletPath.contains(s)) { flag = true ; break ; } } if (!flag) { User user = (User) request.getSession().getAttribute("user" ); if (user == null ) { System.out.println(" |----验证不通过,重新登录" ); request.setAttribute("message" , "请先登录再访问网站" ); request.getRequestDispatcher("loginForm" ).forward(request, response); } else { System.out.println(" |----验证通过,放行/main请求" ); flag = true ; } } return flag; } }
测试 1 <a href ="main" > 直接请求/main</a >
如果没有登录,直接访问main请求,拦截器会拦截请求,验证用户是否登录,此时用户若没有登录,则跳转到登录页面,如下图所示: 从控制台的输出可以看到各个方法的执行顺序,如下所示.
1 2 3 4 5 6 7 拦截器的 preHandle方法 |----验证不通过,重新登录 拦截器的 preHandle方法 请求处理方法 loginForm 拦截器的 postHandle方法 拦截器的 afterCompletion方法 -----------------------------
此时填写正确的用户名xiaoming和密码xiaoming,点击登录 此时才可以正确得到main请求的资源,如下图所示: 控制台新增内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 拦截器的 preHandle方法 请求处理方法 login |----->登陆验证通过,转发到main请求 拦截器的 postHandle方法 拦截器的 afterCompletion方法 ----------------------------- 拦截器的 preHandle方法 |----验证通过,放行/main请求 请求处理方法 main 拦截器的 postHandle方法 拦截器的 afterCompletion方法 -----------------------------
可以看到执行顺序是,表单提交到login请求,login方法验证成功后,转发到main请求,当请求main时,拦截器的preHandle方法先验证是否登录过,现在的情况是已经登录了,preHandle方法放行main请求,main方法得到执行.
原文链接: 7.3 拦截器