什么年代了,你还不知道Servlet3.0中的文件上传方式?-mimedxgroup股票资讯

频道:信用卡知识 日期: 浏览:0

其实文件上传这块松哥之前和大家聊过很多次了,这次因为最近正在进行 SpringMVC 的源码分析,所以又再次把这个话题拉出来“鞭尸”,不过这次松哥想从源码角度来聊聊这个话题。

理解源码的前提是先会用,所以我们还是先来看看用法,然后再来分析源码。

1.两种文件解析方案

对于上传文件的请求,SpringMVC 中目前共有两种不同的解析方案:

StandardServletMultipartResolver

CommonsMultipartResolver

StandardServletMultipartResolver 支持 Servlet3.0 中标准的文件上传方案,使用非常简单;CommonsMultipartResolver 则需要结合 Apache Commons fileupload 组件一起使用,这种方式兼容低版本的 Servlet。

StandardServletMultipartResolver

先来回顾下 StandardServletMultipartResolver 的用法。

使用 StandardServletMultipartResolver,可以直接通过 HttpServletRequest 自带的 getPart 方法获取上传文件并保存,这是一种标准的操作方式,这种方式也不用添加任何额外的依赖,只需要确保 Servlet 的版本在 3.0 之上即可。

首先我们需要为 Servlet 配置

multipart-config

,哪个 Servlet 负责处理上传文件,就为哪个 Servlet 配置

multipart-config

。在 SpringMVC 中,我们的请求都是通过 DispatcherServlet 进行分发的,所以我们就为 DispatcherServlet 配置

multipart-config

配置方式如下:

springmvc

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:spring-servlet.xml

/tmp

1024

10240

springmvc

/

然后在 SpringMVC 的配置文件中提供一个 StandardServletMultipartResolver 实例,注意该实例的 id 必须为 multipartResolver(具体原因参见:SpringMVC 初始化流程分析一文)。

配置完成后,我们就可以开发一个文件上传接口了,如下:

@RestController

publicclassFileUploadController{

SimpleDateFormatsdf=newSimpleDateFormat("/yyyy/MM/dd/");

@PostMapping("/upload")

publicStringfileUpload(MultipartFilefile,HttpServletRequestreq){

Stringformat=sdf.format(newDate());

StringrealPath=req.getServletContext().getRealPath("/img")+format;

Filefolder=newFile(realPath);

if(!folder.exists()){

folder.mkdirs();

}

StringoldName=file.getOriginalFilename();

StringnewName=UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."));

try{

file.transferTo(newFile(folder,newName));

returnreq.getScheme()+"://"+req.getRemoteHost()+":"+req.getServerPort()+"/img"+format+newName;

}catch(IOExceptione){

e.printStackTrace();

}

return"error";

}

@PostMapping("/upload2")

publicStringfileUpload2(HttpServletRequestreq)throwsIOException,ServletException{

StandardServletMultipartResolverresolver=newStandardServletMultipartResolver();

MultipartFilefile=resolver.resolveMultipart(req).getFile("file");

Stringformat=sdf.format(newDate());

StringrealPath=req.getServletContext().getRealPath("/img")+format;

Filefolder=newFile(realPath);

if(!folder.exists()){

folder.mkdirs();

}

StringoldName=file.getOriginalFilename();

StringnewName=UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."));

try{

file.transferTo(newFile(folder,newName));

returnreq.getScheme()+"://"+req.getRemoteHost()+":"+req.getServerPort()+"/img"+format+newName;

}catch(IOExceptione){

e.printStackTrace();

}

return"error";

}

@PostMapping("/upload3")

publicStringfileUpload3(HttpServletRequestreq)throwsIOException,ServletException{

Stringother_param=req.getParameter("other_param");

System.out.println("other_param="+other_param);

Stringformat=sdf.format(newDate());

StringrealPath=req.getServletContext().getRealPath("/img")+format;

Filefolder=newFile(realPath);

if(!folder.exists()){

folder.mkdirs();

}

PartfilePart=req.getPart("file");

StringoldName=filePart.getSubmittedFileName();

StringnewName=UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."));

try{

filePart.write(realPath+newName);

returnreq.getScheme()+"://"+req.getRemoteHost()+":"+req.getServerPort()+"/img"+format+newName;

}catch(IOExceptione){

e.printStackTrace();

}

return"error";

}

}

我这里一共提供了三个文件上传接口,其实最终都是通过 StandardServletMultipartResolver 进行处理的。

第一个接口是我们在 SpringMVC 框架中常见的一种文件上传处理方式,直接在参数中写上 MultipartFile,这个 MultipartFile 其实就是从当前请求中解析出来的,具体负责参数解析工作的就是 RequestParamMethodArgumentResolver。

第二个接口其实是一种古老的文件上传实现方案,参数就是普通的 HttpServletRequest,然后在参数里边,我们再手动利用 StandardServletMultipartResolver 实例进行解析(

这种情况可以不用自己 new 一个 StandardServletMultipartResolver 实例,直接将 Spring 容器中的注入进来即可

)。

第三个接口我们就利用了 Servlet3.0 的 API,调用 getPart 获取文件,然后再调用对象的 write 方法将文件写出去即可。

大致上一看,感觉办法还挺多,其实仔细看,万变不离其宗,一会我们看完源码,相信小伙伴们还能变化出更多写法。

CommonsMultipartResolver

CommonsMultipartResolver 估计很多人都比较熟悉,这个兼容性很好,就是有点过时了。使用 CommonsMultipartResolver 需要我们首先引入 commons-fileupload 依赖:

commons-fileupload

commons-fileupload

1.4

然后在 SpringMVC 的配置文件中提供 CommonsMultipartResolver 实例,如下:

-->

接下来开发文件上传接口就行了:

@PostMapping("/upload")

publicStringfileUpload(MultipartFilefile,HttpServletRequestreq){

Stringformat=sdf.format(newDate());

StringrealPath=req.getServletContext().getRealPath("/img")+format;

Filefolder=newFile(realPath);

if(!folder.exists()){

folder.mkdirs();

}

StringoldName=file.getOriginalFilename();

StringnewName=UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."));

try{

file.transferTo(newFile(folder,newName));

returnreq.getScheme()+"://"+req.getRemoteHost()+":"+req.getServerPort()+"/img"+format+newName;

}catch(IOExceptione){

e.printStackTrace();

}

return"error";

}

这个就没啥好说,比较容易。

文件上传这块松哥之前在视频中也和大家分享过,公号后台回复 ssm 可以查看视频详情。

用法掌握了,接下来我们来看原理。

2.StandardServletMultipartResolver

不废话,直接来看看源码:

publicclassStandardServletMultipartResolverimplementsMultipartResolver{

privatebooleanresolveLazily=false;

publicvoidsetResolveLazily(booleanresolveLazily){

this.resolveLazily=resolveLazily;

}

@Override

publicbooleanisMultipart(HttpServletRequestrequest){

returnStringUtils.startsWithIgnoreCase(request.getContentType(),"multipart/");

}

@Override

publicMultipartHttpServletRequestresolveMultipart(HttpServletRequestrequest)throwsMultipartException{

returnnewStandardMultipartHttpServletRequest(request,this.resolveLazily);

}

@Override

publicvoidcleanupMultipart(MultipartHttpServletRequestrequest){

if(!(requestinstanceofAbstractMultipartHttpServletRequest)||

((AbstractMultipartHttpServletRequest)request).isResolved()){

try{

for(Partpart:request.getParts()){

if(request.getFile(part.getName())!=null){

part.delete();

}

}

}

catch(Throwableex){

}

}

}

}

这里满打满算就四个方法,其中一个还是 set 方法,我们来看另外三个功能性方法:

isMultipart:这个方法主要是用来判断当前请求是不是文件上传请求,这里的判断思路很简单,就看请求的 content-type 是不是以

multipart/

开头,如果是,则这就是一个文件上传请求,否则就不是文件上传请求。

resolveMultipart:这个方法负责将当前请求封装一个 StandardMultipartHttpServletRequest 对象然后返回。

cleanupMultipart:这个方法负责善后,主要完成了缓存的清理工作。

在这个过程中涉及到 StandardMultipartHttpServletRequest 对象,我们也来稍微说一下:

publicStandardMultipartHttpServletRequest(HttpServletRequestrequest,booleanlazyParsing)

throwsMultipartException{

super(request);

if(!lazyParsing){

parseRequest(request);

}

}

privatevoidparseRequest(HttpServletRequestrequest){

try{

Collectionparts=request.getParts();

this.multipartParameterNames=newLinkedHashSet<>(parts.size());

MultiValueMapfiles=newLinkedMultiValueMap<>(parts.size());

for(Partpart:parts){

StringheaderValue=part.getHeader(HttpHeaders.CONTENT_DISPOSITION);

ContentDispositiondisposition=ContentDisposition.parse(headerValue);

Stringfilename=disposition.getFilename();

if(filename!=null){

if(filename.startsWith("=?")&&filename.endsWith("?=")){

filename=MimeDelegate.decode(filename);

}

files.add(part.getName(),newStandardMultipartFile(part,filename));

}

else{

this.multipartParameterNames.add(part.getName());

}

}

setMultipartFiles(files);

}

catch(Throwableex){

handleParseFailure(ex);

}

}

StandardMultipartHttpServletRequest 对象在构建的过程中,会自动进行请求解析,调用 getParts 方法获取所有的项,然后进行判断,将文件和普通参数分别保存下来备用。

这块的逻辑比较简单。

3.CommonsMultipartResolver

再来看 CommonsMultipartResolver。

先来看它的 isMultipart 方法:

@Override

publicbooleanisMultipart(HttpServletRequestrequest){

returnServletFileUpload.isMultipartContent(request);

}

publicstaticfinalbooleanisMultipartContent(

HttpServletRequestrequest){

if(!POST_METHOD.equalsIgnoreCase(request.getMethod())){

returnfalse;

}

returnFileUploadBase.isMultipartContent(newServletRequestContext(request));

}

ServletFileUpload.isMultipartContent 方法其实就在我们引入的

commons-fileupload

包中。它的判断逻辑分两步:首先检查是不是 POST 请求,然后检查 content-type 是不是以

multipart/

开始。

再来看它的 resolveMultipart 方法:

@Override

publicMultipartHttpServletRequestresolveMultipart(finalHttpServletRequestrequest)throwsMultipartException{

if(this.resolveLazily){

returnnewDefaultMultipartHttpServletRequest(request){

@Override

protectedvoidinitializeMultipart(){

MultipartParsingResultparsingResult=parseRequest(request);

setMultipartFiles(parsingResult.getMultipartFiles());

setMultipartParameters(parsingResult.getMultipartParameters());

setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());

}

};

}

else{

MultipartParsingResultparsingResult=parseRequest(request);

returnnewDefaultMultipartHttpServletRequest(request,parsingResult.getMultipartFiles(),

parsingResult.getMultipartParameters(),parsingResult.getMultipartParameterContentTypes());

}

}

根据 resolveLazily 属性值,选择两种不同的策略将当前对象重新构建成一个 DefaultMultipartHttpServletRequest 对象。如果 resolveLazily 为 true,则在 initializeMultipart 方法中进行请求解析,否则先解析,再构建 DefaultMultipartHttpServletRequest 对象。

具体的解析方法如下:

protectedMultipartParsingResultparseRequest(HttpServletRequestrequest)throwsMultipartException{

Stringencoding=determineEncoding(request);

FileUploadfileUpload=prepareFileUpload(encoding);

try{

ListfileItems=((ServletFileUpload)fileUpload).parseRequest(request);

returnparseFileItems(fileItems,encoding);

}

catch(FileUploadBase.SizeLimitExceededExceptionex){

//...

}

}

protectedMultipartParsingResultparseFileItems(ListfileItems,Stringencoding){

MultiValueMapmultipartFiles=newLinkedMultiValueMap<>();

MapmultipartParameters=newHashMap<>();

MapmultipartParameterContentTypes=newHashMap<>();

for(FileItemfileItem:fileItems){

if(fileItem.isFormField()){

Stringvalue;

StringpartEncoding=determineEncoding(fileItem.getContentType(),encoding);

try{

value=fileItem.getString(partEncoding);

}

catch(UnsupportedEncodingExceptionex){

value=fileItem.getString();

}

String[]curParam=multipartParameters.get(fileItem.getFieldName());

if(curParam==null){

multipartParameters.put(fileItem.getFieldName(),newString[]{value});

}

else{

String[]newParam=StringUtils.addStringToArray(curParam,value);

multipartParameters.put(fileItem.getFieldName(),newParam);

}

multipartParameterContentTypes.put(fileItem.getFieldName(),fileItem.getContentType());

}

else{

CommonsMultipartFilefile=createMultipartFile(fileItem);

multipartFiles.add(file.getName(),file);

}

}

returnnewMultipartParsingResult(multipartFiles,multipartParameters,multipartParameterContentTypes);

}

这里的解析就是首先获取到 FileItem 集合,然后调用 parseFileItems 方法进行进一步的解析。在进一步的解析中,会首先判断这是文件还是普通参数,如果是普通参数,则保存到 multipartParameters 中,具体保存过程中还会判断是否为数组,然后再将参数的 ContentType 保存到 multipartParameterContentTypes 中,文件则保存到 multipartFiles 中,最后由三个 Map 构成一个 MultipartParsingResult 对象并返回。

至此,StandardServletMultipartResolver 和 CommonsMultipartResolver 源码就和大家说完了,可以看到,还是比较容易的。

4.解析流程

最后,我们再来梳理一下解析流程。

以如下接口为例(因为在实际开发中一般都是通过如下方式上传文件):

@PostMapping("/upload")

publicStringfileUpload(MultipartFilefile,HttpServletRequestreq){

Stringformat=sdf.format(newDate());

StringrealPath=req.getServletContext().getRealPath("/img")+format;

Filefolder=newFile(realPath);

if(!folder.exists()){

folder.mkdirs();

}

StringoldName=file.getOriginalFilename();

StringnewName=UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."));

try{

file.transferTo(newFile(folder,newName));

returnreq.getScheme()+"://"+req.getRemoteHost()+":"+req.getServerPort()+"/img"+format+newName;

}catch(IOExceptione){

e.printStackTrace();

}

return"error";

}

这里 MultipartFile 对象主要就是在参数解析器中获取的,关于参数解析器,大家可以参考:深入分析 SpringMVC 参数解析器 一文,这里涉及到的参数解析器是 RequestParamMethodArgumentResolver。

在 RequestParamMethodArgumentResolver#resolveName 方法中有如下一行代码:

if(servletRequest!=null){

ObjectmpArg=MultipartResolutionDelegate.resolveMultipartArgument(name,parameter,servletRequest);

if(mpArg!=MultipartResolutionDelegate.UNRESOLVABLE){

returnmpArg;

}

}

这个方法会进行请求解析,返回 MultipartFile 对象或者 MultipartFile 数组。

@Nullable

publicstaticObjectresolveMultipartArgument(Stringname,MethodParameterparameter,HttpServletRequestrequest)

throwsException{

MultipartHttpServletRequestmultipartRequest=

WebUtils.getNativeRequest(request,MultipartHttpServletRequest.class);

booleanisMultipart=(multipartRequest!=null||isMultipartContent(request));

if(MultipartFile.class==parameter.getNestedParameterType()){

if(!isMultipart){

returnnull;

}

if(multipartRequest==null){

multipartRequest=newStandardMultipartHttpServletRequest(request);

}

returnmultipartRequest.getFile(name);

}

elseif(isMultipartFileCollection(parameter)){

if(!isMultipart){

returnnull;

}

if(multipartRequest==null){

multipartRequest=newStandardMultipartHttpServletRequest(request);

}

Listfiles=multipartRequest.getFiles(name);

return(!files.isEmpty()?files:null);

}

elseif(isMultipartFileArray(parameter)){

if(!isMultipart){

returnnull;

}

if(multipartRequest==null){

multipartRequest=newStandardMultipartHttpServletRequest(request);

}

Listfiles=multipartRequest.getFiles(name);

return(!files.isEmpty()?files.toArray(newMultipartFile[0]):null);

}

elseif(Part.class==parameter.getNestedParameterType()){

if(!isMultipart){

returnnull;

}

returnrequest.getPart(name);

}

elseif(isPartCollection(parameter)){

if(!isMultipart){

returnnull;

}

Listparts=resolvePartList(request,name);

return(!parts.isEmpty()?parts:null);

}

elseif(isPartArray(parameter)){

if(!isMultipart){

returnnull;

}

Listparts=resolvePartList(request,name);

return(!parts.isEmpty()?parts.toArray(newPart[0]):null);

}

else{

returnUNRESOLVABLE;

}

}

首先获取 multipartRequest 对象,然后再从中获取文件或者文件数组。如果我们使用 StandardServletMultipartResolver 做文件上传,这里获取到的 multipartRequest 就是 StandardMultipartHttpServletRequest;如果我们使用 CommonsMultipartResolver 做文件上传,这里获取到的 multipartRequest 就是 DefaultMultipartHttpServletRequest。

5.小结

好啦,今天就和大家简单分析了下两个文件上传组件的源码,至此,SpringMVC 九大组件我们已经分析了 7 个啦,还剩两个,即将完结,完结后松哥会整理成 pdf 分享给大家。

欧大师铝合金门窗定制

欧大师系统门窗厂家

高端系统门窗品牌最新排名

高档隔音降噪门窗

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 931614094@qq.com 举报,一经查实,本站将立刻删除。