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

2022-11-13 18:11:40 源码 方法 文件上传

一、拦截器

拦截器我们之前在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:设置要拦截的请求,如果是
    @GetMapping(value = "/fORM_layouts")
    public String uploadPage() {
        return "form/form_layouts";
    }
    
    @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拦截器与文件上传内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章