1. 首页 > 快讯

Servlet 3.0 文件上传进阶指南

大家好,如果您还对Servlet 3.0 文件上传进阶指南不太了解,没有关系,今天就由本站为大家分享Servlet 3.0 文件上传进阶指南的知识,包括的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!

[[393335]]

其实宋哥之前已经和大家聊过很多次关于文件上传的事情了。这次因为我们目前正在做SpringMVC的源码分析,所以我们又提起了这个话题,不过这次宋哥想从源码的角度来谈谈。我们来谈谈这个话题。

了解源码的前提是先会用,所以我们先看一下用法,然后再分析源码。

1.两种文件解析方案

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

StandardServletMultipartResolverCommonsMultipartResolverStandardServletMultipartResolver支持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。

配置如下:

namespringmvcnameorg.springframework.web.servlet.DispatcherServletnamecontextConfigLocationnameclasspath:spring-servlet.xml/tmpmax-file-size1024max-file-sizemax-request-size10240max-request-sizenamespringmvcname/然后在SpringMVC配置文件中提供一个StandardServletMultipartResolver实例,注意该id instance 必须是multipartResolver(具体原因参见:SpringMVC初始化流程分析文章)。

'org.springframework.web.multipart.support.StandardServletMultipartResolver'id='multipartResolver'配置完成后,我们就可以开发文件上传接口了,如下:

@RestControllerpublicclassFileUploadController{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('文件');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.getSubscribedFileName();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实例来进行解析(此时不需要自己新建一个StandardServletMultipartResolver实例,直接添加Spring容器注入进去即可)。对于第三个接口,我们使用Servlet3.0的API,调用getPart获取文件,然后调用对象的write方法写入文件。乍一看,方法好像还蛮多的。事实上,如果你仔细观察,你会发现一切都保持不变。我们看完源码后,相信小伙伴们能够想出更多的写法。

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

commons-fileuploadcommons-fileupload1.4然后在SpringMVC配置文件中提供了一个CommonsMultipartResolver实例,如下:

'org.springframework.web.multipart.commons.CommonsMultipartResolver'id='multipartResolver'--接下来开发文件上传接口:

@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';}这个没什么好说的,就是更轻松。

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

2.StandardServletMultipartResolver

废话不多说,我们看一下源码:

publicclassStandardServletMultipartResolverimplementsMultipartResolver{privatebooleanresolveLazily=false;publicvoidsetResolveLazily(booleanresolveLazily){this.resolveLazily=resolveLazily;}@OverridepublicbooleanisMultipart(HttpServletRequestrequest){returnStringUtils.startsWithIgnoreCase(request.getCon) tentType(),'multipart/');}@OverridepublicMultipartHttpServletRequestresolveMultipart(HttpServletRequestrequest) throwsMultipartException{returnnewStandardMultipartHttpServletRequest(请求,this.resolveLazily);}@OverridepublicvoidcleanupMultipart(MultipartHttpServletRequestrequest){if(!(requestinstanceofAbstractMultipartHttpServletRequest)||((AbstractMultipartHttpServletRequest)request).isResolved()){try{for(Partpart:request.getParts()){if(re quest.获取文件(part.getName())!=null){part.delete();}}}catch(Throwableex){}}}} 这里只有四个方法,其中之一就是set 方法。我们来看看其他三个函数。性方法:

isMultipart:该方法主要用于判断当前请求是否为文件上传请求。这里的判断很简单。这取决于请求的content-type是否以multipart/开头。如果是,则为文件上传请求。否则就是文件上传请求。这不是文件上传请求。

resolveMultipart:该方法负责将当前请求封装成StandardMultipartHttpServletRequest对象,然后返回。

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

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

publicStandardMultipartHttpServletRequest(HttpServletRequestrequest,booleanlazyParsing) throwsMultipartException{super(request);if(!lazyParsing){pars eRequest(request);}}privatevoidparseRequest(HttpServletRequestrequest){try{Collectionparts=request.getParts();this.multipartParameterNames=newLinkedH ashSet(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方法:

@OverridepublicbooleanisMultipart(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方法:

@OverridepublicMultipartHttpServletRequestresolveMultipart(finalHttpServletRequestrequest)throwsMultipartException{if(this.resolveLazily){returnnewDefaultMultipartHttpServletRequest(request){@OverrideprotectedvoidinitializeMultipart(){MultipartParsingResultparsingResult=parseRequest(request);setMultipartFiles(parsingResult.getMultipartFiles());setMultipartParameters(parsingResult.get) MultipartParameters());设置MultipartParameterContentTypes (parsingResult.getMultipartParameterContentTypes());}};}else{MultipartParsingResultparsingResult=parseRequest(request);returnnewDefaultMultipartHttpServletRequest(request,parsingResult.getMultipartFiles(),parsingResult.getMultipartParameters(),par singResult.getMultipartParameterContentTypes());}}根据res

olveLazily 属性值,选择两种不同的策略将当前对象重新构建成一个 DefaultMultipartHttpServletRequest 对象。如果 resolveLazily 为 true,则在 initializeMultipart 方法中进行请求解析,否则先解析,再构建 DefaultMultipartHttpServletRequest 对象。 具体的解析方法如下: protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {  String encoding = determineEncoding(request);  FileUpload fileUpload = prepareFileUpload(encoding);  try {   List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);   return parseFileItems(fileItems, encoding);  }  catch (FileUploadBase.SizeLimitExceededException ex) {   //...  } } protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {  MultiValueMap multipartFiles = new LinkedMultiValueMap<>();  Map multipartParameters = new HashMap<>();  Map multipartParameterContentTypes = new HashMap<>();  for (FileItem fileItem : fileItems) {   if (fileItem.isFormField()) {    String value;    String partEncoding = determineEncoding(fileItem.getContentType(), encoding);    try {     value = fileItem.getString(partEncoding);    }    catch (UnsupportedEncodingException ex) {     value = fileItem.getString();    }    String[] curParam = multipartParameters.get(fileItem.getFieldName());    if (curParam == null) {     multipartParameters.put(fileItem.getFieldName(), new String[] {value});    }    else {     String[] newParam = StringUtils.addStringToArray(curParam, value);     multipartParameters.put(fileItem.getFieldName(), newParam);    }    multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());   }   else {    CommonsMultipartFile file = createMultipartFile(fileItem);    multipartFiles.add(file.getName(), file);   }  }  return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes); } 这里的解析就是首先获取到 FileItem 集合,然后调用 parseFileItems 方法进行进一步的解析。在进一步的解析中,会首先判断这是文件还是普通参数,如果是普通参数,则保存到 multipartParameters 中,具体保存过程中还会判断是否为数组,然后再将参数的 ContentType 保存到 multipartParameterContentTypes 中,文件则保存到 multipartFiles 中,最后由三个 Map 构成一个 MultipartParsingResult 对象并返回。 至此,StandardServletMultipartResolver 和 CommonsMultipartResolver 源码就和大家说完了,可以看到,还是比较容易的。

4.解析流程

最后,我们再来梳理一下解析流程。 以如下接口为例(因为在实际开发中一般都是通过如下方式上传文件): @PostMapping("/upload") public String fileUpload(MultipartFile file, HttpServletRequest req) {     String format = sdf.format(new Date());     String realPath = req.getServletContext().getRealPath("/img") + format;     File folder = new File(realPath);     if (!folder.exists()) {         folder.mkdirs();     }     String oldName = file.getOriginalFilename();     String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));     try {         file.transferTo(new File(folder, newName));         return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName;     } catch (IOException e) {         e.printStackTrace();     }     return "error"; } 这里 MultipartFile 对象主要就是在参数解析器中获取的,关于参数解析器,大家可以参考:深入分析 SpringMVC 参数解析器 一文,这里涉及到的参数解析器是 RequestParamMethodArgumentResolver。 在 RequestParamMethodArgumentResolver#resolveName 方法中有如下一行代码: if (servletRequest != null) {  Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);  if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {   return mpArg;  } } 这个方法会进行请求解析,返回 MultipartFile 对象或者 MultipartFile 数组。 @Nullable public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)   throws Exception {  MultipartHttpServletRequest multipartRequest =    WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);  boolean isMultipart = (multipartRequest != null || isMultipartContent(request));  if (MultipartFile.class == parameter.getNestedParameterType()) {   if (!isMultipart) {    return null;   }   if (multipartRequest == null) {    multipartRequest = new StandardMultipartHttpServletRequest(request);   }   return multipartRequest.getFile(name);  }  else if (isMultipartFileCollection(parameter)) {   if (!isMultipart) {    return null;   }   if (multipartRequest == null) {    multipartRequest = new StandardMultipartHttpServletRequest(request);   }   List files = multipartRequest.getFiles(name);   return (!files.isEmpty() ? files : null);  }  else if (isMultipartFileArray(parameter)) {   if (!isMultipart) {    return null;   }   if (multipartRequest == null) {    multipartRequest = new StandardMultipartHttpServletRequest(request);   }   List files = multipartRequest.getFiles(name);   return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);  }  else if (Part.class == parameter.getNestedParameterType()) {   if (!isMultipart) {    return null;   }   return request.getPart(name);  }  else if (isPartCollection(parameter)) {   if (!isMultipart) {    return null;   }   List parts = resolvePartList(request, name);   return (!parts.isEmpty() ? parts : null);  }  else if (isPartArray(parameter)) {   if (!isMultipart) {    return null;   }   List parts = resolvePartList(request, name);   return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);  }  else {   return UNRESOLVABLE;  } } 首先获取 multipartRequest 对象,然后再从中获取文件或者文件数组。如果我们使用 StandardServletMultipartResolver 做文件上传,这里获取到的 multipartRequest 就是 StandardMultipartHttpServletRequest;如果我们使用 CommonsMultipartResolver 做文件上传,这里获取到的 multipartRequest 就是 DefaultMultipartHttpServletRequest。  

文章分享结束,Servlet 3.0 文件上传进阶指南和的答案你都知道了吗?欢迎再次光临本站哦!

用户评论

别伤我i

以前好像一直都是在使用Servlet3.0处理文件上传。

    有15位网友表示赞同!

野兽之美

我最近才开始学习Servlet,这个地方还挺实用啊。

    有13位网友表示赞同!

墨染天下

学习一下3.0新版本的文件上传方法好呀,知识要更新!

    有17位网友表示赞同!

清原

对Java项目开发来说,掌握Servlet技术还是很有必要的。

    有11位网友表示赞同!

高冷低能儿

这篇文章肯定能给我解决一些文件上传的难题,太赞了!

    有13位网友表示赞同!

罪歌

我还是更习惯用最新的技术来进行开发,这样安全一点吧。

    有11位网友表示赞同!

从此我爱的人都像你

原来文件上传的方式还有这么多讲究啊,我一直以为很简单的。

    有15位网友表示赞同!

南初

Servlet3.0确实在文件上传方面做了很多优化吧,效率应该会更高一些吧!

    有9位网友表示赞同!

蔚蓝的天空〃没有我的翅膀

感觉这篇文章写的比较全面,从基础到进阶都讲的很透。

    有9位网友表示赞同!

断秋风

这个技术我之前就听说过,现在看来真的挺好用的。

    有15位网友表示赞同!

嘲笑!

对于入门开发人员来说,了解Servlet3.0的文件上传方式很重要啊!

    有20位网友表示赞同!

一生荒唐

看了这篇文章后,感觉我的代码可以优化了,效率应该会提高不少哦!

    有17位网友表示赞同!

哥帅但不是蟋蟀

文件上传是一个常用的功能,掌握 Servlet 的文件上传技巧非常重要。

    有15位网友表示赞同!

盲从于你

学习新的技术总是一件很有成就感的!加油!

    有19位网友表示赞同!

那伤。眞美

希望这篇文章能帮助我快速掌握Servlet3.0的文件上传方式!

    有6位网友表示赞同!

限量版女汉子

做开发的肯定都要了解这个东西吧,现在技术更新太快了。

    有10位网友表示赞同!

你很爱吃凉皮

对一些老项目进行升级和维护,学习一下 Servlet 3.0 文件上传也是蛮重要的!

    有11位网友表示赞同!

本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/7246.html

联系我们

在线咨询:点击这里给我发消息

微信号:666666