欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot攔截器與文件上傳實現(xiàn)方法與源碼分析

 更新時間:2022年10月08日 10:31:40   作者:Decade0712  
其實spring boot攔截器的配置方式和springMVC差不多,只有一些小的改變需要注意下就ok了。本文主要給大家介紹了關(guān)于如何在Springboot實現(xiàn)登陸攔截器與文件上傳功能,需要的朋友可以參考下

一、攔截器

攔截器我們之前在springmvc已經(jīng)做過介紹了

大家可以看下【SpringMVC】自定義攔截器和過濾器

為什么在這里還要再講一遍呢?

因為spring boot里面對它做了簡化,大大節(jié)省了我們配置那些煩人的xml文件的時間

接下來,我們就通過一個小例子來了解一下攔截器在spring boot中的使用

1、創(chuàng)建一個攔截器

首先我們創(chuàng)建一個攔截器,實現(xiàn)HandlerInterceptor接口

package com.decade.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    // 在調(diào)用控制器接口方法之前進(jìn)入,如果放回true就放行,進(jìn)入下一個攔截器或者控制器,如果返回false就不繼續(xù)往下走
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 獲取當(dāng)前請求路徑
        final String requestURL = request.getRequestURI();
        log.info("攔截到的請求為:{}", requestURL);
        final HttpSession session = request.getSession();
        final Object userSession = session.getAttribute("loginUser");
        // 如果session中存在用戶登錄信息,那么就判定為用戶已登錄,放行
        if (null != userSession) {
            return true;
        } else {
            // model和request都會往請求域中塞信息,所以這里可以使用request傳遞我們需要返回給前端的信息
            request.setAttribute("msg", "請登錄!");
            // 轉(zhuǎn)發(fā)到登錄頁
            request.getRequestDispatcher("/").forward(request, response);
            return false;
        }
    }
    //調(diào)用前提:preHandle返回true
    //調(diào)用時間:Controller方法處理完之后,DispatcherServlet進(jìn)行視圖的渲染之前,也就是說在這個方法中你可以對ModelAndView進(jìn)行操作
    //執(zhí)行順序:鏈?zhǔn)絀nterceptor情況下,Interceptor按照聲明的順序倒著執(zhí)行。
    //備注:postHandle雖然post打頭,但post、get方法都能處理
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle執(zhí)行{}", modelAndView);
    }
    //調(diào)用前提:preHandle返回true
    //調(diào)用時間:DispatcherServlet進(jìn)行視圖的渲染之后
    //多用于清理資源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("頁面渲染完成后執(zhí)行");
    }
}

2、配置攔截器

創(chuàng)建完之后,我們就需要將攔截器注冊到容器中,并指定攔截規(guī)則

那么,我們創(chuàng)建一個配置類,實現(xiàn)WebMvcConfigurer接口,重寫addInterceptors方法,將我們之前創(chuàng)建好的攔截器放入即可

值得注意的是,我們要放開對登錄頁以及靜態(tài)資源的限制

package com.decade.config;
import com.decade.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns:設(shè)置要攔截的請求,如果是/**,那么會攔截包括靜態(tài)資源在內(nèi)的所有請求
        // excludePathPatterns:設(shè)置不被攔截的請求,這里我們放行登錄頁請求和靜態(tài)資源
        registry.addInterceptor(new LoginInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/", "/login", "/css/**", "/images/**", "/js/**", "/fonts/**");
    }
}

我們在未登錄的狀態(tài)下,對主頁發(fā)起一個請求,可以發(fā)現(xiàn),攔截器生效,而且攔截器中的方法所執(zhí)行的順序也符合預(yù)期

二、攔截器原理

我們還是使用debug模式,通過斷點來進(jìn)行分析

調(diào)用之前的主頁面接口,可以發(fā)現(xiàn)斷點還是走到了DispatcherServlet類下的doDispatch()

首先,他還是會返回給我們一個處理器執(zhí)行鏈HandlerExecutionChain

這個里面除了包含我們的請求應(yīng)該由哪個控制器類的哪個方法進(jìn)行處理之外,還包含了攔截器鏈

然后在使用mv = ha.handle(processedRequest, response, mappedHandler.getHandler());執(zhí)行目標(biāo)方法之前,他會調(diào)用一個applyPreHandle()方法

如果這個方法返回false,那么就會直接返回,不再繼續(xù)往下走

我們進(jìn)入applyPreHandle()方法可以看到,這個方法里會遍歷所有的攔截器,如果preHandle()方法返回結(jié)果為true,那就繼續(xù)調(diào)用下一個攔截器的preHandle()方法

只要有一個攔截器的preHandle()方法返回false,那么就會從當(dāng)前遍歷到的攔截器開始,倒序執(zhí)行afterCompletion()方法

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        // 如果攔截器的preHandle()返回false,那么就會調(diào)用下面的triggerAfterCompletion()
        if (!interceptor.preHandle(request, response, this.handler)) {
            this.triggerAfterCompletion(request, response, (Exception)null);
            return false;
        }
    }
    return true;
}
// 這個方法里面會從當(dāng)前遍歷到的攔截器開始,倒序執(zhí)行afterCompletion()方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for(int i = this.interceptorIndex; i >= 0; --i) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable var7) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
        }
    }
}

執(zhí)行完目標(biāo)方法之后,斷點又走到mappedHandler.applyPostHandle(processedRequest, response, mv);

深入這個方法,我們可以發(fā)現(xiàn),這里是倒序執(zhí)行了所有攔截器的postHandle()方法

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

最后,頁面渲染完成之后,他也會倒序執(zhí)行所有攔截器的afterCompletion()方法

注意:只要在請求處理期間出現(xiàn)任何異常,它都會倒序執(zhí)行所有攔截器的postHandle()方法

三、文件上傳

之前博主也寫過關(guān)于SpringMVC的文件上傳和下載

使用Spring Boot之后,我們節(jié)約了很多的配置

接下來,我們就通過一個例子,了解Spring Boot中的文件上傳

首先,我們先創(chuàng)建一個頁面,這里我們只貼核心代碼

  • 默認(rèn)情況下,enctype的值是application/x-www-form-urlencoded,不能用于文件上傳,只有使用了multipart/form-data,才能完整的傳遞文件數(shù)據(jù)
  • multiple表示可接受多個值的文件上傳字段
<div class="panel-body">
    <form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label for="exampleInputEmail1">郵箱</label>
            <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
        </div>
        <div class="form-group">
            <label for="exampleInputPassword1">名字</label>
            <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
        </div>
        <div class="form-group">
            <label for="exampleInputFile">頭像</label>
            <input type="file" name="headerImg" id="exampleInputFile">
        </div>
        <div class="form-group">
            <label for="exampleInputFile">生活照</label>
            <input type="file" name="photos" multiple>
        </div>
        <div class="checkbox">
            <label>
                <input type="checkbox"> Check me out
            </label>
        </div>
        <button type="submit" class="btn btn-primary">提交</button>
    </form>
</div>

然后我們寫一下后端的業(yè)務(wù)代碼

package com.decade.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
@Slf4j
public class FileUploadController {
    /**
     * 頁面跳轉(zhuǎn),跳轉(zhuǎn)到文件上傳頁面
     * @return 跳轉(zhuǎn)到文件上傳頁面
     */
    @GetMapping(value = "/form_layouts")
    public String uploadPage() {
        return "form/form_layouts";
    }
    /**
     * 文件上傳請求
     * @param email 郵件
     * @param username 用戶名
     * @param headerImg 頭像文件
     * @param photos 生活照
     * @return 如果上傳文件成功,跳轉(zhuǎn)到首頁
     */
    @PostMapping(value = "/upload")
    public String uploadFile(@RequestParam(name = "email") String email,
        @RequestParam(name = "username") String username, @RequestPart("headerImg") MultipartFile headerImg,
        @RequestPart("photos") MultipartFile[] photos) {
        log.info("請求參數(shù)email{}, username{}, 頭像headerImg大小{}, 生活照photos張數(shù){}",
            email, username, headerImg.getSize(), photos.length);
        try {
            // 判斷頭像文件是否為空,如果不是為空,那么就保存到本地
            if (!headerImg.isEmpty()) {
                final String filename = headerImg.getOriginalFilename();
                headerImg.transferTo(new File("D:\\test1\\" + filename));
            }
            // 判斷生活照是否上傳,循環(huán)保存到本地
            if (photos.length > 0) {
                for (MultipartFile photo : photos) {
                    final String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("D:\\test1\\" + originalFilename));
                }
            }
        } catch (IOException e) {
            log.error("上傳文件出錯!", e);
        }
        return "redirect:/main.html";
    }
}

如果報錯信息如下,那么我們需要去Spring Boot的默認(rèn)文件中添加如下配置

# 單個文件最大限制
spring.servlet.multipart.max-file-size=10MB
# 單次請求最大限制
spring.servlet.multipart.max-request-size=100MB

修改相關(guān)配置之后,文件上傳成功

四、文件上傳流程

文件上傳相關(guān)配置類MultipartAutoConfiguration,相關(guān)配置類MultipartProperties

MultipartAutoConfiguration中我們自動配置好了文件上傳解析器StandardServletMultipartResolver(它在容器中的beanName為multipartResolver)

然后我們跟著上面文件上傳的例子進(jìn)行一個debug,分析一下流程

首先,斷點還是來到DispatcherServlet下面的doDispatch()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    // 設(shè)置文件解析默認(rèn)值為false
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
            	// 檢查當(dāng)前請求是否涉及文件上傳
                processedRequest = this.checkMultipart(request);
                // 將文件解析設(shè)置為true,表明當(dāng)前請求涉及文件上傳
                multipartRequestParsed = processedRequest != request;

這里的processedRequest = this.checkMultipart(request);

會調(diào)用StandardServletMultipartResolver類中的isMultipart()判斷當(dāng)前請求是否涉及文件上傳

如果涉及那么就會對當(dāng)前請求做一個處理,將原生的請求封裝成一個StandardMultipartHttpServletRequest請求,把文件相關(guān)信息解析后放進(jìn)Map中(具體可以看StandardMultipartHttpServletRequest類中的parseRequest方法)

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	// 如果文件上傳解析器不為空,那么就調(diào)用StandardServletMultipartResolver類中的isMultipart()判斷當(dāng)前請求是否涉及文件上傳
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
                this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        } else if (this.hasMultipartException(request)) {
            this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
        } else {
            try {
            	// 將原生的請求封裝成一個StandardMultipartHttpServletRequest請求,把文件相關(guān)信息解析放進(jìn)Map中
                return this.multipartResolver.resolveMultipart(request);

然后我們按照之前請求處理那篇博客里的路徑,從mv = ha.handle(processedRequest, response, mappedHandler.getHandler())進(jìn)入

一直走到InvocableHandlerMethod下面的getMethodArgumentValues()方法,深入斷點

我們得知,使用@RequestParam注解的參數(shù)使用RequestParamMethodArgumentResolver這個解析器

而文件相關(guān)入?yún)⑹鞘褂?code>@RequestPart注解的,它使用RequestPartMethodArgumentResolver來進(jìn)行文件相關(guān)參數(shù)解析

在這個解析器中,他又會根據(jù)參數(shù)的名稱去上面checkMultipart()方法所生成的Map中獲取文件相關(guān)信息

@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");
    RequestPart requestPart = (RequestPart)parameter.getParameterAnnotation(RequestPart.class);
    boolean isRequired = (requestPart == null || requestPart.required()) && !parameter.isOptional();
    // 獲取文件上傳的參數(shù)名稱
    String name = this.getPartName(parameter, requestPart);
    parameter = parameter.nestedIfOptional();
    Object arg = null;
    // 根據(jù)參數(shù)名稱去獲取前面map中的value,也就是MultipartFile對象
    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);

后面的調(diào)用鏈為MultipartResolutionDelegate.resolveMultipartArgument()—>判斷當(dāng)前參數(shù)是否是文件上傳,如果是,繼續(xù)判斷是多文件上傳還是單文件上傳—>然后進(jìn)入AbstractMultipartHttpServletRequest中,單文件走getFile()從map中獲取文件信息,多文件走getFiles()從map中獲取文件信息

最后,在控制器的目標(biāo)方法處使用MultipartFile類實現(xiàn)文件上傳的相關(guān)功能

到此這篇關(guān)于SpringBoot攔截器與文件上傳實現(xiàn)方法與源碼分析的文章就介紹到這了,更多相關(guān)SpringBoot攔截器與文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java語言注解基礎(chǔ)概念詳解

    java語言注解基礎(chǔ)概念詳解

    這篇文章主要介紹了java語言注解基礎(chǔ)概念詳解,具有一定借鑒價值,需要的朋友可以參考下。
    2017-12-12
  • SpringCloud:feign對象傳參和普通傳參及遇到的坑解決

    SpringCloud:feign對象傳參和普通傳參及遇到的坑解決

    這篇文章主要介紹了SpringCloud:feign對象傳參和普通傳參及遇到的坑解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 詳解Maven settings.xml配置(指定本地倉庫、阿里云鏡像設(shè)置)

    詳解Maven settings.xml配置(指定本地倉庫、阿里云鏡像設(shè)置)

    這篇文章主要介紹了詳解Maven settings.xml配置(指定本地倉庫、阿里云鏡像設(shè)置),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • springboot 重定向方式(redirect前綴)

    springboot 重定向方式(redirect前綴)

    這篇文章主要介紹了springboot 重定向方式(redirect前綴),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring?Data?JPA系列QueryByExampleExecutor使用詳解

    Spring?Data?JPA系列QueryByExampleExecutor使用詳解

    這篇文章主要為大家介紹了Spring?Data?JPA系列QueryByExampleExecutor使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • SpringBoot中的響應(yīng)式web應(yīng)用詳解

    SpringBoot中的響應(yīng)式web應(yīng)用詳解

    這篇文章主要介紹了SpringBoot中的響應(yīng)式web應(yīng)用詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • Java設(shè)計模式之工廠模式案例詳解

    Java設(shè)計模式之工廠模式案例詳解

    工廠模式(Factory Pattern)是Java中最常用的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。本文將通過案例詳細(xì)講解一下工廠模式,需要的可以參考一下
    2022-02-02
  • Java的Hibernate框架中的繼承映射學(xué)習(xí)教程

    Java的Hibernate框架中的繼承映射學(xué)習(xí)教程

    Hibernate中的映射可以將類與表對應(yīng),并利用類的繼承特性,這里我們就來看一下Java的Hibernate框架中的繼承映射學(xué)習(xí)教程
    2016-07-07
  • 詳解Java爬蟲利器Jsoup

    詳解Java爬蟲利器Jsoup

    Jsoup是一款Java語言開發(fā)的HTML解析器,用于解析HTML文檔以及對HTML文檔進(jìn)行操作,處理等,本文就將詳細(xì)給大家介紹一下Java中的爬蟲利器Jsoup,感興趣的同學(xué)可以參考一下
    2023-06-06
  • Java 高并發(fā)七:并發(fā)設(shè)計模型詳解

    Java 高并發(fā)七:并發(fā)設(shè)計模型詳解

    本文主要介紹Java高并發(fā) 并發(fā)設(shè)計模型的知識,這里主要講解 1. 什么是設(shè)計模式 2. 單例模式 3. 不變模式 4. Future模式 5. 生產(chǎn)者消費(fèi)者,有需要的小伙伴可以參考下
    2016-09-09

最新評論