SpringMVC MultipartResolver

阅读数:124 评论数:0

跳转到新版页面

分类

python/Java

正文

一、概述

MultipartResolver 组件,内容类型( Content-Type )为 multipart/* 的请求的解析器,主要解析文件上传的请求。例如,MultipartResolver 会将 HttpServletRequest 封装成 MultipartHttpServletRequest 对象,便于获取参数信息以及上传的文件。

在DispatchServlet的doDispatch方法中:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ... 省略相关代码
    // <2> 检测请求是否为上传请求,如果是则通过 multipartResolver 将其封装成 MultipartHttpServletRequest 对象
    processedRequest = checkMultipart(request);
    // ... 省略相关代码
}

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 如果该请求是一个涉及到 multipart (文件)的请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

二、MultipartResolver接口

public interface MultipartResolver {
	/**
	 * 是否为 multipart 请求
	 */
	boolean isMultipart(HttpServletRequest request);
	/**
	 * 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象
	 */
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

	/**
	 * 清理处理 multipart 产生的资源,例如临时文件
	 */
	void cleanupMultipart(MultipartHttpServletRequest request);
}

1、初始化过程

DispatcherServletinitMultipartResolver(ApplicationContext context) 方法,初始化 MultipartResolver 组件,方法如下:

private void initMultipartResolver(ApplicationContext context) {
    try {
        // 从 Spring 上下文中获取名称为 "multipartResolver" ,类型为 MultipartResolver 的 Bean
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("Detected " + this.multipartResolver);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName());
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        if (logger.isTraceEnabled()) {
            logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared");
        }
    }
}

2、StandardServletMultipartResolver

基于 Servlet 3.0 标准的上传文件 API 的 MultipartResolver 实现类

public class StandardServletMultipartResolver implements MultipartResolver {

   /**
    * 是否延迟解析
    */
   private boolean resolveLazily = false;

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


   @Override
   public boolean isMultipart(HttpServletRequest request) {
      // 请求的 Content-type 必须 multipart/ 开头
      return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
   }

   @Override
   public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
      return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
   }

   @Override
   public void cleanupMultipart(MultipartHttpServletRequest request) {
      if (!(request instanceof AbstractMultipartHttpServletRequest) || ((AbstractMultipartHttpServletRequest) request).isResolved()) {
         // To be on the safe side: explicitly delete the parts,
         // but only actual file parts (for Resin compatibility)
         try {
            // 删除临时的 Part
            for (Part part : request.getParts()) {
               if (request.getFile(part.getName()) != null) {
                  part.delete();
               }
            }
         }
         catch (Throwable ex) {
            LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
         }
      }
   }

}
isMultipart(HttpServletRequest request) 请求的 Content-type 是否以 multipart/ 开头
resolveMultipart(HttpServletRequest request) 直接将 HttpServletRequest 转换成 StandardMultipartHttpServletRequest 对象
cleanupMultipart(MultipartHttpServletRequest request) 清理资源,删除临时的 javax.servlet.http.Part

3、StandardMultipartHttpServletRequest

基于 Servlet 3.0 的 Multipart HttpServletRequest 实现类,包含了一个 javax.servlet.http.HttpServletRequest 对象和它的 javax.servlet.http.Part 对象们,其中 Part 对象会被封装成 StandardMultipartFile 对象

public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {
	/**
	 * 普通参数名的集合
	 */
	@Nullable
	private Set<String> multipartParameterNames;

	public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException {
		this(request, false);
	}

	public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
		super(request);
		// 如果不需要延迟解析
		if (!lazyParsing) {
			// 解析请求
			parseRequest(request);
		}
	}
}
multipartParameterNames 普通参数名的集合,非上传文件的参数名

(1)parseRequest方法

,解析 HttpServletRequest 中的 Part 对象,如果是文件,则封装成 StandardMultipartFile 对象,否则就是普通参数,获取其名称

private void parseRequest(HttpServletRequest request) {
    try {
        // <1> 从 HttpServletRequest 中获取 Part 们
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        // <2> 遍历 parts 数组
        for (Part part : parts) {
            // <2.1> 获得请求头中的 Content-Disposition 信息,MIME 协议的扩展
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            // <2.2> 对 Content-Disposition 信息进行解析,生成 ContentDisposition 对象
            // 包含请求参数信息,以面向“对象”的形式进行访问
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            // <2.3> 获得文件名
            String filename = disposition.getFilename();
            // <2.4> 情况一,文件名非空,说明是文件参数,则创建 StandardMultipartFile 对象
            if (filename != null) {
                if (filename.startsWith("=?") && filename.endsWith("?=")) {
                    filename = MimeDelegate.decode(filename);
                }
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            // <2.5> 情况二,文件名为空,说明是普通参数,则保存参数名称
            else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        // <3> 将上面生成的 StandardMultipartFile 文件对象们,设置到父类的 multipartFiles 属性中
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}

(2)其它方法

/** 初始化请求 */
@Override
protected void initializeMultipart() {
    parseRequest(getRequest());
}
/** 获取请求中的参数名称 */
@Override
public Enumeration<String> getParameterNames() {
    if (this.multipartParameterNames == null) {
        initializeMultipart();
    }
    if (this.multipartParameterNames.isEmpty()) {
        return super.getParameterNames();
    }

    // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items
    // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
    Set<String> paramNames = new LinkedHashSet<>();
    Enumeration<String> paramEnum = super.getParameterNames();
    while (paramEnum.hasMoreElements()) {
        paramNames.add(paramEnum.nextElement());
    }
    paramNames.addAll(this.multipartParameterNames);
    return Collections.enumeration(paramNames);
}
/** 获取请求中的参数,参数名和参数值的映射 */
@Override
public Map<String, String[]> getParameterMap() {
    if (this.multipartParameterNames == null) {
        initializeMultipart();
    }
    if (this.multipartParameterNames.isEmpty()) {
        return super.getParameterMap();
    }
    // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items
    // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side
    Map<String, String[]> paramMap = new LinkedHashMap<>(super.getParameterMap());
    for (String paramName : this.multipartParameterNames) {
        if (!paramMap.containsKey(paramName)) {
            paramMap.put(paramName, getParameterValues(paramName));
        }
    }
    return paramMap;
}
/** 获取请求的 Content-Type 内容类型 */
@Override
public String getMultipartContentType(String paramOrFileName) {
    try {
        Part part = getPart(paramOrFileName);
        return (part != null ? part.getContentType() : null);
    }
    catch (Throwable ex) {
        throw new MultipartException("Could not access multipart servlet request", ex);
    }
}
/** 获取请求头信息 */
@Override
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
    try {
        Part part = getPart(paramOrFileName);
        if (part != null) {
            HttpHeaders headers = new HttpHeaders();
            for (String headerName : part.getHeaderNames()) {
                headers.put(headerName, new ArrayList<>(part.getHeaders(headerName)));
            }
            return headers;
        }
        else {
            return null;
        }
    }
    catch (Throwable ex) {
        throw new MultipartException("Could not access multipart servlet request", ex);
    }
}

4、StandardMultipartFile

StandardMultipartHttpServletRequest 的私有内部静态类,实现了 MultipartFile 接口和 Serializable 接口,内部封装了 javax.servlet.http.Part 对象和文件名称

private static class StandardMultipartFile implements MultipartFile, Serializable {

    private final Part part;

    private final String filename;

    public StandardMultipartFile(Part part, String filename) {
        this.part = part;
        this.filename = filename;
    }

    @Override
    public String getName() {
        return this.part.getName();
    }

    @Override
    public String getOriginalFilename() {
        return this.filename;
    }

    @Override
    public String getContentType() {
        return this.part.getContentType();
    }

    @Override
    public boolean isEmpty() {
        return (this.part.getSize() == 0);
    }

    @Override
    public long getSize() {
        return this.part.getSize();
    }

    @Override
    public byte[] getBytes() throws IOException {
        return FileCopyUtils.copyToByteArray(this.part.getInputStream());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return this.part.getInputStream();
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        this.part.write(dest.getPath());
        if (dest.isAbsolute() && !dest.exists()) {
            // Servlet 3.0 Part.write is not guaranteed to support absolute file paths:
            // may translate the given path to a relative location within a temp dir
            // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths).
            // At least we offloaded the file from memory storage; it'll get deleted
            // from the temp dir eventually in any case. And for our user's purposes,
            // we can manually copy it to the requested location as a fallback.
            FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
        }
    }

    @Override
    public void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest));
    }
}

这个类封装了 Servlet 3.0 的 Part 对象,也就是我们常用到的 MultipartFile 对象,支持对文件的操作,内部其实都是调用 javax.servlet.http.Part 的方法

5、AbstractMultipartHttpServletRequest

继承了 HttpServletRequestWrapper 类,实现了 MultipartHttpServletRequest接口

该类是 StandardMultipartHttpServletRequestDefaultMultipartHttpServletRequest 的父类,实现了一些公共的方法

public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest {
    /**
     * 请求中的文件信息
     */
	@Nullable
	private MultiValueMap<String, MultipartFile> multipartFiles;

	protected AbstractMultipartHttpServletRequest(HttpServletRequest request) {
		super(request);
	}

	@Override
	public HttpServletRequest getRequest() {
		return (HttpServletRequest) super.getRequest();
	}

	@Override
	public HttpMethod getRequestMethod() {
		return HttpMethod.resolve(getRequest().getMethod());
	}

    /** 获取请求头信息 */
	@Override
	public HttpHeaders getRequestHeaders() {
		HttpHeaders headers = new HttpHeaders();
		Enumeration<String> headerNames = getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String headerName = headerNames.nextElement();
			headers.put(headerName, Collections.list(getHeaders(headerName)));
		}
		return headers;
	}

    /** 获取文件名称列表 */
	@Override
	public Iterator<String> getFileNames() {
		return getMultipartFiles().keySet().iterator();
	}

    /** 获取指定文件名的单个文件 */
	@Override
	public MultipartFile getFile(String name) {
		return getMultipartFiles().getFirst(name);
	}

    /** 获取指定文件名的多个文件 */
	@Override
	public List<MultipartFile> getFiles(String name) {
		List<MultipartFile> multipartFiles = getMultipartFiles().get(name);
		if (multipartFiles != null) {
			return multipartFiles;
		}
		else {
			return Collections.emptyList();
		}
	}

	@Override
	public Map<String, MultipartFile> getFileMap() {
		return getMultipartFiles().toSingleValueMap();
	}

	@Override
	public MultiValueMap<String, MultipartFile> getMultiFileMap() {
		return getMultipartFiles();
	}

	public boolean isResolved() {
		return (this.multipartFiles != null);
	}

	protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
		this.multipartFiles = new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
	}

	protected MultiValueMap<String, MultipartFile> getMultipartFiles() {
		if (this.multipartFiles == null) {
			initializeMultipart();
		}
		return this.multipartFiles;
	}

	/** 交由子类实现 */
	protected void initializeMultipart() {
		throw new IllegalStateException("Multipart request not initialized");
	}
}

MultiValueMap<String, MultipartFile> multipartFiles属性,保存由子类解析出请求中的 Part 对象所封装成的 MultipartFile 对象

6、CommonMultipartResolver

如果需要使用这个 MultipartResolver 实现类,需要引入 commons-fileuploadcommons-iocommons-codec 组件

<dependencies>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
    </dependency>
</dependencies>

如果 Spring Boot 项目中需要使用 CommonsMultipartResolver,需要在 application.yml 中添加如下配置,排除其默认的配置,

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration

7、DefaultMultipartHttpServletRequest

MultipartHttpServletRequest 的默认实现类

public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest {

	private static final String CONTENT_TYPE = "Content-Type";

	@Nullable
	private Map<String, String[]> multipartParameters;

	@Nullable
	private Map<String, String> multipartParameterContentTypes;

	public DefaultMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, MultipartFile> mpFiles,
			Map<String, String[]> mpParams, Map<String, String> mpParamContentTypes) {

		super(request);
		setMultipartFiles(mpFiles);
		setMultipartParameters(mpParams);
		setMultipartParameterContentTypes(mpParamContentTypes);
	}

	public DefaultMultipartHttpServletRequest(HttpServletRequest request) {
		super(request);
	}

	@Override
	@Nullable
	public String getParameter(String name) {
		String[] values = getMultipartParameters().get(name);
		if (values != null) {
			return (values.length > 0 ? values[0] : null);
		}
		return super.getParameter(name);
	}

	@Override
	public String[] getParameterValues(String name) {
		String[] parameterValues = super.getParameterValues(name);
		String[] mpValues = getMultipartParameters().get(name);
		if (mpValues == null) {
			return parameterValues;
		}
		if (parameterValues == null || getQueryString() == null) {
			return mpValues;
		}
		else {
			String[] result = new String[mpValues.length + parameterValues.length];
			System.arraycopy(mpValues, 0, result, 0, mpValues.length);
			System.arraycopy(parameterValues, 0, result, mpValues.length, parameterValues.length);
			return result;
		}
	}

	@Override
	public Enumeration<String> getParameterNames() {
		Map<String, String[]> multipartParameters = getMultipartParameters();
		if (multipartParameters.isEmpty()) {
			return super.getParameterNames();
		}

		Set<String> paramNames = new LinkedHashSet<>();
		paramNames.addAll(Collections.list(super.getParameterNames()));
		paramNames.addAll(multipartParameters.keySet());
		return Collections.enumeration(paramNames);
	}

	@Override
	public Map<String, String[]> getParameterMap() {
		Map<String, String[]> result = new LinkedHashMap<>();
		Enumeration<String> names = getParameterNames();
		while (names.hasMoreElements()) {
			String name = names.nextElement();
			result.put(name, getParameterValues(name));
		}
		return result;
	}

	@Override
	public String getMultipartContentType(String paramOrFileName) {
		MultipartFile file = getFile(paramOrFileName);
		if (file != null) {
			return file.getContentType();
		}
		else {
			return getMultipartParameterContentTypes().get(paramOrFileName);
		}
	}

	@Override
	public HttpHeaders getMultipartHeaders(String paramOrFileName) {
		String contentType = getMultipartContentType(paramOrFileName);
		if (contentType != null) {
			HttpHeaders headers = new HttpHeaders();
			headers.add(CONTENT_TYPE, contentType);
			return headers;
		}
		else {
			return null;
		}
	}

	protected final void setMultipartParameters(Map<String, String[]> multipartParameters) {
		this.multipartParameters = multipartParameters;
	}

	protected Map<String, String[]> getMultipartParameters() {
		if (this.multipartParameters == null) {
			initializeMultipart();
		}
		return this.multipartParameters;
	}

	protected final void setMultipartParameterContentTypes(Map<String, String> multipartParameterContentTypes) {
		this.multipartParameterContentTypes = multipartParameterContentTypes;
	}

	protected Map<String, String> getMultipartParameterContentTypes() {
		if (this.multipartParameterContentTypes == null) {
			initializeMultipart();
		}
		return this.multipartParameterContentTypes;
	}
}



相关推荐

1、依赖 <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</arti