對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);
        } 
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章