16.Spring的MultipartResolver接口及文件上傳和異常處理

這篇文章主要學(xué)習(xí)MultipartResolver接口,通過(guò)這個(gè)實(shí)現(xiàn)類(lèi)來(lái)解析請(qǐng)求中的內(nèi)容。
1.我們有兩種選擇來(lái)解析multipart請(qǐng)求中的內(nèi)容:
(1)CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart請(qǐng)求;
(2)StandardServletMultipartResolver:依賴于Servlet3.0對(duì)multipart請(qǐng)求的支持。
一般來(lái)說(shuō)我們會(huì)優(yōu)先選擇(2),它使用Servlet提供的功能支持,并不需要依賴任何其他的項(xiàng)目。
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 這個(gè)類(lèi)并且實(shí)現(xiàn)了MultipartResolver接口。
(2)該類(lèi)不會(huì)強(qiáng)制要求設(shè)置臨時(shí)文件路勁,默認(rèn)情況下,這個(gè)路徑就是servlet容器的臨時(shí)目錄。也可以通過(guò)uploadTempDir屬性,將其指定為一個(gè)不同的位置。
3.文件上傳需要設(shè)置臨時(shí)路徑外,其他的構(gòu)造器所能接收的參數(shù)如下:
(1)上傳文件的最大容量,以字節(jié)為單位,默認(rèn)是沒(méi)有限制的。
(2)整個(gè)multipart請(qǐng)求的最大容量,以字節(jié)為單位,不會(huì)關(guān)心有多少個(gè)part以及每個(gè)part的大小,默認(rèn)是沒(méi)有限制的。
(3)上傳過(guò)程中,如果文件大小達(dá)到了一個(gè)指定最大容量,以字節(jié)為單位,將會(huì)寫(xiě)入到臨時(shí)文件路勁中,默認(rèn)值為0.也就是會(huì)把所有上傳的文件都寫(xiě)入到磁盤(pán)上。
4.用xml對(duì)其進(jìn)行配置

<multipart-config>
    <location>上傳文件的位置</location>
    <max-file-size>上傳文件的最大限制,以字節(jié)為單位</max-file-size>
    <max-request-size>請(qǐng)求的最大限制</max-request-size>
</multipart-config>

其中<location>元素是必須進(jìn)行配置的。
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;
}

通過(guò)上面代碼我們可以看出,@RequestPart注解的默認(rèn)值value是為空的。它將會(huì)給定一個(gè)byte數(shù)組,這個(gè)數(shù)組中包含了請(qǐng)求中對(duì)應(yīng)part的數(shù)據(jù)。如果用戶提交表單的時(shí)候沒(méi)有選擇文件,那么者 數(shù)組會(huì)是空不是null。
6.MultipartFile接口的學(xué)習(xí)
在文件上傳中,我們還可以選擇MultipartFile類(lèi)來(lái)進(jìn)行文件上傳,比@RequestPart注解功能更強(qiáng)大一些,下面是該類(lèi)的定義:

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();//獲取上傳文件的類(lèi)型
    boolean isEmpty();//判斷上傳文件是否為空
    long getSize();//獲取文件上傳的字節(jié)大小
    byte[] getBytes() throws IOException;//將上傳的文件轉(zhuǎn)為一個(gè)字節(jié)數(shù)組返回
    InputStream getInputStream() throws IOException;//將文件數(shù)據(jù)以流的方式進(jìn)行讀取
    //將上傳文件寫(xiě)入到文件系統(tǒng)中
    void transferTo(File var1) throws IOException, IllegalStateException;
}

所以在文件上傳的時(shí)候,我們可以選擇用MultipartFile來(lái)進(jìn)行文件上傳的處理。
只有使用MultipartFile的時(shí)候,我們才需要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提供了多種方式將異常轉(zhuǎn)為響應(yīng):
(1)特定的spring異常將會(huì)自動(dòng)映射為指定的HTTP狀態(tài)碼
(2)異常上可以添加@ResponseStatus注解,從而映射為某一個(gè)HTTP狀態(tài)碼
(3)在方法上添加@ExceptionHandler注解,使用其來(lái)處理異常。


Spring的異常會(huì)默認(rèn)映射為HTTP狀態(tài)碼

如果找不到對(duì)應(yīng)的異常信息,則會(huì)拋出NoSuchRequestHandlingMethodException異常,最終結(jié)果就是產(chǎn)生404狀態(tài)碼的響應(yīng)。
9.@ResponseStatus注解學(xué)習(xí)
該注解是用來(lái)將異常轉(zhuǎn)為對(duì)應(yīng)的HTTP響應(yīng)狀態(tài)碼。

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注解有兩個(gè)參數(shù),一個(gè)是value屬性,用來(lái)指定具體的HttpStatus的狀態(tài)碼,reason屬性用來(lái)描述狀態(tài)碼的響應(yīng)信息。
通過(guò)該注解,可以將異常映射為HTTP狀態(tài)碼。
下面是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 {};
}

從接口定義,可以看出它返回的是一個(gè)類(lèi)型信息的數(shù)組。他可以處理同一個(gè)控制器中所有處理器方法所做出的異常。
11.為控制器添加通知
控制器通知是任意帶有@ControllerAdvice注解的類(lèi),這個(gè)類(lèi)會(huì)包含一個(gè)或者多個(gè)如下類(lèi)型的方法:
(1)@ExceptionHandler注解標(biāo)注的方法
(2)@InitBinder注解標(biāo)注的方法
(3)@ModelAttribute注解標(biāo)注的方法
該注解最常用的一個(gè)場(chǎng)景就是將所有的@ExceptionHandler方法收集到一個(gè)類(lèi)中,這樣所有控制器的異常就能在一個(gè)地方進(jìn)行一致的處理。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容