Spring实战(第四版)读书笔记20——Spring MVC的高级技术

1、添加其他的Servlet和Filter

基础的Servlet注册初始化器类例子:

public class MyServletInitializer implements WebApplicationInitializer{
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
        myServlet.addMapping("/custom/**");
    }
}

注册Filter的WebApplicationInitializer:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {

    javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);
    filter.addMappingForUrlPatterns(null, false, "/custom/*");
}

如果只是映射到DispatcherServlet,则仅仅需要重载AbstractAnnotationConfigDispatcherServletInitializer的getServletFilters()方法:

@Override
protected Filter[] getServletFilters(){
    return new Filter[] {new MyFilter()};
}

2、处理multipart形式的数据

2.1、配置multipart解析器

multipart解析器用来告诉DispatcherServlet如何读取multipart请求,其中一个实现是StandardServletMultipartResolver。

首先在Spring应用上下文中声明为bean:

@Bean
public MultipartResolver multipartResolver() throws IOException{
    return new StandardServletMultipartResolver();
}

StandardServletMultipartResolver是在Servlet中进行限制条件的配置的(采用Servlet初始化类方式配置DispatcherServlet时对其进行基础配置):

DispatcherServlet ds = new DispatcherServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));

如果配置DispatcherServlet的Servlet初始化类继承了AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer的话,就不会直接创建DIspatcherServlet实例并注册到Servlet上下文之中,这样就不会有对Dynamic Servlet registration的引用,但是可以通过重载customizeRegistration()方法来配置:

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

稍复杂些的例子:

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

表示限制文件大小不超过2MB,整个请求不超过4MB,而且所有文件都要写到磁盘中(最后制定了一个最大容量,超过这个最大容量将会写入临时文件路径中)。

如果需要将应用部署到非Servlet3.0的容器中,那么就需要配置Jakarta Commons FilesUpload multipart解析器,此处不进行展开说明。

2.2、以MultipartFile形式处理multipart请求

Thymeleaf注册表单视图(registrationForm.html):

<form method="POST" th:object="${spitter}"
      enctype="multipart/form-data">
...
    <label>Profile Picture</label>
        <input type="file"
               name="profilePicture"
               accept="image/jpeg,image/png,image/gif" /><br/>
...
</form>

修改SpitterController中的processRegistration方法:

@RequestMapping(value="/register", method=POST)
public String processRegistration(
    @RequestPart("profilePicture") byte[] profilePicture,
    @Valid Spitter spitter,
    Error errors){
...
}

使用byte功能太过有限,spring还提供了更强大的MultipartFile接口:

public interface MultipartFile{
    String getName();
    String getOriginalFileName();
    String getContentType();
    boolean isEmpty();
    long getSize();
    byte[] getBytes() throws IOException;
    InputStream getInputStream() throws IOException;
    void transferTo(File dest) throws IOException;
}

通过transferTo()方法将上传的文件写入到文件系统,在processRegistration()方法中添加如下代码:

profilePicture.transferTo(new File("/data/spittr/"+profilePicture.getOriginalFilename())));

也可以将MultipartFile保存到云端,如Amazon S3中,这里不详细展开。

2.3、以Part形式处理multipart请求

processRegistration()方法:

@RequestMapping(value="/register", method=POST)
public String processRegistration(
    @RequestPart("profilePicture") Part profilePicture,
    @Valid Spitter spitter,
    Errors errors){
...
}

Part接口和MultipartFile接口很多是相对应的,比如getSubmittedFileName()和getOriginalFilename(),write()和transferTo()。但是要注意,只有使用MultipartFile时才需要配置MultipartResolver。

3、处理异常

Spring提供了多种方式将异常转换为响应:

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

默认映射表:

@ResponseStatus注解,将异常映射为特定的状态码:

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException{
}

方法上添加@ExceptionHandler注解以处理异常:

@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittle(){
    return "error/duplicate";
}

注意,@ExceptionHandler标注的方法能够处理所在控制器的所有处理方法抛出的相应异常。

4、控制器通知

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

  • @ExceptionHandler
  • @InitBinder
  • @ModelAttribute

控制器通知中的以上所有方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。@ControllerAdvice本身已经使用了@Component,因此可以直接自动被组件扫描获取。控制器通知通常用来将所有异常处理放到同一个类中。例子如下:

@ControllerAdvice
public class AppWideExceptionHandler(){
    
    @ExceptionHandler(DuplicateSpittleException.class)
    public String duplicateSpittleHandler(){
        return "error/duplicate";
    }
}

5、跨重定向请求传递数据

对于重定向来说,模型并不能用来传递属性,但Spring可以通过其他方案从发起重定向的方法将数据传递到处理重定向的方法中:

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

5.1、使用URL模板

return "redirect:/spitter/{username}";

使用占位符要优于String连接,因为这样可以任意输入username,其中不安全的字符都会进行转义。如果模型中的属性没有匹配URL中的任何占位符,则会自动以查询参数的形式附加到重定向URL上。

5.2、使用Flash属性

RedirectAttributes是Model的一个子接口,提供了Model所有的功能,并且可以用来设置flash属性。

更新后的processRegistration()方法:

@RequestMapping(value="register", method=POST)
public String processRegistration(
    Spitter spitter, RedirectAttributes model){
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername());
    model.addFlashAttribute("spitter",spitter);
    return "redirect:/spitter/{username}";
}

更新后的showSpitterProfile()方法:

@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(
    @PathVariable String username, Model model){
    if(!model.containsAttribute("spitter")){
        spitterRepository.findByUsername(username));
    }
    return "profile";
}

猜你喜欢

转载自blog.csdn.net/Nemoosi/article/details/107488221