Spring专题学习

16.Spring的MultipartResolver接口及文件

2018-09-11  本文已影响0人  Lee_java

这篇文章主要学习MultipartResolver接口,通过这个实现类来解析请求中的内容。
1.我们有两种选择来解析multipart请求中的内容:
(1)CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求;
(2)StandardServletMultipartResolver:依赖于Servlet3.0对multipart请求的支持。
一般来说我们会优先选择(2),它使用Servlet提供的功能支持,并不需要依赖任何其他的项目。
2.CommonsMultipartResolver的定义:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.web.multipart.commons;

import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsFileUploadSupport.MultipartParsingResult;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
import org.springframework.web.util.WebUtils;

public class CommonsMultipartResolver extends CommonsFileUploadSupport implements MultipartResolver, ServletContextAware {
    private boolean resolveLazily;

    public CommonsMultipartResolver() {
        this.resolveLazily = false;
    }

    public CommonsMultipartResolver(ServletContext servletContext) {
        this();
        this.setServletContext(servletContext);
    }

    public void setResolveLazily(boolean resolveLazily) {
        this.resolveLazily = resolveLazily;
    }

    protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
        return new ServletFileUpload(fileItemFactory);
    }

    public void setServletContext(ServletContext servletContext) {
        if(!this.isUploadTempDirSpecified()) {
            this.getFileItemFactory().setRepository(WebUtils.getTempDir(servletContext));
        }

    }

    public boolean isMultipart(HttpServletRequest request) {
        return request != null && ServletFileUpload.isMultipartContent(request);
    }

    public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
        Assert.notNull(request, "Request must not be null");
        if(this.resolveLazily) {
            return new DefaultMultipartHttpServletRequest(request) {
                protected void initializeMultipart() {
                    MultipartParsingResult parsingResult = CommonsMultipartResolver.this.parseRequest(request);
                    this.setMultipartFiles(parsingResult.getMultipartFiles());
                    this.setMultipartParameters(parsingResult.getMultipartParameters());
                    this.setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
                }
            };
        } else {
            MultipartParsingResult parsingResult = this.parseRequest(request);
            return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(), parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
        }
    }

    protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
        String encoding = this.determineEncoding(request);
        FileUpload fileUpload = this.prepareFileUpload(encoding);

        try {
            List<FileItem> fileItems = ((ServletFileUpload)fileUpload).parseRequest(request);
            return this.parseFileItems(fileItems, encoding);
        } catch (SizeLimitExceededException var5) {
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), var5);
        } catch (FileUploadException var6) {
            throw new MultipartException("Could not parse multipart servlet request", var6);
        }
    }

    protected String determineEncoding(HttpServletRequest request) {
        String encoding = request.getCharacterEncoding();
        if(encoding == null) {
            encoding = this.getDefaultEncoding();
        }

        return encoding;
    }

    public void cleanupMultipart(MultipartHttpServletRequest request) {
        if(request != null) {
            try {
                this.cleanupFileItems(request.getMultiFileMap());
            } catch (Throwable var3) {
                this.logger.warn("Failed to perform multipart cleanup for servlet request", var3);
            }
        }

    }
}

(1)它继承了CommonsFileUploadSupport 这个类并且实现了MultipartResolver接口。
(2)该类不会强制要求设置临时文件路劲,默认情况下,这个路径就是servlet容器的临时目录。也可以通过uploadTempDir属性,将其指定为一个不同的位置。
3.文件上传需要设置临时路径外,其他的构造器所能接收的参数如下:
(1)上传文件的最大容量,以字节为单位,默认是没有限制的。
(2)整个multipart请求的最大容量,以字节为单位,不会关心有多少个part以及每个part的大小,默认是没有限制的。
(3)上传过程中,如果文件大小达到了一个指定最大容量,以字节为单位,将会写入到临时文件路劲中,默认值为0.也就是会把所有上传的文件都写入到磁盘上。
4.用xml对其进行配置

<multipart-config>
    <location>上传文件的位置</location>
    <max-file-size>上传文件的最大限制,以字节为单位</max-file-size>
    <max-request-size>请求的最大限制</max-request-size>
</multipart-config>

其中<location>元素是必须进行配置的。
5.@RequestPart注解

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {
    String value() default "";
    boolean required() default true;
}

通过上面代码我们可以看出,@RequestPart注解的默认值value是为空的。它将会给定一个byte数组,这个数组中包含了请求中对应part的数据。如果用户提交表单的时候没有选择文件,那么者 数组会是空不是null。
6.MultipartFile接口的学习
在文件上传中,我们还可以选择MultipartFile类来进行文件上传,比@RequestPart注解功能更强大一些,下面是该类的定义:

package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public interface MultipartFile {
    String getName();//获取上传文件的名称
    String getOriginalFilename();//获取上传文件的原始名字
    String getContentType();//获取上传文件的类型
    boolean isEmpty();//判断上传文件是否为空
    long getSize();//获取文件上传的字节大小
    byte[] getBytes() throws IOException;//将上传的文件转为一个字节数组返回
    InputStream getInputStream() throws IOException;//将文件数据以流的方式进行读取
    //将上传文件写入到文件系统中
    void transferTo(File var1) throws IOException, IllegalStateException;
}

所以在文件上传的时候,我们可以选择用MultipartFile来进行文件上传的处理。
只有使用MultipartFile的时候,我们才需要MultipartResolver。
7.文件以Part的形式接受上传

package javax.servlet.http;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
//给接口位于javax.servlet.http包下面
public interface Part {
    InputStream getInputStream() throws IOException;
    String getContentType();
    String getName();
    String getSubmittedFileName();//等同于MultiPartFile中的getOriginalFilename
    long getSize();
    void write(String var1) throws IOException;
    void delete() throws IOException;
    String getHeader(String var1);
    Collection<String> getHeaders(String var1);
    Collection<String> getHeaderNames();
}

8.异常处理
spring提供了多种方式将异常转为响应:
(1)特定的spring异常将会自动映射为指定的HTTP状态码
(2)异常上可以添加@ResponseStatus注解,从而映射为某一个HTTP状态码
(3)在方法上添加@ExceptionHandler注解,使用其来处理异常。


Spring的异常会默认映射为HTTP状态码

如果找不到对应的异常信息,则会抛出NoSuchRequestHandlingMethodException异常,最终结果就是产生404状态码的响应。
9.@ResponseStatus注解学习
该注解是用来将异常转为对应的HTTP响应状态码。

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.http.HttpStatus;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {
    HttpStatus value();
    String reason() default "";
}

从上述代码中可以看出。@ResponseStatus注解有两个参数,一个是value属性,用来指定具体的HttpStatus的状态码,reason属性用来描述状态码的响应信息。
通过该注解,可以将异常映射为HTTP状态码。
下面是HttpStatus的定义:

package org.springframework.http;

public enum HttpStatus {
    CONTINUE(100, "Continue"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols"),
    PROCESSING(102, "Processing"),
    CHECKPOINT(103, "Checkpoint"),
    OK(200, "OK"),
    CREATED(201, "Created"),
    ACCEPTED(202, "Accepted"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
    NO_CONTENT(204, "No Content"),
    RESET_CONTENT(205, "Reset Content"),
    PARTIAL_CONTENT(206, "Partial Content"),
    MULTI_STATUS(207, "Multi-Status"),
    ALREADY_REPORTED(208, "Already Reported"),
    IM_USED(226, "IM Used"),
    MULTIPLE_CHOICES(300, "Multiple Choices"),
    MOVED_PERMANENTLY(301, "Moved Permanently"),
    FOUND(302, "Found"),
    SEE_OTHER(303, "See Other"),
    NOT_MODIFIED(304, "Not Modified"),
    TEMPORARY_REDIRECT(307, "Temporary Redirect"),
    PERMANENT_REDIRECT(308, "Permanent Redirect"),
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "Unauthorized"),
    PAYMENT_REQUIRED(402, "Payment Required"),
    FORBIDDEN(403, "Forbidden"),
    NOT_FOUND(404, "Not Found"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
    NOT_ACCEPTABLE(406, "Not Acceptable"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
    REQUEST_TIMEOUT(408, "Request Timeout"),
    CONFLICT(409, "Conflict"),
    GONE(410, "Gone"),
    LENGTH_REQUIRED(411, "Length Required"),
    PRECONDITION_FAILED(412, "Precondition Failed"),
    PAYLOAD_TOO_LARGE(413, "Payload Too Large"),
    URI_TOO_LONG(414, "URI Too Long"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
    EXPECTATION_FAILED(417, "Expectation Failed"),
    I_AM_A_TEAPOT(418, "I'm a teapot"),
    UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
    LOCKED(423, "Locked"),
    FAILED_DEPENDENCY(424, "Failed Dependency"),
    UPGRADE_REQUIRED(426, "Upgrade Required"),
    PRECONDITION_REQUIRED(428, "Precondition Required"),
    TOO_MANY_REQUESTS(429, "Too Many Requests"),
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
    NOT_IMPLEMENTED(501, "Not Implemented"),
    BAD_GATEWAY(502, "Bad Gateway"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
    VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
    INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
    LOOP_DETECTED(508, "Loop Detected"),
    BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
    NOT_EXTENDED(510, "Not Extended"),
    NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");
    private final int value;
    private final String reasonPhrase;
    private HttpStatus(int value, String reasonPhrase) {
        this.value = value;
        this.reasonPhrase = reasonPhrase;
    }
    public int value() {
        return this.value;
    }
    public String getReasonPhrase() {
        return this.reasonPhrase;
    }
    public boolean is1xxInformational() {
        return HttpStatus.Series.INFORMATIONAL.equals(this.series());
    }
    public boolean is2xxSuccessful() {
        return HttpStatus.Series.SUCCESSFUL.equals(this.series());
    }
    public boolean is3xxRedirection() {
        return HttpStatus.Series.REDIRECTION.equals(this.series());
    }
    public boolean is4xxClientError() {
        return HttpStatus.Series.CLIENT_ERROR.equals(this.series());
    }
    public boolean is5xxServerError() {
        return HttpStatus.Series.SERVER_ERROR.equals(this.series());
    }
    public HttpStatus.Series series() {
        return HttpStatus.Series.valueOf(this);
    }
    public String toString() {
        return Integer.toString(this.value);
    }
    public static HttpStatus valueOf(int statusCode) {
        HttpStatus[] var1 = values();
        int var2 = var1.length;
        for(int var3 = 0; var3 < var2; ++var3) {
            HttpStatus status = var1[var3];
            if(status.value == statusCode) {
                return status;
            }
        }
        throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
    }
    public static enum Series {
        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);
        private final int value;
        private Series(int value) {
            this.value = value;
        }
        public int value() {
            return this.value;
        }
        public static HttpStatus.Series valueOf(int status) {
            int seriesCode = status / 100;
            HttpStatus.Series[] var2 = values();
            int var3 = var2.length;
            for(int var4 = 0; var4 < var3; ++var4) {
                HttpStatus.Series series = var2[var4];
                if(series.value == seriesCode) {
                    return series;
                }
            }
            throw new IllegalArgumentException("No matching constant for [" + status + "]");
        }
        public static HttpStatus.Series valueOf(HttpStatus status) {
            return valueOf(status.value);
        }
    }
}

10.@ExceptionHandler接口定义

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}

从接口定义,可以看出它返回的是一个类型信息的数组。他可以处理同一个控制器中所有处理器方法所做出的异常。
11.为控制器添加通知
控制器通知是任意带有@ControllerAdvice注解的类,这个类会包含一个或者多个如下类型的方法:
(1)@ExceptionHandler注解标注的方法
(2)@InitBinder注解标注的方法
(3)@ModelAttribute注解标注的方法
该注解最常用的一个场景就是将所有的@ExceptionHandler方法收集到一个类中,这样所有控制器的异常就能在一个地方进行一致的处理。

上一篇下一篇

猜你喜欢

热点阅读