Spring之旅第十站:MVC配置、上傳文件、異常處理、跨重定向請求、爲控制器添加通知

Spring MVC 高級的技術

本章內容:html

  • Spring MVC配置的替代方案
  • 處理文件上傳
  • 在控制器中處理異常
  • 使用flash屬性

稍等還沒結束前端

說明

若是你有幸能看到。後面的章節暫時不更新了,改變學習方式了。重要理解思想,這本書寫的太好了。記得要看做者的代碼,書上只是闡述了知識點。還有之後會把重點放在GitHub上,閱讀別人的代碼,本身理解的同時在模仿出來,分享給你們。大家的點贊就是對個人支持,謝謝你們了。java

  • 一、本文參考了《Spring 實戰》重點內容,參考了做者GitHub上的代碼,推薦使用chrome上的GitHub插件Insight.io,FireFox也有。
  • 二、本文只爲記錄做爲之後參考,要想真正領悟Spring的強大,請看原書。跟着做者套路來,先別瞎搗騰!!!
  • 三、在一次佩服老外,國外翻譯過來的書,在GiuHub上大都有實例。看書的時候,跟着敲一遍,效果很好。
  • 四、代碼和筆記在這裏GitHub,對你有幫助的話,歡迎點贊。
  • 五、每一個人的學習方式不同,找到合適本身的就行。2018,加油。
  • 六、Java 8 In Action 的做者Mario Fusco、Spring In Action 、Spring Boot In Action的做者Craig Walls
  • 七、知其然,也要知其因此然。有些是在Atom上手敲的,有拼寫錯誤,還請見諒。
  • 八、從Spring Web Flow 開始,我要加快速度了,邏輯可能不完整,一切以原書爲準,本身只是記錄。加深印象。

談一些我的感覺git

  • 一、趕快學習Spring吧,Spring MVC 、Spring Boot 、微服務。
  • 二、重點中的重點,學習JDK 8 Lambda,Stream,Spring 5 最低要求JDK1.8.
  • 三、還有Netty、放棄SH吧,否則你會落伍的。
  • 四、多看一些國外翻譯過來的書,例如 Xxx In Action 系列。權威指南系列。用Kindle~
  • 五、寫代碼以前先寫測試,這就是老外不一樣之處。學到了不少技巧。
  • 六、再一次佩服老外對細節的處理。值得咱們每個人學習

在不少方面,Spirng MVC(整個Spirng也是如此),也有還沒結束這樣的感受。github

在第五章,咱們學習了Sprng MVC的基礎知識,以及如何編寫控制器來處理各類請求,基於這些知識。咱們在第六章學習瞭如何建立JSP和Thymeleaf視圖,這些視圖會將模型數據展現給用戶。你可能認爲咱們已經掌握了Spring MVC的所有知識,可是,稍等!還沒結束。web

在本章中,咱們將會看到如何編寫控制器處理文件上傳,如何處理控制器所拋出的異常,以及如何在模型中傳遞數據,使其可以在重定向(redirect)以後依然存活。spring

但,首先我要兌現一個承諾。在第5章中,咱們快速展示瞭如何經過AbstractAnnotationConfigDispatcherServletInitializer搭建Spring MVC,當時,咱們承諾會爲讀者展示其餘的配置方案。因此,在介紹文件上傳和異常處理以前,咱們花時間探討一下如何使用其餘方式來搭建DispatcherServletContextLoaderListenerchrome

7.1 Spring MVC 配置的替代方案

儘管對不少Spring應用來講,這是一種安全的假設,可是並不必定能知足咱們的要求。除了DispatcherServlet之外,咱們還可能須要額外的DispatcherServletFilter,咱們可能還須要對DispatcherServlet自己作一些額外的配置:或者,若是咱們須要將應用部署到Servlet3.0以前的容器中,那麼還須要將DispatcherServlet配置到傳統的web.xml中。數組

7.1.1 自定義DispatcherServlet配置

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

}

AbstractAnnotationConfigDispatcherServletInitializer所完成的事其實比看上去要多,在SpitterWebInitializer中咱們所編寫的三個方法僅僅是必需要重載的三個抽象方法,但實際上還有更多的方法能夠進行重載,從而實現額外的配置。瀏覽器

此類的方法之一就是customizeRegistration().在AbstractAnnotationConfigDispatcherServletInitializerDispatcherServlet註冊到Servlet容器中就會調用customizeRegistration(),並將Servlet註冊後獲得的Registration.Dynamic傳遞進來,經過重載customizeRegistration()方法,咱們就能夠對DispatcherServlet進行額外的配置。

在本章稍後,咱們將會看到如何在Spirng MVC中處理multiparty請求和文件上傳。若是計劃使用Servlet3.0對multiparty配置的支持,那麼咱們須要使用DispatcherServlet的registration來啓用multilpart請求。咱們能夠重載customizeRegistration()方法來設置MultipartConfigElement,

@Override
protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(
            new MultipartConfigElement("C:\\Temp"));   //設置上傳文件目錄
}

藉助customizeRegistration()方法中的ServletRegistration.Dynamic咱們可以完成更多的任務,

  • 包括經過調用setLoadOnstartup()設置load-on-startup 優先級,
  • 經過setInitParameter()設置初始化參數,
  • 經過調用setMultipartConfig()配置Servlet3.0對multipart的支持,

7.1.2 添加其餘的Servlet和Filter

按照AbstractAnnotationConfigDispatcherServletInitializer的定義,它會建立DispatcherServletContextLoaderListener.可是若是你想要註冊其餘的Servlet、Filter、Listener的話,那該怎麼辦?

基於Java的初始化器(initializer)的一個好處在於咱們能夠定義任意數量的初始化類。若是咱們想要往Web容器中註冊其餘組件的話,只須要建立一個 新的初始化類就能夠了,最簡單的方式就是實現Spring的WebApplicationInitializer並註冊一個Servlet。

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

以上程序至關基礎的Servlet註冊初始化器類,它註冊了一個Servlet並將其映射到了一個路徑上,咱們也能夠經過這種方式來手動註冊DispatcherServlet.(可是不必,由於AbstractAnnotationConfigDispatcherServletInitializer沒用太多代碼就將這項任務完成得很漂亮)

相似的,咱們還能夠建立新的WebApplicationInitializer來實現註冊Listener和 Filter,

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter",myFilter.class);
    filter.addMappingForUrlPatterns(null,false,"/custom/*");
}

若是你將應用部署到Servlet3.0的容器中,那麼WebApplicationInitializer提供了一種通用的方法,實如今Java中註冊Servlet和Filter、Listener,若是你只是註冊Filter,而且該Filter只會映射到DispatcherServlet上的話,那麼AbstractAnnotationConfigDispatcherServletInitializer還有一種快捷的方式。

爲了註冊Filter並將其映射到DispatcherServlet,所須要作的僅僅是重載AbstractAnnotationConfigDispatcherServletInitializer的getServletFilter()方法。

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

這個方法返回一個javax.servlet.filter數組。在這裏沒有必要聲明它的映射路徑,getServletFilter()方法返回全部Filter都會被映射到DispatcherServlet上。

若是要將應用部署到Servlet3.0上,那麼Spring容器提供了多種註冊方式,而沒必要建立web.xml文件,可是,若是你不想採起上述方案的話,也是能夠的,假設你將應用部署到不支持Servlet3.0的容器中(或者你只但願使用web.xml),那麼咱們徹底能夠按照傳統的方式,經過web.xml配置Spirng MVC,

7.1.3 在web.xml中聲明DispatcherServlet

在典型的Spirng MVC應用中,咱們會須要DispatcherServletContextLoaderListener.AbstractAnnotationConfigDispatcherServletInitializer會自動註冊它們,但若是須要在web.xml中註冊的話,那就須要咱們本身動手來完成了。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="taotao" version="2.5">
    <display-name>appServlet</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <!-- 加載spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 解決post亂碼 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- springmvc的前端控制器 -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- contextConfigLocation不是必須的, 若是不配置contextConfigLocation, springmvc的配置文件默認在:WEB-INF/servlet的name+"-servlet.xml" -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

ContextLoaderListenerDispatcherServlet各自都會加載一個Spirng應用上下文。上下文ContextLoaderLocation指定了一個XMl文件的地址,這個文件定義了根據應用上下文,它會被ContextLoaderListener加載。根上下文會從"/WEB-INF/spring/applicationContext-*.xml"中加載bean的定義

DispatcherServlet會根據Servlet的名字找到一個文件,並基於該文件加載應用上下文。

若是你但願指定DispatcherServlet配置文件的話,那麼能夠在Servlet指定一個ContextLoaderLocation初始化參數。

<!-- springmvc的前端控制器 -->
<servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- contextConfigLocation不是必須的, 若是不配置contextConfigLocation, springmvc的配置文件默認在:WEB-INF/servlet的name+"-servlet.xml" -->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/springmvc.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

如今咱們已經看到了如何以多種不一樣的方式來搭建Spring MVC,那麼接下來咱們看一下如何使用Spring MVC來處理文件上傳。

7.2 處理multipart形式的數據

在Web應用中,容許用戶上傳內容是很常見的需求,在Facebook和Flickr這樣的網站中,容許用戶會上傳圖片和視頻,並與家人朋友分享。還有一些服務器中容許用戶上傳照片,而後按照傳統的方式將其打印在紙上,或者咖啡杯上。

Spittr應用有兩個地方須要文件上傳。當新用戶註冊的時候,咱們但願可以上傳一張照片,從而與他的我的信息相關聯。當用戶提交新的Spittle時,除了文本信息外,他們可能還會上傳一張照片。

通常表單提交所造成的請求結果是很是簡單的,就是以"&"符號分割的多個name-value對,儘管這種方式簡單,而且對於典型的基於文本的表單提交也足夠知足需求,可是對於二進制數據,就顯得力不從心了。與之不一樣的是multipart格式的數據會將一個表單拆分爲多個部分(part),每一個部分對應一個輸入域。在通常的表單輸入域中,它所對應的部分中會放置文本數據,可是若是是上傳文件的話,它所對應的部分能夠是二進制。

Content-Type 他表它的類型。儘管multipart請求看起來很複雜,可是在SpringMVC中處理它卻很容易,在編寫控制器方法處理文件上傳以前,咱們必須配置一個multipart解析器,經過它來告訴DispatcherServlet該如何讀取multipart請求。

7.2.1 配置multipart解析器

DispatcherServlet並無實現愛你任何解析multipart請求數據的功能。它將該任務委託給了Spring中的MultipartResolver策略接口的實現,經過這個實現類來解析multipart請求中的內容。從Spirng 3.1開始,Spirng內置了兩個MultipartResolver的實現供咱們選擇。

  • CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart請求。
  • StandardServletMultipartResolver:依賴於Servlet3.0對multipart請求的支持。

通常來說StandardServletMultipartResolver可能會是優選方案,他使用Servlet所提供的功能支持。並不須要依賴任何其餘的項目。若是,咱們須要將項目部署到Sevrvlet3.0以前的容器中,或者尚未使用Spring 3.1 或者更高的版本,那麼可能就須要 CommonsMultipartResolver了。

使用Servlet3.0解析multipart

兼容Servlet3.0的StandardServletMultipartResolver沒有構造器參數,也沒有要設置的參數,這樣,在Spring應用上下文中,將其聲明爲bean就會很是簡單。

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

若是咱們採用Servlet初始化類的方式來配置DispatcherServlet的話,這個初始化類應該已經實現了WebApplicationInitializer,那麼咱們能夠在ServletRegistration上調用setMultipartConfig()方法,傳入一個MultipartConfigElement實例,具體的配置以下:

@Override
protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(
            new MultipartConfigElement("C:\\Temp"));
}
}

經過重載customizeRegistration()方法(它會獲得一個Dynamic做爲參數)類配置multipart的具體細節。

到目前爲止,咱們所使用的是隻有一個參數的MultipartConfigElemenet構造器,這個參數指定的是文件系統中一個絕對目錄,上傳文件將會臨時寫入該目錄,可是,咱們還能夠經過其餘的構造器來限制上傳文件的大小,除了臨時路徑的位置,其餘的構造器能夠接受的參數以下:

  • 上傳文件的最大容量(以字節爲單位)。默認是沒有限制的
  • 整個multipart請求的容量。不會關心有多少個part以及每一個part的大小。默認是沒有限制的。
  • 在上傳的過程當中,若是文件大小達到了一個指定最大容量,將會寫入到臨時文件路徑中,默認值爲0,也就是全部上傳的文件都會寫入磁盤上。

例如,假設咱們想要限制文件的大小不超過2MB,整個請求不超過4MB,並且全部的文件都要寫入磁盤,

@Override
protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(
            new MultipartConfigElement("C:\\Temp\\uploads",2097152, 4194304, 0));
}

若是使用更爲傳統的web.xml來配置MultipartConfigResolver的話,那麼可使用<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>C:\\Temp\\uploads<location>
    <max-file-size>2097152</max-file-size>
    <max-request-size>4194304 </max-request-size>
  </multipart-config>
</servlet>

配置Jakarta Commons FileUpload Multipart解析器

Spring內置了CommonsMultipartResolver,能夠做爲StandardServletMultipartResolver的替代方案。

7.2.2 處理multipart請求。

已經配置好multipart請求的處理器,那麼接下來咱們就編寫控制器方法來接收上傳的文件。要實現這一點,最多見的方法就是在某個控制器方法參數上添加@RequestPart註解。

<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/>
  <input type="submit" value="Register" />
</form>

<form>標籤如今將enctype屬性設置爲multipart/form-data,這會告訴瀏覽器以multipart數據的形式提交表單,而不是以表單數據的形式進行提交。還添加了一個新的< input>域其type爲file。 accept屬性用來將文件類型限制爲jpeg,png,gif格式的。根據name屬性,圖片數據將會發送到multipart請求中的profilePicture之中。

如今咱們須要修改processRegistration()方法,使其可以接受上傳的圖片。其中一種方式就是添加btye數組,併爲其添加@RequestPart註解。

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

當表單提交的時候,profilePicture屬性將會給定一個byte數組,這個數組中包含了請求中對應的part的數據(經過@RequestPart指定的)。若是沒有選擇文件,那麼這個數據爲空(而不是null),獲取到圖片數據後,processRegistration()方法剩下的任務就是將文件保存到某個地方。

接受MultipartFile

使用上傳文件的原始byte比較簡單,可是功能有限。所以,Spring提供了MultipartFile接口,它爲處理multipart數據提供了內容更爲豐富的對象。

package org.springframework.web.multipart;
/**
 * A representation of an uploaded file received in a multipart request.
 * @author Juergen Hoeller
 * @author Trevor D. Cook
 */
public interface MultipartFile {
    /**
     * Return the name of the parameter in the multipart form.
     * @return the name of the parameter (never {@code null} or empty)
     */
    String getName();
    /**
     * Return the original filename in the client's filesystem.
     */
    String getOriginalFilename();
    /**
     * Return the content type of the file.
     */
    String getContentType();
    /**
     * Return whether the uploaded file is empty, that is, either no file has
     * been chosen in the multipart form or the chosen file has no content.
     */
    boolean isEmpty();
    /**
     * Return the size of the file in bytes.
     */
    long getSize();
    /**
     * Return the contents of the file as an array of bytes.
     */
    byte[] getBytes() throws IOException;
    /**
     * Return an InputStream to read the contents of the file from.
     */
    InputStream getInputStream() throws IOException;
    /**
     * Transfer the received file to the given destination file.
     */
    void transferTo(File dest) throws IOException, IllegalStateException;
}

能夠看到,MultipartFile提供了獲取文件上傳文件byte的方式,還能獲取原始的文件名,大小以及內容類型、還提供了一個InputStream用來將文件數據以流的方式進行讀取。

除此以外,還提供了一個transferTo()方法,它可以幫助咱們將上傳文件寫入到文件系統中。做爲樣例,咱們在能夠在processRegistration()方法中添加以下的幾行代碼,從而將上傳的圖片文件寫入到文件系統中

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

將文件保存到本地文件系統中是很是簡單的,可是這須要咱們對這些文件進行管理。咱們須要確保有足夠的空間,確保當出現硬件故障時,文件進行了備份,還須要在集羣的多個服務器之間處理這些圖片文件的同步。

以Part的形式接受上傳的文件

Spring MVC接受javax.servlet.http.Part做爲控制器方法的參數,若是使用part來替換MultiFile的話,那麼processRegistration()方法簽名會變成以下的形式。

@RequestMapping(value="/register", method=POST)
public String processRegistration(
    @RequestPart(value="profilePictures", required=false) Part fileBytes,
    RedirectAttributes redirectAttributes,
    @Valid Spitter spitter,
    Errors errors) throws IOException {
  if (errors.hasErrors()) {
    return "registerForm";
  }

Part接口

package javax.servlet.http;
public interface Part {
  public InputStream getInputStream() throws IOException;

  public String getContentType();

  public String getName();

  public String getSubmittedFileName();

  public long getSize();

  public void write(String fileName) throws IOException;

  public void delete() throws IOException;

  public String getHeader(String name);

  public Collection<String> getHeaders(String name);

  public Collection<String> getHeaderNames();
}

不少狀況下,Part方法的名稱與MultiPartFile方法的名稱是徹底相同的。有一些比較相似,可是稍有差異。
好比getSubmittedFileName()方法對應getOriginalFilename().相似的,write()方法對應於transfer()方法,藉助該方法咱們可以將上傳的文件寫入文件系統中。

值得一提的是,若是沒有在編寫控制器方法的時候,經過Part參數的形式接受文件上傳,那就不必配置MultipartResolver了。只有使用MultipartFile的時候 ,咱們才須要MultipartResolver.

7.3 處理異常

Spring提供了多種方式將異常轉換爲響應

  • 特定的Spring異常將會自動映射爲指定的HTTP狀態碼
  • 異常上能夠添加@RequestStatus註解,從而將其映射爲某一個HTTP狀態
  • 在方法上添加@ExceptionHandle註解,使其用來處理異常。

處理異常最簡單的方式就是將其映射到HTTP狀態碼上。

7.3.1 將異常映射 爲HTTP狀態碼

異常通常由Spring自身拋出,做爲DispatcherServlet處理過程當中或執行校驗時出現問題的結果。

Spring提供了一種機制,可以經過使用@RequestStatus註解將其映射爲HTTP狀態碼

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittleId") long spittleId,
    Model model) {
  Spittle spittle = spittleRepository.findOne(spittleId);
  if (spittle == null) {
    throw new SpittleNotFoundException();     //這裏會拋出異常
  }
  model.addAttribute(spittle);
  return "spittle";
}

若是資源沒有找到的話,HTTP狀態碼404是最爲精確的響應狀態碼

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

7.3.2 編寫異常處理方法

若是響應中不只包含狀態碼,還要包含所產生的錯誤信息,須要按照請求的方式來處理異常。

@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model) {
  try {
    spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),
        form.getLongitude(), form.getLatitude()));
    return "redirect:/spittles";
  } catch (DuplicateSpittleException e) {     //捕獲異常
    return "error/duplicate";
  }
}
@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model) {
    spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),
        form.getLongitude(), form.getLatitude()));
    return "redirect:/spittles";
    return "error/duplicate";
}

它只關注成功保存Spittle的狀況,因此只須要一個執行路徑,很容易理解和測試。

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

方法上加上@ExceptionHandler註解後,當方法拋出異常的時候,將委託該方法來處理,它可以處理同一個控制器中全部的方法拋出的異常。

7.4 爲控制器添加通知

若是控制器類的特定切面可以 運用到整個應用程序的全部控制器中,那麼這將會便利不少。,爲了 避免重複,咱們會建立一個基礎的控制器,全部的控制器類要擴展這個類,從而繼承通用的@ExceptionHandler方法。

Spring3.2 引入了一個新的解決方法:控制器通知。控制器通知(controllerAdvice)是任意帶有@ControllerAdvice註解的類,這個類會包含一個或多個 以下類型的方法:

  • @ExceptionHandle註解標註的方法
  • @InitBinder註解標註的方法
  • @ModelAttribute註解標註的方法。
@ControllerAdvice
public class AppWideExceptionHandler {

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

}

7.5 跨重定向請求傳遞數據

「redirect:」前綴能讓重定向功能變得更簡單,可是,請稍等,Spirng爲重定向功能還提供了一些其餘的輔助功能。

當一個處理器方法完成以後,該方法所指定的模型數據會將複製到請求中,並做爲請求中的屬性,請求會轉發(forward)到視圖上進行渲染。

對於 重定向來講,模型並不能完全數據,有一些其餘方法,可以從發起重定向的方法傳遞數據給處理重定向的方法

  • 使用URL模板以路徑變量和/或查詢參數的形式傳遞數據
  • 經過flash屬性發生數據

7.5.1 經過URL模板進行重定向

@RequestMapping(value="/register", method=POST)
public String processRegistration(
        @Valid SpitterForm spitterForm,
        Errors errors) throws IllegalStateException, IOException {

    if (errors.hasErrors()) {
        return "registerForm";
    }
    Spitter spitter = spitterForm.toSpitter();
    spitterRepository.save(spitter);
    MultipartFile profilePicture = spitterForm.getProfilePicture();
    profilePicture.transferTo(new File("/tmp/spittr/" + spitter.getUsername() + ".jpg"));
    return "redirect:/spitter/" + spitter.getUsername();    //根據名字重定向
}

經過路徑變量和查詢參數的形式跨重定向傳遞數據是很簡單直接的方式,可是也有限制它只能發送簡單的值,如String和數字的值。在URl中,並無 辦法發送更爲複雜的值,但這正是flash屬性可以提供幫助。

### 7.5.2 使用flash屬性。

有個方案是將Spittr放到會話中,會話能長期存在,而且會話可以跨多個請求,因此咱們能夠在重定向以前將Spittr放到會話中,並在重定向後,從會話中取出 ,固然,咱們須要負責在重定向以後在會話中將其清理掉。

Spring提供了將數據發送爲flash屬性的功能,按照定義,flash屬性會一直攜帶這些數據,直到下一次請求,而後才消失

Spring提供了經過RedirectAttributes設置flash屬性的方式,這是Spring3.1引入的Modwl的一個子接口 。 RedirectAttributes提供了Model的全部功能。除此以外,還有幾個方法用來設置flash屬性。

public String processRegistration(
   @RequestPart(value="profilePictures", required=false) Part fileBytes,
   RedirectAttributes redirectAttributes,
   @Valid Spitter spitter,
   Errors errors) throws IOException {
 if (errors.hasErrors()) {
   return "registerForm";
 }

 spitterRepository.save(spitter);
 redirectAttributes.addAttribute("username", spitter.getUsername());
   //調用方法,將spitter做爲ket,Spitter做爲值。也能夠不設置值,根據值得類型自行判斷。
 redirectAttributes.addFlashAttribute(spitter);
 return "redirect:/spitter/" + spitter.getUsername();

在重定向以前,全部的flash屬性都會複製到會話中,在重定向結束後,存在會話中的 flash屬性會被取出,並從會話中轉移到模型之中。

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

showSpitterProfile()方法首先檢查是否存在key爲sptter的modle屬性。若是模型中包含的話,那就什麼都不用作了。包含的Spitter對象將會傳遞到視圖中進行渲染。若是不包含則從spitterRepository中查找Spitter,並將其放到模型中。

7.6 小節(最喜歡這裏)

在Spirng中,老是會有「尚未結束」的感受更多的特性,更多的選擇,以及實現開發目標的更多方式。Spring MVC有不少功能和技巧。

固然,Spirng MVC的環境搭建是由多種可選方案的一個領域。在本章中,咱們首先看來一下搭建Spring MVC中DispatcherServletContextLoaderListener的多種方式。還看到了如何調整DispatcherServlet的註冊功能以及如何註冊自定義的Servlet和FIlterr。若是你須要將應用部署到更老的服務器上,咱們還快速瞭解瞭如何使用web.xml聲明DispatcherServletContextLoaderListener.

而後咱們瞭解 如何處理Spirng MVC控制器所拋出的異常,儘管帶有@Requestmapping註解的方法能夠在自身的代碼中處理異常,可是若是將異常處理的代碼抽取到單獨的方法中,那麼控制器的代碼會整潔不少。

爲了採用一致的方式處理通用的任務,包括在應用中的全部控制器 中處理異常,Spirng 3.2 引入了@ControllerAdvice,他所建立的類可以將控制器的通用行爲抽取到同一個方法。

最後,咱們看了下如何跨重定向傳遞數據,包括Spring對flash屬性的支持:相似於模板,可是能在重定向後存活下來。這樣的話,就能採用很是恰當的方式爲POST請求執行一個重定向迴應。並且可以將處理POST請求時的模型數據傳遞過來,而後再重定向後使用或展示這些模型數據。

若是你有疑惑的話,那麼能夠告訴你,這就是我所說的「更多的功能」,其實,咱們並無討論到Spirng MVC 的每一個方面。咱們將會在16章中從新討論 Spirng MVC,到時你會看到如何使用它來建立REST API。

但如今,咱們將會暫時放下Spring MVC,看一下Spirng web Flow,這是一個構建在Spirng MVC 之上的流程框架,它可以引導用戶執行一系列嚮導步驟。

納悶,你忘記總結文件上傳了。期待下一章。

相關文章
相關標籤/搜索