SpringMVC上传方式与异常处理

😂 这篇文章最后更新于1728天前,您需要注意相关的内容是否还可用。
目录导航
  • SpringMVC文件上传
    • 传统方式上传文件
    • SpringMVC方式上传
      • 文件解析器配置
      • 上传大小异常处理
      • 上传格式异常处理
      • 拦截器返回Json数据
      • 用MultipartFile的transferTo
      • CommonsMultipartFile的getFileItem()
    • 多文件上传
    • 跨服务器上传
      • 实例代码
      • 关于报错
    • 文件上传注意事项
  • SpringMVC文件上传

    所需依赖

    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>

    传统方式上传文件

    通用上传页

    <h3>文件上传</h3>
    <form action="user/fileupload" method="post" enctype="multipart/form-data">
    选择文件:<input type="file" name="upload"/><br/>
    <input type="submit" value="上传文件"/>
    </form>

    从request中获取文件

        @RequestMapping("/fileupload")
        public String fileuoload1(HttpServletRequest req) throws Exception {
            StandardMultipartHttpServletRequest request = (StandardMultipartHttpServletRequest) req;
            System.out.println("文件上传...");
            // 使用fileupload组件完成文件上传
            // 上传的位置 获取最大的目录 即webapp/WebRoot目录
            String path = request.getSession().getServletContext().getRealPath("/uploads/");
            // 判断,该路径是否存在
            File file = new File(path);
            if (!file.exists()) {
                // 创建该文件夹
                file.mkdirs();
            }
            // 解析request对象,获取上传文件项
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            // 解析request
            List<FileItem> items = upload.parseRequest(request);
            // 遍历
            for (FileItem item : items) {
                // 进行判断,当前item对象是否是上传文件项
                if (item.isFormField()) {
                    // 说明普通表单向
                } else {
                    // 说明上传文件项
                    // 获取上传文件的名称
                    String filename = item.getName();
                    // 把文件的名称设置唯一值,uuid
                    String uuid = UUID.randomUUID().toString().replace("-", "");
                    filename = uuid + "_" + filename;
                    // 完成文件上传
                    item.write(new File(path, filename));
                    // 删除临时文件(大于10k的都会生成临时文件 因此要删除)
                    item.delete();
                }
            }
            return "success";
        }

    SpringMVC方式上传

    文件解析器配置

    springmvc.xml配置文件解析器:

    <!-- 配置文件解析器对象,要求id名称必须是multipartResolver 不能改-->
    <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10485760"/>
    </bean>

    更多配置

    <!-- 多部分文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
         <property name="maxUploadSize" value="104857600" />
         <property name="maxInMemorySize" value="4096" />
         <property name="defaultEncoding" value="UTF-8"></property>
    </bean>

    上述配置文件单位是Byte,10M就是10x1024x1024相乘的值

    上传大小异常处理

        <!--配置文件解析器对象-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="maxUploadSize" value="51200" />
        </bean>
        <!-- 1.在文件上传解析时发现异常,此时还没有进入到Controller方法中 -->
        <bean id="exceptionResolver" class= "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            <property name="exceptionMappings">
                <props>
                    <!-- 遇到MaxUploadSizeExceededException异常时,跳转到error.jsp页面 -->
                    <prop key= "org.springframework.web.multipart.MaxUploadSizeExceededException">/error </prop>
                </props>
            </property>
        </bean>

    上述即文件超过50k就跳转错误页面,报错就不会执行Controller里的代码了。

    也可以自定义异常类,如下:

    /**
     * 自定义异常处理器类
     */
    public class ExceptionHandler implements HandlerExceptionResolver{
        /**
         * 处理上传文件大小超过限制抛出的异常
         */
        @Override
        public ModelAndView resolveException(HttpServletRequest req,
                HttpServletResponse res, Object ob,Exception ex) {
            ModelAndView mv=new ModelAndView();
            //判断异常类型,来跳转不同页面
            if (ex instanceof MaxUploadSizeExceededException){ 
                //指定错误信息
                mv.addObject("errormessage", "上传文件过大");
                //设置跳转视图
                mv.setViewName("userEdit");
                return mv;
            }  
            //其他异常
            return null;
        }
    }

    然后将其加入springmvc.xml中即可

    <bean class="com.cheng.exception.ExceptionHandler" />

    由于Tomcat问题,7.0系列版本在上传大文件如果超过大小限制会直接页面连接断开。这时可以不用SpringMVC自带的解析器限制文件大小,而是通过代码实现。

    配置拦截器

    public class FileUploadInterceptor extends HandlerInterceptorAdapter {
        private long maxSize;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //判断是否文件上传
            if(request!=null && ServletFileUpload.isMultipartContent(request)) {
                ServletRequestContext ctx = new ServletRequestContext(request);
                //获取上传文件尺寸大小
                long requestSize = ctx.contentLength();
                if (requestSize > maxSize) {
                    //当上传文件大小超过指定大小限制后,模拟抛出MaxUploadSizeExceededException异常
                    throw new MaxUploadSizeExceededException(maxSize);
                }
            }
            return true;
        }
        public void setMaxSize(long maxSize) {
            this.maxSize = maxSize;
        }
    }

    配置解析器

        <!-- 配置文件上传类型解析器 multipartResolver-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
    
        <mvc:interceptors>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="cn.cheng.controller.FileUploadInterceptor">
                    <!-- 设定限制的文件上传大小 -->
                    <property name="maxSize" value="51200"/>
                </bean>
            </mvc:interceptor>
        </mvc:interceptors>

    上传格式异常处理

    第一种在Controller方法中限制,但每次都写太过于麻烦,第二种使用拦截器,下列是后者:

    public class FileTypeInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request,
                                 HttpServletResponse response, Object handler)throws Exception {
            boolean flag= true;
            // 判断是否为文件上传请求
            if (request instanceof MultipartHttpServletRequest) {
                MultipartHttpServletRequest multipartRequest =
                        (MultipartHttpServletRequest) request;
                Map<String, MultipartFile> files =
                        multipartRequest.getFileMap();
                Iterator<String> iterator = files.keySet().iterator();
                //对多部件请求资源进行遍历
                while (iterator.hasNext()) {
                    String formKey = (String) iterator.next();
                    MultipartFile multipartFile =
                            multipartRequest.getFile(formKey);
                    String filename=multipartFile.getOriginalFilename();
                    //判断是否为限制文件类型
                    if (! checkFile(filename)) {
                        //限制文件类型,请求转发到原始请求页面,并携带错误提示信息
                        request.setAttribute("errormessage", "不支持的文件类型!");
                        request.getRequestDispatcher("/WEB-INF/pages/typeerror.jsp")
                                .forward(request, response);
                        flag= false;
                    }
                }
            }
            return flag;
        }
        /**
         * 判断是否为允许的上传文件类型,true表示允许
         */
        private boolean checkFile(String fileName) {
            //设置允许上传文件类型
            String suffixList = "jpg,gif,png,ico,bmp,jpeg";
            // 获取文件后缀
            String suffix = fileName.substring(fileName.lastIndexOf(".")
                    + 1, fileName.length());
            if (suffixList.contains(suffix.trim().toLowerCase())) {
                return true;
            }
            return false;
        }
    }

    xml配置文件配置拦截器,注意先后顺序

        <mvc:interceptors>
            <mvc:interceptor>
                <!-- /**表示所有URL和子URL路径 -->
                <mvc:mapping path="/**"/>
                <!-- 配置自定义的文件上传类型限制拦截器 -->
                <bean class="cn.cheng.controller.FileTypeInterceptor"/>
            </mvc:interceptor>
            <mvc:interceptor>
                <mvc:mapping path="/**"/>
                <bean class="cn.cheng.controller.FileUploadInterceptor">
                    <!-- 设定限制的文件上传大小 -->
                    <property name="maxSize" value="512000000000"/>
                </bean>
            </mvc:interceptor>
        </mvc:interceptors>

    这样无论传多大都不会错,但是格式不正确会被跳转到错误页面。

    拦截器返回Json数据

    很多时候经常用ajax与json进行数据交互,因此并不一定每次出错都是跳转页面,返回json也很简单,直接写在拦截器response对象中即可

    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    PrintWriter out = null ;
    try{
        JSONObject res = new JSONObject();
        res.put("success","false");
        res.put("msg","上传文件类型错误");
        out = response.getWriter();
        out.append(res.toString());
        return false;
    }
    catch (Exception e){
        e.printStackTrace();
        response.sendError(500);
        return false;
    }

    当然我们也可将其封装成工具类

    public class SendMsgUtil {
        /**
         * 将某个对象转换成json格式并发送到客户端
         * @param response
         * @param obj
         * @throws Exception
         */
        public static void sendJsonMessage(HttpServletResponse response, Object obj) throws Exception {
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.print(JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue,
                    SerializerFeature.WriteDateUseDateFormat));
            writer.close();
            response.flushBuffer();
        }
    }

    用MultipartFile的transferTo

    代码:

        @RequestMapping("/fileupload2")
        public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {
            System.out.println("springmvc文件上传...");
            // 使用fileupload组件完成文件上传
            // 上传的位置
            String path = request.getSession().getServletContext().getRealPath("/uploads/");
            // 判断,该路径是否存在
            File file = new File(path);
            if (!file.exists()) {
                // 创建该文件夹
                file.mkdirs();
            }
    
            // 说明上传文件项
            // 获取上传文件的名称
            String filename = upload.getOriginalFilename();
            // 把文件的名称设置唯一值,uuid
            String uuid = UUID.randomUUID().toString().replace("-", "");
            filename = uuid + "_" + filename;
            // 完成文件上传 new File中做目录 又文件名
            upload.transferTo(new File(path, filename));
            return "success";
        }

    其中用文件参数类型用CommonsMultipartFile亦可,但是这时必须给参数@RequestParam("file")注解指定所传文件项!

    CommonsMultipartFile的getFileItem()

        @RequestMapping("/fileupload2")
        public String fileuoload2(HttpServletRequest request, @RequestParam("upload") CommonsMultipartFile upload) throws Exception {
            System.out.println("springmvc文件上传...");
            // 使用fileupload组件完成文件上传
            // 上传的位置
            String path = request.getSession().getServletContext().getRealPath("/uploads/");
            // 判断,该路径是否存在
            File file = new File(path);
            if (!file.exists()) {
                // 创建该文件夹
                file.mkdirs();
            }
            // 说明上传文件项
            // 获取上传文件的名称
            String filename = upload.getOriginalFilename();
            String uploadFileFileNameWithoutSpace = filename.replaceAll(" ", "");
            File targetFile = new File(path + File.separator, uploadFileFileNameWithoutSpace);
            //判断目标文件存在就删除 其实只要保证文件名唯一就无需该操作
            if (targetFile.exists()) {
                targetFile.delete();
            }
            upload.getFileItem().write(targetFile);
            return "success";
        }

    其中File.separator等效于分隔符"/",当然也可以拼接这个分隔符,按常理说windows系统路径分隔符是\,使用时候需转义为\\,Linux是/,因此separator可以对系统进行判断返回相应分隔符。

    多文件上传

        @RequestMapping("fileupload22")
        public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException {
            long startTime = System.currentTimeMillis();
            //将当前上下文初始化给  CommonsMutipartResolver (多部分解析器)
            // 上传的位置
            String path = request.getSession().getServletContext().getRealPath("/uploads/");
            CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(
                    request.getSession().getServletContext());
            //检查form中是否有enctype="multipart/form-data"
            if (multipartResolver.isMultipart(request)) {
                //将request变成多部分request
                MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
                //获取multiRequest 中所有的文件名
                Iterator iter = multiRequest.getFileNames();
                while (iter.hasNext()) {
                    //一次遍历所有文件
                    MultipartFile file = multiRequest.getFile(iter.next().toString());
                    if (file != null) {
                        String filepath = path + "/" + file.getOriginalFilename();
                        System.out.println(filepath);
                        //上传
                        file.transferTo(new File(filepath));
                    }
                }
            }
            long endTime = System.currentTimeMillis();
            System.out.println("运行时间:" + (endTime - startTime) + "ms");
            return "success";
        }

    跨服务器上传

    有专门的图片服务器B,用户访问网站在A服务器,在A网站上传图片被转存到B服务器,A服务器不作储存。首先我们需要两个tomcat。

    依赖:

    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-core</artifactId>
        <version>1.18.1</version>
    </dependency>
    <dependency>
        <groupId>com.sun.jersey</groupId>
        <artifactId>jersey-client</artifactId>
        <version>1.18.1</version>
    </dependency>

    实例代码

    创建端口不同的tomcat的web项目,在WEB-INF里建uploads文件夹,修改web.xml配置文件

    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>0</param-value>
        </init-param>
        <init-param>
          <param-name>readonly</param-name>
          <param-value>false</param-value>
        </init-param>
        <init-param>
          <param-name>listings</param-name>
          <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
    </web-app>

    代码:

        @RequestMapping("/fileupload3")
        public String fileuoload3(MultipartFile upload) throws Exception {
            System.out.println("跨服务器文件上传...");
            // 定义上传文件服务器路径
            String path = "http://localhost:9090/uploads/";
            // 说明上传文件项
            // 获取上传文件的名称
            String filename = upload.getOriginalFilename();
            // 把文件的名称设置唯一值,uuid
            String uuid = UUID.randomUUID().toString().replace("-", "");
            filename = uuid + "_" + filename;
            // 创建客户端的对象
            Client client = Client.create();
    //        需要对文件名进行URL编码 若直接中文或空格上传会报错
            filename = URLEncoder.encode(filename, "utf-8");
            // 和图片服务器进行连接
            WebResource webResource = client.resource(path + filename);
            // 上传文件
            webResource.put(upload.getBytes());
            return "success";
        }

    关于报错

    409可能原因是编译好的web目录下没有相应的例如以上的uploads文件夹,你项目的web目录即使有ploads文件夹文件夹但没一个文件,编译好后会自动忽略创建该目录,可在源码web目录文件夹内放任意文件即可。

    403可能是web.xml未按上述配置写入权限,另外如果以上代码文件名未URL编码,中文文件名文件上传亦会产生403错误。

    文件上传注意事项

      1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于WEB-INF目录下。

      2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。

      3、为防止一个目录下面出现太多文件,要使用hash算法打散存储。

      4、要限制上传文件的最大值。

      5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。