SpringMVC拦截器:HandleInterceptor接口

Interceptor拦截器也是SpringMVC中相当重要的功能,他的主要作用是拦截用户的请求并进行相应的处理.比如通过拦截器来进行用户权限验证,或者用来判断用户是否已经登录等.
SpringMVC拦截器是可插拔式设计的.如果需要使用某个拦截器,只需要在配置文件中应用该拦截器即可:如果不需要使用该拦截器,只需要在配置文件中取消应用该拦截器,不管是否应用某个拦截器,对SpringMVC框架不会有任何影响.
HandleInterceptor接口
SpringMVC中的Interceptor拦截器拦截请求是通过实现HandleInterceptor接口来完成的.在SpringMVC中定义一个Interceptor拦截器非常简单,通常在要定义的Interceptor拦截器类中实现Spring的HandlerInterceptor接口中定义了三个方法,SpringMVC就是通过这三个方法来对用户请求进行拦截处理的.
boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle)
顾名思义,该方法在请求处理之前被调用.SpringMVC中的Interceptor实行的是链式调用,即在一个应用中或者说在一个请求中可以同时存在多个Interceptor.每个Interceptor的调用会依据他的声明顺序一次执行,而且最优先执行的是Interceptor中的preHandle方法,所以可以在这个方法中进行一些判断来决定请求是否要继续进行下去.该方法的返回值是Boolean类型的,当返回值为true时就会继续调用下一个Interceptor的preHandle方法;如果已经是最后一个Interceptor,就会调用当前请求的Controller方法.
void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView mv)
该方法和之后的atferCompletion方法都只能在当前所属的Interceptor的preHandle方法的返回值为true时才能被调用.postHandle方法,顾名思义,就是在当前请求被处理之后,也就是Controller方法被调用之后执行,但是他会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作.postHandle方法被调用的方向跟preHandle是相反的,也就是说先声明的Interceptor的postHandle方法反而会后执行,这和Struts2里面的Interceptor的执行过程类似.
void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception exception)
该方法也是在当前所属的Interceptor的preHandle方法的返回值为true时才会执行,顾名思义,该方法将在整个请求之后,也就是在DispatcherServlet渲染了对应视图之后执行.这个方法的主要作用是进行资源清理.
示例:拦截器实现用户权限验证
本小节通过拦截器完成一个用户权限验证的功能.即用户必须登录之后才可以访问网站首页,如果没有登录就直接访问网站首页,这拦截器会拦截请求,并将请求重新转发到登录页面,同时提示用户需要先登录再访问网站.
1.登录表单视图
    <h2>登录</h2>
    
    <h3 style="color:red">${requestScope.message}</h3>
    <form action="/springTest/user/login" method="post">
        
        <input type="text" name="loginname" />
        <input type="text" name="password" />
        
        <input type="submit" value="submit" />
        
    </form>
2.登录控制器
@Controller
@RequestMapping("/user/")
public class UserController
{
    @RequestMapping("/login")
    public ModelAndView login(
            String loginname,
            String password,
            ModelAndView mv,
            HttpSession session
            )
    {
        //模拟数据库根据用户名,密码查找用户
        if(
            loginname != null
            && loginname.equals("fkit")
            && password != null
            && password.equals("123456")
            )
        {
            User user = new User();
            user.setLoginname(loginname);
            user.setPassword(password);
            user.setUsername("管理员");
            //登录成功,将user对象注册到HttpSession作用域
            session.setAttribute("user", user);
            //转发到main请求
            mv.setViewName("redirect:/main");
        } else {
            //登录失败,显示提示信息,并跳转到登录页
            mv.addObject("message","登录名或密码错误,请重新输入!");
            mv.setViewName("loginForm");
        }
        return mv;
    }
    
    @RequestMapping("/loginForm")
    public String loginForm()
    {
        return "loginForm";
    }
    
}
UserController类的login方法用来处理登录请求,本示例没有使用数据库存储数据,只是简单的模拟了用户登录,只要用户输入的登录名是fkit,密码是123456,这验证通过,并创建一个User对象保存到HttpSession当中,同时将请求使用客户端跳转到main请求:如果登录失败,这设置失败提示信息到ModelAndView对象,同时将请求四通客户端跳转到loginForm请求,即登录页面:
3.首页控制器
@Controller
public class MainController
{
    @RequestMapping("/main")
    public String main(
            Model model
            )
    {
        //模拟数据库获得所有图书集合
        List<Book> book_list = new ArrayList<Book>();
        book_list.add(new Book(1,"书名1","作者1"));
        book_list.add(new Book(2,"书名2","作者2"));
        book_list.add(new Book(3,"书名3","作者3"));
        //将图书集合添加到model当中
        model.addAttribute("book_list", book_list);
        return "main";
    }
    
}
BookController类的main方法用来处理网站首页的请求,该方法获得所有图书信息,并将他们设置到Model当中,然后传递到main页面.本示例没有使用数据库存储数据,只是简单的创建了一个集合模拟从数据库获取图书信息.
4.首页视图
    <h2>main</h2>
    
    <h3 style="color:red">当前登录用户:${sessionScope.user.username}</h3>
    <c:forEach items="${requestScope.book_list }" var="book">
        <h3>(${book.id}) ${book.name } -- ${book.author }</h3>
    </c:forEach>
登录后即跳转到此页面:
5.接下来,我们需要设计一个拦截器验证用户是否登录,如果用户没有登录,不可以访问除登录页和登录请求的所有Controller
//拦截器必须实现HandlerInterceptor接口
public class AuthorizationInterceptor implements HandlerInterceptor
{
    //不拦截/loginForm和/login请求
    private static final String[] IGNORE_URI = {"/loginForm","/login"};
    
    /**
     * 该方法将在整个请求完成之后执行,主要作用是用于清理资源
     * 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行
     */
    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            Exception exception
            ) throws Exception
    {
        System.out.println("AuthorizatoinInterceptor afterCompletion -->");
    }
    
    /**
     * 该方法将在Controller方法调用之后执行,方法中可以对ModelAndView进行操作
     * 该方法也只能在当前Interceptor的preHandle方法的返回值为true时才会执行
     */
    @Override
    public void postHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            ModelAndView mv
            ) throws Exception
    {
        System.out.println("AuthorizationInterceptor postHandle");
    }
    
    /**
     * preHandle方法是进行处理器拦截用的,该方法在Controller处理之前进行调用
     * 该方法返回值为true拦截器才会继续往下执行,该方法的返回值为false的时候整个请求就结束了
     */
    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler
            ) throws Exception
    {
        System.out.println("AuthorizationInterceptor preHandle");
        //flag变量用于判断用户是否登录 默认为false
        boolean flag = false;
        //获取请求的路径进行判断
        String servletPath = request.getServletPath();
        //判断请求是否需要拦截
        for(String s : IGNORE_URI)
        {
            if(servletPath.contains(s))
            {
                flag = true;
                break;
            }
        }
        //拦截请求
        if(!flag)
        {
            //获取session中的用户
            User user = (User) request.getSession().getAttribute("user");
            //判断用户是否登录
            if(user == null)
            {
                System.out.println("AuthorizationInterceptor 拦截请求:");
                request.setAttribute("message", "请先登录");
                request.getRequestDispatcher("/user/loginForm").forward(request, response);
            } else {
                flag = true;
            }
        }
        return flag;
    }
}
6.修改配置文件,注册拦截器
<!-- SpringMVC拦截器 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- 拦截所有请求 -->
        <mvc:mapping path="/*" />
        <!-- 使用bean定义一个Interceptor -->
        <bean class="org.fkit.interceptor.AuthorizationInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>
若没有登录这会跳转至登录页