加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_郴州站长网 (http://www.0735zz.com/)- 云通信、区块链、物联设备、云计算、站长网!
当前位置: 首页 > 服务器 > 系统 > 正文

什么年代了,你还不了解 Servlet3.0 中的文件上传方式?

发布时间:2022-06-21 11:11:48 所属栏目:系统 来源:互联网
导读:其实文件上传这块松哥之前和大家聊过很多次了,这次因为最近正在进行 SpringMVC 的源码分析,所以又再次把这个话题拉出来鞭尸,不过这次松哥想从源码角度来聊聊这个话题。 理解源码的前提是先会用,所以我们还是先来看看用法,然后再来分析源码。 1.两种文件
  其实文件上传这块松哥之前和大家聊过很多次了,这次因为最近正在进行 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。
 
  配置方式如下:
 
  复制
  <servlet>
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</serv
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring-servlet.xml</param-value>
      </init-param>
      <multipart-config>
          <location>/tmp</location>
          <max-file-size>1024</max-file-size>
          <max-request-size>10240</max-request-size>
      </multipart-config>
  </servlet>
  <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  9.
  10.
  11.
  12.
  13.
  14.
  15.
  16.
  17.
  然后在 SpringMVC 的配置文件中提供一个 StandardServletMultipartResolver 实例,注意该实例的 id 必须为 multipartResolver(具体原因参见:SpringMVC 初始化流程分析一文)。
 
  复制
  <bean class="org.springframework.web.multipart.support.StandardServletMultipartResolver" id="multipartResolver">
  </bean>
  1.
  2.
  配置完成后,我们就可以开发一个文件上传接口了,如下:
 
  复制
  @RestController
  public class FileUploadController {
      SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/");
   
      @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";
      }
   
      @PostMapping("/upload2")
      public String fileUpload2(HttpServletRequest req) throws IOException, ServletException {
          StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
          MultipartFile file = resolver.resolveMultipart(req).getFile("file");
          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";
      }
   
      @PostMapping("/upload3")
      public String fileUpload3(HttpServletRequest req) throws IOException, ServletException {
          String other_param = req.getParameter("other_param");
          System.out.println("other_param = " + other_param);
          String format = sdf.format(new Date());
          String realPath = req.getServletContext().getRealPath("/img") + format;
          File folder = new File(realPath);
          if (!folder.exists()) {
              folder.mkdirs();
          }
          Part filePart = req.getPart("file");
          String oldName = filePart.getSubmittedFileName();
          String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
          try {
              filePart.write(realPath + newName);
              return req.getScheme() + "://" + req.getRemoteHost() + ":" + req.getServerPort() + "/img" + format + newName;
          } catch (IOException e) {
              e.printStackTrace();
          }
          return "error";
      }
  }
  1.
  2.
  3.
  4.
  5.
  6.
  7.
  8.
  9.
  10.
  11.
  12.
  13.
  14.
  15.
  16.
  17.
  18.
  19.
  20.
  21.
  22.
  23.
  24.
  25.
  26.
  27.
  28.
  29.
  30.
  31.
  32.
  33.
  34.
  35.
  36.
  37.
  38.
  39.
  40.
  41.
  42.
  43.
  44.
  45.
  46.
  47.
  48.
  49.
  50.
  51.
  52.
  53.
  54.
  55.
  56.
  57.
  58.
  59.
  60.
  61.
  62.
  63.
  64.
  65.
  66.
  我这里一共提供了三个文件上传接口,其实最终都是通过 StandardServletMultipartResolver 进行处理的。
 
  第一个接口是我们在 SpringMVC 框架中常见的一种文件上传处理方式,直接在参数中写上 MultipartFile,这个 MultipartFile 其实就是从当前请求中解析出来的,具体负责参数解析工作的就是 RequestParamMethodArgumentResolver。
  第二个接口其实是一种古老的文件上传实现方案,参数就是普通的 HttpServletRequest,然后在参数里边,我们再手动利用 StandardServletMultipartResolver 实例进行解析(这种情况可以不用自己 new 一个 StandardServletMultipartResolver 实例,直接将 Spring 容器中的注入进来即可)。
  第三个接口我们就利用了 Servlet3.0 的 API,调用 getPart 获取文件,然后再调用对象的 write 方法将文件写出去即可。
  大致上一看,感觉办法还挺多,其实仔细看,万变不离其宗,一会我们看完源码,相信小伙伴们还能变化出更多写法。
 
  CommonsMultipartResolver
 
  CommonsMultipartResolver 估计很多人都比较熟悉,这个兼容性很好,就是有点过时了。使用 CommonsMultipartResolver 需要我们首先引入 commons-fileupload 依赖:
 
  复制
  <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
  </dependency>
  1.
  2.
  3.
  4.
  5.
  然后在 SpringMVC 的配置文件中提供 CommonsMultipartResolver 实例,如下:
 
  复制
  <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">-->
  </bean>
  1.
  2.
  接下来开发文件上传接口就行了:
 
  复制
  @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";
   这个就没啥好说,比较容易。
 
  文件上传这块松哥之前在视频中也和大家分享过,公号后台回复 ssm 可以查看视频详情。
 
  用法掌握了,接下来我们来看原理。
 
  2.StandardServletMultipartResolver
  不废话,直接来看看源码:
 
  复制
  public class StandardServletMultipartResolver implements MultipartResolver {
   private boolean resolveLazily = false;
   public void setResolveLazily(boolean resolveLazily) {
    this.resolveLazily = resolveLazily;
   }
   @Override
   public boolean isMultipart(HttpServletRequest request) {
    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()) {
     try {
      for (Part part : request.getParts()) {
       if (request.getFile(part.getName()) != null) {
        part.delete();
       }
      }
     }
     catch (Throwable ex) {
     }
    }
   }
   这里满打满算就四个方法,其中一个还是 set 方法,我们来看另外三个功能性方法:
 
  isMultipart:这个方法主要是用来判断当前请求是不是文件上传请求,这里的判断思路很简单,就看请求的 content-type 是不是以 multipart/ 开头,如果是,则这就是一个文件上传请求,否则就不是文件上传请求。
 
  resolveMultipart:这个方法负责将当前请求封装一个 StandardMultipartHttpServletRequest 对象然后返回。
 
  cleanupMultipart:这个方法负责善后,主要完成了缓存的清理工作。
 
  在这个过程中涉及到 StandardMultipartHttpServletRequest 对象,我们也来稍微说一下:
 
  复制
  public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
    throws MultipartException {
   super(request);
   if (!lazyParsing) {
    parseRequest(request);
   }
  }
  private void parseRequest(HttpServletRequest request) {
   try {
    Collection<Part> parts = request.getParts();
    this.multipartParameterNames = new LinkedHashSet<>(parts.size());
    MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
    for (Part part : parts) {
     String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
     ContentDisposition disposition = ContentDisposition.parse(headerValue);
     String filename = disposition.getFilename();
     if (filename != null) {
      if (filename.startsWith("=?") && filename.endsWith("?=")) {
       filename = MimeDelegate.decode(filename);
      }
      files.add(part.getName(), new StandardMultipartFile(part, filename));
     }
     else {
      this.multipartParameterNames.add(part.getName());
     }
    }
    setMultipartFiles(files);
   }
   catch (Throwable ex) {
    handleParseFailure(ex);
   }
  }
   StandardMultipartHttpServletRequest 对象在构建的过程中,会自动进行请求解析,调用 getParts 方法获取所有的项,然后进行判断,将文件和普通参数分别保存下来备用。
 
  这块的逻辑比较简单。

(编辑:开发网_郴州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读