Spring学习笔记之Spring MVC的高级技术

来源:互联网 发布:linux强制关闭程序 编辑:程序博客网 时间:2024/05/01 21:34

本篇主要介绍利用Spring MVC处理文件上传,异常处理,为控制器添加通知以及跨重定向请求传递数据。

1.Spring MVC处理文件上传

1.1使用multipart格式提交表单

在有文件上传的表单中,我们需要使用multipart格式的数据来上传,multipart格式的数据会将一个表单拆分成多个部分(part),每个部分对应一个输入域。在一半的表单输入域中,它所对应的部分会放置文本型数据,但是如果上传文件的话,它所对应的部分可以是二进制。下面是前段页面表单部分:

<form action="${ctx}/register" enctype="multipart/form-data" method="post">        <label>用户名:</label>        <input type="text" name="username"><br/>        <label>密码:</label>        <input type="password" name="password"><br/>        <label>邮箱:</label>        <input type="text" name="email"><br/>        <label>头像:</label>        <input type="file" name="image" accept="image/jpeg,image/png,image/gif"><br/>        <input type="submit" value="注册">        </form>

form表单现在将enctype属性设置为multipart/form-data,这会告诉浏览器以multipart数据的形式提交表单,而不是以表单数据的形式进行提交。在multipart中,每个输入域都会对应一个part。而文件上传输入域中的accept属性用来将文件类型限制为JPEG、PNG和GIF图片。

1.2配置multipart解析器

DispatcherServlet将解析multipart请求数据的任务委托给了Spring中MultipartResolver策略接口的实现。Spring内置了两个MultipartResolver的实现供我们选择:

  • CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart请求
  • StandardServletMultipartResolver:依赖于Servlet3.0对multipart请求的支持。

使用Servlet 3.0解析multipart请求:
在Spring MVC上下文中将其声明为bean:

@Configuration @EnableWebMvc //启用Spring MVC@ComponentScan("spittr.web") //启用组件扫描public class WebConfig         extends WebMvcConfigurerAdapter{    /**     * 配置multipart解析器     * @return     */    @Bean    public MultipartResolver multipartResolver(){        return new StandardServletMultipartResolver();    }}

StandardServletMultipartResolver的配置是在Servlet中通过传入一个MultipartConfigElement实例来指定:
如果是自定义的Servlet,即(自己实现WebApplicationInitializer),这样做:

public class myServletIntializer implements WebApplicationInitializer{    public void onStartup(ServletContext servletContext) throws ServletException {        MyServlet myServlet = new MyServlet();        Dynamic dynamic = servletContext.addServlet("myServlet", myServlet);        dynamic.addMapping("/");        dynamic.setMultipartConfig(                new MultipartConfigElement("/tmp/spittr/uploads"));    }}

如果是通过继承AbstractAnnotationConfigDispatcherServletInitializer的到的DispatcherServlet的话,将不会有对Dynamic的引用,还记得上一篇我们提到的么,通过重载customizeRegistration会提供一个Dynamic参数,so:

@Override    protected void customizeRegistration(Dynamic registration) {        registration.setMultipartConfig(                new MultipartConfigElement("/tmp/spittr/uploads"));    }

这里,我们都是通过构造器来对MultipartConfigElement进行配置的。new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold)这是其所有的构造器。

  • location:用来指定上传文件的临时写入目录,一定要注意是绝对目录,比如Windows下的F:\Workspace\spring,上面我们配置的是Linux下的/tmp/spittr/uploads
  • maxFileSize:上传文件的最大容量(以字节为单位),默认无限制
  • maxRequestSize:整个multipart请求的最大容量(以字节为单位),不会关心有多少个part以及每个part的大小。默认是没有限制的
  • fileSizeThreshold:在文件上传的过程中,如果文件大小达到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为0,也就是所有上床的文件都会写入到磁盘上。

下面是用web.xml来配置MultipartConfigElement,使用<servlet><multipart-config>元素:

<servlet>        <servlet-name>appServlet</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <load-on-startup>1</load-on-startup>        <multipart-config>            <location>/tmp/spittr/uploads</location>            <max-file-size>2097152</max-file-size>            <max-request-size>4194304</max-request-size>            <file-size-threshold>0</file-size-threshold>        </multipart-config>    </servlet>

限制文件的大小为2MB,整个请求不超过4MB,而且所有的文件都写到磁盘里。


使用使用Jakarta Commons FileUpload解析multipart请求
声明为bean:

@Bean    public MultipartResolver multipartResolver(){        //return new StandardServletMultipartResolver();        return new CommonsMultipartResolver();    }

CommonsMultipartResolver的配置不是在Servlet指定的,而是直接配置在实例中,而且CommonsMultipartResolver不会轻质要求设置临时文件路径,默认情况下,这个路径就是Servlet容器的临时目录。下面是配置一个等价于上面我们对MultipartConfigElement的配置:

@Bean    public MultipartResolver multipartResolver() throws IOException{        //return new StandardServletMultipartResolver();        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();        multipartResolver.setUploadTempDir(                new FileSystemResource("/tmp/spittr/uploads"));//对应于MultipartConfigElement的location        multipartResolver.setMaxUploadSize(2097152);//对应于MultipartConfigElement的maxFileSize        multipartResolver.setMaxInMemorySize(0);//对应于MultipartConfigElement的fileSizeThreshold        return multipartResolver;    }

CommonsMultipartResolver 无法设定multipart请求整体的最大容量。


1.3处理multipart请求

1.3.1用byte[]数组来接受上传文件的二进制数据
代码:

@RequestMapping("/register")    public void spittle(            @RequestPart("image") byte[] image,            User user){        System.out.println(image.length);    }

1.3.2接受MultipartFile:

@RequestMapping("/register")    public void spittle(            @RequestPart("image") MultipartFile image,            User user,HttpServletRequest request) throws IllegalStateException, IOException{        System.out.println(image.getContentType());        System.out.println(image.getName());        System.out.println(image.getOriginalFilename());        System.out.println(image.getSize());        image.transferTo(                new File("/uploads/"+image.getOriginalFilename()));//将上传文件写入到文件系统中    }

需要注意的是这里File的位置是以你配置的临时文件路径为前提的,比如说你的临时文件路径为F:\Workspace\spring,那么上面的文件就是在F:\Workspace\spring\uploads\下

2.处理异常

Servlet请求的输出都是一个Servlet响应。如果在请求处理的时候,出现了异常,那它放入输出依然会使Servlet响应。异常必须要以某种方法转换为响应。
Spring提供了多种方式将异常转换为响应:

  • 特定的Spring异常将会自动映射为指定的HTTP状态码
  • 异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码
  • 在方法上可以添加@ExceptionHandler注解,使其用来处理异常。

2.1将异常映射为HTTP状态码

Spring的一些异常会默认映射为HTTP状态码

Spring异常 HTTP状态码 BindException 400-Bad Request ConversionNotSupportedException 500-Internal Server Error HttpMediaTypeNotAcceptableException 406-Not Acceptable HttpMediaTypeNotSupportedException 415-Unsupported Media Type HttpMessageNotReadableException 400-Bad Request HttpMessageNotWritableException 500-Internal Server Error HttpRequestMethodNotSupportedException 405-Method Not Allowed MethodArgumentNotValidException 400-Bad Request MissingServletRequestParameterException 400-Bad Request MissingServletRequestPartException 400-Bad Request NoSuchRequestHandlingMethodException 404-Not Found TypeMismatchException 400-Bad Request

下面介绍将异常映射为特定的状态码
首先我们创建一个异常,在其上面使用@ResponseStatus来将异常映射为特定的状态码。

@ResponseStatus(value=HttpStatus.NOT_FOUND,                reason="Param Not Found")//将异常映射为HTTP状态404public class ParamNotFountException extends RuntimeException{    private static final long serialVersionUID = 1L;}

在控制器如果接受到的username为空则抛出这个异常:

@RequestMapping("/register")    public String spittle(            @RequestPart("image") MultipartFile image,            User user,HttpServletRequest request){        //如果用户名为空则抛出异常        if(user.getUsername().isEmpty()){            throw new ParamNotFountException();        }        return "register";    }

截图:
返回的异常

2.2编写异常处理的方法

继续上面的方法,如果我们不想显示错误页面,想要捕获异常的话,平时我们会使用catch进行捕获,但catch只能捕获当前语句块的异常并处理。下面我们来使用@ExceptionHandler标注的方法,它可以出来同一个控制器中所有处理器方法抛出的异常。接着上面的代码,我们在控制器方法下面写这样一个方法:

@RequestMapping("/register")    public String spittle(            @RequestPart("image") MultipartFile image,            User user,HttpServletRequest request){        //如果用户名为空则抛出异常        if(user.getUsername().isEmpty()){            throw new ParamNotFountException();        }        return "register";    }    //捕获这个控制器抛出的ParamNotFountException异常,并处理    @ExceptionHandler(value=ParamNotFountException.class)    public String HandleException(){        System.out.println("在这里处理异常");        return "error";    }

这一次,返回的不是404页面,而是error页面,而且控制台输出:“在这里处理异常”字段。

3.为控制器添加通知

控制器通知(controller advice)是任意带有@ControllerAdvice注解的类,这个类会包含一个或多个如下类型的方法:

  • @ExceptionHandler注解标注的方法;
  • @InitBinder注解标注的方法;
  • @ModelAttribute注解标注的方法。

在带有@ControllerAdvice注解的类中,以上所述的这些方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。@ControllerAdvice注解本身自带扫描特性,即使用了@Component。
还是上面的代码,我们把控制器中的HandleException()方法删掉,然后新建一个类,运行效果是一样的:

@ControllerAdvicepublic class AppWideException {    //捕获所有控制器抛出的ParamNotFountException异常,并处理        @ExceptionHandler(value=ParamNotFountException.class)        public String HandleException(){            System.out.println("在这里处理异常");            return "error";        }}

4.跨重定向请求传递数据

当一个处理器方法完成之后,该方法所指定的模型数据将会复制到请求中,并作为请求的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所以在转发的过程中,请求属性能够得以保存。当控制器的结果是重定向的话,原始的请求就结束了,并且会发起一个新的get请求。原始请求中所带有的模型数据也就随之请求一起消亡了。
有两种方法能够从发起重定向的方法传递数据给处理重定向方法中:

  • 使用URL模板以路径变量和/或查询参数的形式传递数据
  • 通过flash属性发生数据

4.1通过URL模板进行重定向

使用URL中的占位符进行传递:

@RequestMapping("/register")    public String spittle(User user,Model model){        model.addAttribute("username", "xuexiaoqiang");        model.addAttribute("email", "test@tom.com");        //重定向        return "redirect:/test/{username}";    }    @RequestMapping("/test/{username}")    public String test(            @PathVariable(value="username") String username,            HttpServletRequest request){        String email = request.getParameter("email");        System.out.println("username:"+username);        System.out.println("email:"+email);                return "register";    }

输出:

username:xuexiaoqiangemail:test@tom.com

这里,因为模型中的email没有匹配重定向URL中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上。这里最后的重定向路径是“/test/xuexiaoqiang?email=Mr_xue@tom.com”。

4.2使用flash属性

如果我们想要发生一个对象,那么URL就不能实现,Spring提供了将数据发生为flash属性的功能。按照定义,flash属性会一直携带这些数据知道下一次请求,然后才会消失。
RedirectAttributes是Model的一个子接口,提供了Model的所有功能,除此之外,还有几个方法是用来设置flash属性的。

@RequestMapping("/register")    public String spittle(User user,RedirectAttributes model){        user.setEmail("test@tom.com");        user.setUsername("xuexiaoqiang");        model.addFlashAttribute("user", user);        //重定向        return "redirect:/test";    }    @RequestMapping("/test")    public String test(Model model){        if(model.containsAttribute("user")){            System.out.println("存在");        }    return "testdata";    }

testdata.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><h1>用户名:${user.username }</h1><h1>邮箱:${user.email }</h1></body></html>

最后结果截图:
显示model中的数据

在重定向执行之前,所有flash属性都会复制到会话中。在重定向后,存在会话中的flash属性会被取出,并从会话转移到模型之中。处理重定向的方法就能从模型中访问User对象了,就像获取其他的模型对象一样。

1 0
原创粉丝点击