這篇文章主要學(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)處理異常。

如果找不到對(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)行一致的處理。