SpringBoot拦截器与文件上传实现方法与源码分析

目录
  • 一、拦截器
    • 1、创建一个拦截器
    • 2、配置拦截器
  • 二、拦截器原理
  • 三、文件上传
  • 四、文件上传流程

一、拦截器

拦截器我们之前在springmvc已经做过介绍了

大家可以看下【SpringMVC】自定义拦截器和过滤器

为什么在这里还要再讲一遍呢?

因为spring boot里面对它做了简化,大大节省了我们配置那些烦人的xml文件的时间

接下来,我们就通过一个小例子来了解一下拦截器在spring boot中的使用

1、创建一个拦截器

首先我们创建一个拦截器,实现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 {
    // 在调用控制器接口方法之前进入,如果放回true就放行,进入下一个拦截器或者控制器,如果返回false就不继续往下走
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取当前请求路径
        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", "请登录!");
            // 转发到登录页
            request.getRequestDispatcher("/").forward(request, response);
            return false;
        }
    }
    //调用前提:preHandle返回true
    //调用时间:Controller方法处理完之后,DispatcherServlet进行视图的渲染之前,也就是说在这个方法中你可以对ModelAndView进行操作
    //执行顺序:链式Interceptor情况下,Interceptor按照声明的顺序倒着执行。
    //备注:postHandle虽然post打头,但post、get方法都能处理
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}", modelAndView);
    }
    //调用前提:preHandle返回true
    //调用时间:DispatcherServlet进行视图的渲染之后
    //多用于清理资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("页面渲染完成后执行");
    }
}

2、配置拦截器

创建完之后,我们就需要将拦截器注册到容器中,并指定拦截规则

那么,我们创建一个配置类,实现WebMvcConfigurer接口,重写addInterceptors方法,将我们之前创建好的拦截器放入即可

值得注意的是,我们要放开对登录页以及静态资源的限制

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:设置要拦截的请求,如果是/**,那么会拦截包括静态资源在内的所有请求
        // excludePathPatterns:设置不被拦截的请求,这里我们放行登录页请求和静态资源
        registry.addInterceptor(new LoginInterceptor())
            .addPathPatterns("/**")
            .excludePathPatterns("/", "/login", "/css/**", "/images/**", "/js/**", "/fonts/**");
    }
}

我们在未登录的状态下,对主页发起一个请求,可以发现,拦截器生效,而且拦截器中的方法所执行的顺序也符合预期

二、拦截器原理

我们还是使用debug模式,通过断点来进行分析

调用之前的主页面接口,可以发现断点还是走到了DispatcherServlet类下的doDispatch()

首先,他还是会返回给我们一个处理器执行链HandlerExecutionChain

这个里面除了包含我们的请求应该由哪个控制器类的哪个方法进行处理之外,还包含了拦截器链

然后在使用mv = ha.handle(processedRequest, response, mappedHandler.getHandler());执行目标方法之前,他会调用一个applyPreHandle()方法

如果这个方法返回false,那么就会直接返回,不再继续往下走

我们进入applyPreHandle()方法可以看到,这个方法里会遍历所有的拦截器,如果preHandle()方法返回结果为true,那就继续调用下一个拦截器的preHandle()方法

只要有一个拦截器的preHandle()方法返回false,那么就会从当前遍历到的拦截器开始,倒序执行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,那么就会调用下面的triggerAfterCompletion()
        if (!interceptor.preHandle(request, response, this.handler)) {
            this.triggerAfterCompletion(request, response, (Exception)null);
            return false;
        }
    }
    return true;
}
// 这个方法里面会从当前遍历到的拦截器开始,倒序执行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);
        }
    }
}

执行完目标方法之后,断点又走到mappedHandler.applyPostHandle(processedRequest, response, mv);

深入这个方法,我们可以发现,这里是倒序执行了所有拦截器的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);
    }
}

最后,页面渲染完成之后,他也会倒序执行所有拦截器的afterCompletion()方法

注意:只要在请求处理期间出现任何异常,它都会倒序执行所有拦截器的postHandle()方法

三、文件上传

之前博主也写过关于SpringMVC的文件上传和下载

使用Spring Boot之后,我们节约了很多的配置

接下来,我们就通过一个例子,了解Spring Boot中的文件上传

首先,我们先创建一个页面,这里我们只贴核心代码

  • 默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据
  • 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>

然后我们写一下后端的业务代码

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 {
    /**
     * 页面跳转,跳转到文件上传页面
     * @return 跳转到文件上传页面
     */
    @GetMapping(value = "/form_layouts")
    public String uploadPage() {
        return "form/form_layouts";
    }
    /**
     * 文件上传请求
     * @param email 邮件
     * @param username 用户名
     * @param headerImg 头像文件
     * @param photos 生活照
     * @return 如果上传文件成功,跳转到首页
     */
    @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("请求参数email{}, username{}, 头像headerImg大小{}, 生活照photos张数{}",
            email, username, headerImg.getSize(), photos.length);
        try {
            // 判断头像文件是否为空,如果不是为空,那么就保存到本地
            if (!headerImg.isEmpty()) {
                final String filename = headerImg.getOriginalFilename();
                headerImg.transferTo(new File("D:\\test1\\" + filename));
            }
            // 判断生活照是否上传,循环保存到本地
            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的默认文件中添加如下配置

# 单个文件最大限制
spring.servlet.multipart.max-file-size=10MB
# 单次请求最大限制
spring.servlet.multipart.max-request-size=100MB

修改相关配置之后,文件上传成功

四、文件上传流程

文件上传相关配置类MultipartAutoConfiguration,相关配置类MultipartProperties

MultipartAutoConfiguration中我们自动配置好了文件上传解析器StandardServletMultipartResolver(它在容器中的beanName为multipartResolver)

然后我们跟着上面文件上传的例子进行一个debug,分析一下流程

首先,断点还是来到DispatcherServlet下面的doDispatch()方法

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    // 设置文件解析默认值为false
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        try {
            ModelAndView mv = null;
            Object dispatchException = null;
            try {
            	// 检查当前请求是否涉及文件上传
                processedRequest = this.checkMultipart(request);
                // 将文件解析设置为true,表明当前请求涉及文件上传
                multipartRequestParsed = processedRequest != request;

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

会调用StandardServletMultipartResolver类中的isMultipart()判断当前请求是否涉及文件上传

如果涉及那么就会对当前请求做一个处理,将原生的请求封装成一个StandardMultipartHttpServletRequest请求,把文件相关信息解析后放进Map中(具体可以看StandardMultipartHttpServletRequest类中的parseRequest方法)

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
	// 如果文件上传解析器不为空,那么就调用StandardServletMultipartResolver类中的isMultipart()判断当前请求是否涉及文件上传
    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请求,把文件相关信息解析放进Map中
                return this.multipartResolver.resolveMultipart(request);

然后我们按照之前请求处理那篇博客里的路径,从mv = ha.handle(processedRequest, response, mappedHandler.getHandler())进入

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

我们得知,使用@RequestParam注解的参数使用RequestParamMethodArgumentResolver这个解析器

而文件相关入参是使用@RequestPart注解的,它使用RequestPartMethodArgumentResolver来进行文件相关参数解析

在这个解析器中,他又会根据参数的名称去上面checkMultipart()方法所生成的Map中获取文件相关信息

@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();
    // 获取文件上传的参数名称
    String name = this.getPartName(parameter, requestPart);
    parameter = parameter.nestedIfOptional();
    Object arg = null;
    // 根据参数名称去获取前面map中的value,也就是MultipartFile对象
    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);

后面的调用链为MultipartResolutionDelegate.resolveMultipartArgument()—>判断当前参数是否是文件上传,如果是,继续判断是多文件上传还是单文件上传—>然后进入AbstractMultipartHttpServletRequest中,单文件走getFile()从map中获取文件信息,多文件走getFiles()从map中获取文件信息

最后,在控制器的目标方法处使用MultipartFile类实现文件上传的相关功能

到此这篇关于SpringBoot拦截器与文件上传实现方法与源码分析的文章就介绍到这了,更多相关SpringBoot拦截器与文件上传内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot实现登录拦截器的方法详解

    在项目目录下建立两个包:inter 与contsfig 在inter新建层中实现HandlerInterceptor的继承类 package com.example.gameboxadminserver.inter; import com.example.gameboxadminserver.entity.User; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.

  • SpringBoot配置自定义拦截器实现过程详解

    目录 1. HttpServletRequest包装类 2. 使用Filter将request传递下去 3. 添加拦截器 4. 全局异常处理器 5. 配置拦截器 1. HttpServletRequest包装类 因为HttpServletRequest只能读取一次,所以需要对request进行包装,变成可重复读的request. package net.lesscoding.interceptor; import javax.servlet.ReadListener; import javax.

  • SpringBoot面试突击之过滤器和拦截器区别详解

    目录 实现过滤器和拦截器 a) 实现过滤器 b) 实现拦截器 过滤器 VS 拦截器 1.出身不同 2.触发时机不同 3.实现不同 4.支持的项目类型不同 5.使用的场景不同 总结 实现过滤器和拦截器 首先,我们先来看一下二者在 Spring Boot 项目中的具体实现,这对后续理解二者的区别有很大的帮助. a) 实现过滤器 过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法,具

  • SpringBoot配置拦截器实现过程详解

    目录 如何配置拦截器 拦截器设置容易出现的问题 如何取消拦截操作 实例-登录验证 如何配置拦截器 step1: 自定义拦截器 /** * 自定义拦截器 */ public class MyInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class); /** * 在请求匹配controller之前执行,返回t

  • SpringBoot拦截器的配置使用介绍

    目录 1. 配置拦截器 2. 一个小 Demo 1. 自定义拦截器类—LoginInterceptor 2. 将拦截器注册到容器中 3. 原理分析 1. 配置拦截器 具体步骤: 编写一自定义拦截器类实现接口 HandlerInterceptor HandlerInterceptor 接口: 可在三处进行拦截——目标方法执行之前.目标方法执行完成.页面渲染以后拦截 public interface HandlerInterceptor { default boolean preHandle(Htt

  • 在SpringBoot项目中整合拦截器的详细步骤

    目录 引言 1.创建一个SpringBoot项目工程 2.配置自定义的拦截器 3.注册拦截器 4.编写控制器 总结 引言 拦截器在Web系统中非常常见,对于某些全局统一的操作,我们可以把它提取到拦截器中实现.总结起来,拦截器大致有以下几种使用场景: 1.权限检查:如登录检测,进入处理程序检测用户是否登录,如果没有,则直接返回登录页面或error错误页面: 2.性能检测:有时系统在某段时间莫名其妙很慢,我们可以通过拦截器在进入处理程序之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时

  • SpringBoot拦截器与文件上传实现方法与源码分析

    目录 一.拦截器 1.创建一个拦截器 2.配置拦截器 二.拦截器原理 三.文件上传 四.文件上传流程 一.拦截器 拦截器我们之前在springmvc已经做过介绍了 大家可以看下[SpringMVC]自定义拦截器和过滤器 为什么在这里还要再讲一遍呢? 因为spring boot里面对它做了简化,大大节省了我们配置那些烦人的xml文件的时间 接下来,我们就通过一个小例子来了解一下拦截器在spring boot中的使用 1.创建一个拦截器 首先我们创建一个拦截器,实现HandlerIntercepto

  • Java全面深入探究SpringBoot拦截器与文件上传

    目录 拦截器 拦截器的概念 拦截器的配置 配置拦截器 拦截器的原理 文件上传 更改文件上传大小 拦截器 拦截器的概念 动态拦截Actioon调用的对象,使开发者在一个Actioon执行的前后执行一段代码,也可以在Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式. 作用: 动态拦截Action调用的对象(也就是实际项目中的controller层的接口) 一般拦截器用于对用户访问的限制.如当用户没有登录时访问主页面,则可以使用拦截器进行拦截并重定向到登录页面.

  • SpringBoot整合MinIO实现文件上传的方法详解

    目录 前言 1. MinIO 简介 2. MinIO 安装 3. 整合 Spring Boot 4. 配置nginx 5. 小结 前言 现在 OSS 服务算是一个基础服务了,很多云服务厂商都有提供这样的服务,价格也不贵,松哥自己的网站用的就是类似的服务. 不过对于中小公司来说,除了购买 OSS 服务之外,也可以自己搭建专业的文件服务器,自己搭建专门的文件服务器的话,曾经比较专业的做法是 FastDFS,松哥之前也专门为之录过视频发在 B 站上,感兴趣的小伙伴可以自行查看.不过 FastDFS 搭

  • 文件上传程序的全部源码

    1.upfile.php文件 <html> <body> <title>文件上传</title> <form enctype="multipart/form-data" action=upload.php method=post> <input type=file name=upfile size=10><br><br> <input type=submit value='上载文件'&

  • vue+springboot+element+vue-resource实现文件上传教程

    vue页面设置 <el-upload class="upload-demo" action="" :before-upload="beforeUpload" //上传前操作 :before-remove="beforeRemove" //移除钱操作 :multiple="false" //禁止多选 :http-request="myUpload" //文件上传,重写文件上传方法,a

  • SpringBoot整合MongoDB实现文件上传下载删除

    本文主要内容 MongoDB基础操作命令示例练习 MongoDB居于GridFSTemplate的文件上传.下载.删除等操作(工作重点使用) 1. 基础命令 创建的数据库名称:horse,创建的集合名称:blog # 创建数据库 use horse # 删除当前数据库[horse] db.dropDatebase() # 查看所有数据库 show dbs # 设置用户的角色和权限 db.createUser({user:"horse",pwd:"mongo123",

  • springboot集成ftp实现文件上传

    本文实例为大家分享了springboot集成ftp实现文件上传的具体代码,供大家参考,具体内容如下 1.FileUtil package io.renren.modules.oss.utils; import org.apache.commons.net.ftp.FTPClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;

  • springboot整合minio实现文件上传与下载且支持链接永久访问

    目录 1.minio部署 2.项目搭建 3.文件上传 4.文件下载 5.文件永久链接下载 1.minio部署 1.1 拉取镜像 docker pull minio/minio 1.2 创建数据目录 mkdir -p /home/guanz/minio mkdir -p /home/guanz/minio/midata 1.3 启动minio docker run -d -p 9000:9000 -p 9001:9001 --restart=always -e MINIO_ACCESS_KEY=g

  • SpringBoot实现PPT格式文件上传并在线预览功能

    1.需要引入依赖 <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.9</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId&

  • SpringBoot 集成MongoDB实现文件上传功能

    目录 前言 环境 代码实现 测试 源码 前言 记录下SpringBoot集成MongoDB实现文件上传的步骤 MongoDB - 5.0.6安装包 链接: https://pan.baidu.com/s/1_7nJDe3ndraNyo3vGWOXhg?pwd=i4cv 提取码: i4cv 环境 SpringBoot - 2.5.12MongoDB - 5.0.6 代码实现 pom.xml <dependency> <groupId>org.springframework.boot&

随机推荐