对Spring MVC拦截器的理解

在平常练手的项目中,对于用户认证以及用户权限管理往往都是通过SpringMVC 拦截器以及其他手段进行处理,然而当项目规模变大,系统安全性要求增加的时候,基于SpringMVC 拦截器等实现的用户认证功能已经不能满足系统需求。常见的手段为利用Spring Security、Apache Shiro 等常见安全框架进行替换。本文首先介绍一下SpringMVC 拦截器的相关知识。

1.Spring MVC

Spring MVC是SSM中的一件套,它是由Spring提供的一个Web框架,借助于注解,使得控制器(Controller)的开发与测试更加简单。Spring MVC通常由以下几个部分构成:DispatcherServlet(核心)、HandlerMapping、controller、ViewResolver等。

1.1 运行原理

  1. 客户端(浏览器)将请求(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)直接发送至DispatcherServlet
  2. DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的Handler(Controller)。
  3. DispatcherServlet将请求提交至对应的Handler(其实就是Controller),开始由HandlerAdapter 适配器处理
  4. Controller调用业务逻辑对用户的请求进行处理
  5. 处理完成后返回一个ModelAndView对象(包含了数据模型以及相应的视图的信息)给DispatcherServlet。
  6. ModelAndView中的视图是逻辑视图,DispatcherServlet借助ViewResolver(视图解析器)完成真实视图对象的解析
  7. 当得到真实的视图对象后,DispatcherServlet利用真实视图对ModelAndView中的Mode数据对象进行渲染。
  8. 把View返回给浏览器。

2.Spring MVC拦截器

2.1 概述

Spring MVC中的Interceptor拦截器机制主要用于拦截用户的请求并做出相应的处理,如下图所示,可以发现Interceptor位于DispatcherServlet以及Controller之间,所以能够用于拦截对Controller层的相关请求。Interceptor拦截器常用于用户权限认证以及判断用户是否登录等场景。

2.2 拦截器的两种实现方式

2.2.1 通过实现HandlerInterceptor接口

(1)拦截器的实现

在SpringMVC中,拦截器的实现一般都是通过实现HandlerInterceptor接口,并重写该接口中的3个方法:preHandle()postHandle()afterCompletion()

public interface HandlerInterceptor {

    boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;

    void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
}
preHandle

preHandle拦截器作用于用户请求到达Controller之前,如果需要对用户的请求做预处理,可以选择在该方法中完成。3种方法中唯一带有返回值的方法:

  • 如果返回值为true,则继续执行之后的拦截器或者Controller
  • 如果返回值为false,则不再执行后面的拦截器和Controller
postHandle

执行完Controller之后,利用model渲染真实视图之前,作用场景为需要对响应的相关数据进处理。

afterCompletion

调用完Controller接口,渲染View页面后调用,同时如果prehandle方法的返回值为true,则也会执行该方法。

(2)拦截器的配置

实现拦截器之后,需要对拦截器进行配置,默认情况下拦截器将会拦截所有请求,包括静态资源等等,而静态资源(图片、css、js等)的请求一般是不需要拦截的。同时也可以自定义需要拦截的请求。

2.2.2 使用自定义注解实现拦截器

(1)自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 自定义注解需要用元注解标记
// 四种常见的元注解@Target(自定义注解的作用类型,例如类、方法、属性等) 
// @Retention(自定义注解有效时间,例如编译时,运行时)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

(2)在Controller中对相应方法进行自定义注解的标注

@LoginRequired
@RequestMapping(path = "/setting",method = RequestMethod.GET)
public String getSettingPage(){
    return "/site/setting";
}

(3)利用反射获取注解

拦截器会拦截所有请求,但是我们通过判断可以实现只对带有该注解的方法进行处理。

import com.nowcoder.community.annotation.LoginRequired;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Component
public class LoginRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 因为我们的目标是只拦截方法,而拦截器有可能会拦截其他资源,所以必须先判断拦截器拦截的目标handler是否为方法
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            // 利用反射获取注解
            LoginRequired loginRequired = method.getAnnotation(LoginRequired.class);
            if(loginRequired != null && hostHolder.getUser() == null){
                // 如果用户未登录,则重定向至登录界面
                response.sendRedirect(request.getContextPath() + "/login");
                // 拒绝本次请求
                return false;
            }
        }
        return true;
    }
}

自定义注解实现拦截器的好处在于可以避免对拦截器进行配置。

2.3 源码底层

上文说到,所有请求都会直接先传递至DispatcherServlet,所以先分析一下DispatcherServlet,在该类中,最重要的一个方法就是doDispatch,查看部分核心源码之后,你会发现DispatcherServlet就是依赖该方法进行任务分配。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

            try {
                processedRequest = this.checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                //返回所有拦截器
                mappedHandler = this.getHandler(processedRequest);
                
                if(mappedHandler == null || mappedHandler.getHandler() == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //获取能够处理当前请求所对应的适配器,并用于调用Controller中逻辑代码。
                HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

                //调用所有拦截器的preHandle方法
                if(!mappedHandler.applyPreHandle(processedRequest, response)) {
                    //如果拦截器的preHandle方法返回值为false,则结束该方法的执行
                    return;
                }
                //执行Controller中的逻辑代码,获取到ModelAndView对象
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                //调用所有拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var19) {
                dispatchException = var19;
            }
            //处理视图渲染
            this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception var20) {
            //如果在执行过程中有异常,执行后续的收尾工作,执行对应拦截器中的afterCompletion方法
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
        } 
    }
原文地址:https://www.cnblogs.com/XDU-Lakers/p/14349788.html