1. 程式人生 > >SpringMVC之Controller常用註解功能全解析

SpringMVC之Controller常用註解功能全解析

一、簡介

在SpringMVC中,控制器Controller負責處理由DispatcherServlet分發的請求,它把使用者請求的資料經過業務處理層處理之後封裝成一個Model ,然後再把該Model返回給對應的View進行展示。在SpringMVC 中提供了一個非常簡便的定義Controller 的方法,你無需繼承特定的類或實現特定的介面,只需使用@Controller標記一個類是Controller,然後使用@RequestMapping和@RequestParam等一些註解用以定義URL 請求和Controller方法之間的對映,這樣的Controller就能被外界訪問到。此外Controller不會直接依賴於HttpServletRequest和HttpServletResponse等HttpServlet 物件,它們可以通過Controller的方法引數靈活的獲取到。

為了先對Controller有一個初步的印象,以下先定義一個簡單的Controller:

@Controller
public class MyController {
  @RequestMapping("/showView")
  public ModelAndView showView() {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("viewName");
    modelAndView.addObject(" 需要放到 model 中的屬性名稱 ", " 對應的屬性值,它是一個物件 ");
    return modelAndView;
  }
}

在上面的示例中,@Controller是標記在類MyController上面的,所以類MyController就是一個SpringMVC Controller物件了,然後使用@RequestMapping(“/showView”)標記在Controller方法上,表示當請求/showView.do的時候訪問的是MyController的showView方法,該方法返回了一個包括Model和View的ModelAndView物件。這些在後續都將會詳細介紹。

二、使用@Controller定義一個Controller控制器

@Controller用於標記在一個類上,使用它標記的類就是一個SpringMVC Controller物件。分發處理器將會掃描使用了該註解的類的方法,並檢測該方法是否使用了@RequestMapping註解。@Controller只是定義了一個控制器類,而使用@RequestMapping註解的方法才是真正處理請求的處理器,這個接下來就會講到。

單單使用@Controller標記在一個類上還不能真正意義上的說它就是SpringMVC的一個控制器類,因為這個時候Spring還不認識它。那麼要如何做Spring才能認識它呢?這個時候就需要我們把這個控制器類交給Spring來管理。拿MyController來舉一個例子:

@Controller
public class MyController {
  @RequestMapping("/showView")
  public ModelAndView showView() {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("viewName");
    modelAndView.addObject(" 需要放到 model 中的屬性名稱 ", " 對應的屬性值,它是一個物件 ");
    return modelAndView;
  }
}

這個時候有兩種方式可以把MyController交給Spring管理,好讓它能夠識別我們標記的@Controller。

第一種方式是在SpringMVC的配置檔案中定義MyController的bean物件。

<bean class="com.host.app.web.controller.MyController"/>

第二種方式是在SpringMVC的配置檔案中告訴Spring該到哪裡去找標記為@Controller的Controller控制器。

<context:component-scan base-package = "com.host.app.web.controller">
  <context:exclude-filter type = "annotation" expression = "org.springframework.stereotype.Service" />
</context:component-scan>

注:上面 context:exclude-filter標註的是不掃描@Service標註的類。

三、使用@RequestMapping來對映Request請求與處理器

可以使用@RequestMapping來對映URL到控制器類,或者是到Controller控制器的處理方法上。當@RequestMapping標記在Controller類上的時候,裡面使用@RequestMapping標記的方法的請求地址都是相對於類上的@RequestMapping而言的;當Controller類上沒有標記@RequestMapping註解時,方法上的@RequestMapping都是絕對路徑。這種絕對路徑和相對路徑所組合成的最終路徑都是相對於根路徑“/”而言的。

@Controller
public class MyController {
  @RequestMapping("/showView")
  public ModelAndView showView() {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("viewName");
    modelAndView.addObject(" 需要放到 model 中的屬性名稱 ", " 對應的屬性值,它是一個物件 ");
    return modelAndView;
  }
}

在這個控制器中,因為MyController沒有被@RequestMapping標記,所以當需要訪問到裡面使用了@RequestMapping標記的showView方法時,就是使用的絕對路徑/showView.do請求就可以了。

@Controller
@RequestMapping("/test")
public class MyController {
  @RequestMapping("/showView")
  public ModelAndView showView() {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("viewName");
    modelAndView.addObject(" 需要放到 model 中的屬性名稱 ", " 對應的屬性值,它是一個物件 ");
    return modelAndView;
  }
}

這種情況是在控制器上加了@RequestMapping註解,所以當需要訪問到裡面使用了@RequestMapping標記的方法showView()的時候就需要使用showView方法上@RequestMapping相對於控制器MyController上@RequestMapping 的地址,即/test/showView.do。

(一)使用URI模板

URI 模板就是在URI中給定一個變數,然後在對映的時候動態的給該變數賦值。如URI模板http://localhost/app/{variable1}/index.html,這個模板裡面包含一個變數variable1,那麼當我們請求http://localhost/app/hello/index.html的時候,該URL就跟模板相匹配,只是把模板中的variable1用hello來取代。在SpringMVC中,這種取代模板中定義的變數的值也可以給處理器方法使用,這樣我們就可以非常方便的實現URL的RestFul風格。這個變數在SpringMVC中是使用@PathVariable來標記的。

在SpringMVC中,我們可以使用@PathVariable來標記一個Controller的處理方法引數,表示該引數的值將使用URI模板中對應的變數的值來賦值。

@Controller
@RequestMapping("/test/{variable1}")
public class MyController {
  @RequestMapping("/showView/{variable2}")
  public ModelAndView showView(@PathVariable String variable1,
      @PathVariable("variable2") int variable2) {
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("viewName");
    modelAndView.addObject(" 需要放到 model 中的屬性名稱 ", " 對應的屬性值,它是一個物件 ");
    return modelAndView;
  }
}

在上面的程式碼中我們定義了兩個URI變數,一個是控制器類上的variable1,一個是showView方法上的variable2 ,然後在showView方法的引數裡面使用@PathVariable標記使用了這兩個變數。所以當我們使用/test/hello/showView/2.do來請求的時候就可以訪問到MyController的showView方法,這個時候variable1就被賦予值hello,variable2就被賦予值2,然後我們在showView方法引數裡面標註了引數variable1和variable2是來自訪問路徑的path變數,這樣方法引數variable1和variable2就被分別賦予hello和2。方法引數variable1是定義為String型別,variable2是定義為int型別,像這種簡單型別在進行賦值的時候Spring是會幫我們自動轉換的,關於複雜型別該如何來轉換在後續內容中將會講到。

在上面的程式碼中我們可以看到在標記variable1為path變數的時候我們使用的是@PathVariable,而在標記variable2的時候使用的是@PathVariable(“variable2”)。這兩者有什麼區別呢?第一種情況就預設去URI模板中找跟引數名相同的變數,但是這種情況只有在使用debug模式進行編譯的時候才可以,而第二種情況是明確規定使用的就是URI模板中的variable2變數。當不是使用debug模式進行編譯,或者是所需要使用的變數名跟引數名不相同的時候,就要使用第二種方式明確指出使用的是URI模板中的哪個變數。

除了在請求路徑中使用URI模板,定義變數之外,@RequestMapping中還支援萬用字元“*”。如下面的程式碼我就可以使用/myTest/whatever/wildcard.do訪問到Controller的testWildcard方法。

@Controller
@RequestMapping("/myTest")
public class MyController {
	@RequestMapping("*/wildcard")
	public String testWildcard() {
		System.out.println("wildcard------------");
		return "wildcard";
	}
}

(二)使用 @RequestParam繫結HttpServletRequest請求引數到控制器方法引數

@RequestMapping("requestParam")
public String testRequestParam(@RequestParam(required=false) String name, @RequestParam ("age") int age) {
  return "requestParam";
}

在上面程式碼中利用@RequestParam從HttpServletRequest中綁定了引數name到控制器方法引數name,綁定了引數age到控制器方法引數age。值得注意的是和@PathVariable一樣,當你沒有明確指定從request中取哪個引數時,Spring在程式碼是debug編譯的情況下會預設取更方法引數同名的引數,如果不是debug編譯的就會報錯。此外,當需要從request中繫結的引數和方法的引數名不相同的時候,也需要在@RequestParam中明確指出是要繫結哪個引數。在上面的程式碼中如果我訪問/requestParam.do?name=hello&age=1則Spring將會把request請求引數name的值hello賦給對應的處理方法引數name,把引數age的值1賦給對應的處理方法引數age。

在@RequestParam中除了指定繫結哪個引數的屬性value之外,還有一個屬性required,它表示所指定的引數是否必須在request屬性中存在,預設是true,表示必須存在,當不存在時就會報錯。在上面程式碼中我們指定了引數name的required的屬性為false,而沒有指定age的required屬性,這時候如果我們訪問/requestParam.do而沒有傳遞引數的時候,系統就會丟擲異常,因為age引數是必須存在的,而我們沒有指定。而如果我們訪問/requestParam.do?age=1的時候就可以正常訪問,因為我們傳遞了必須的引數age,而引數name 是非必須的,不傳遞也可以。

(三)使用@CookieValue繫結cookie的值到Controller方法引數

@RequestMapping("cookieValue")
public String testCookieValue(@CookieValue("hello") String cookieValue,
    @CookieValue String hello) {
  System.out.println(cookieValue + "-----------" + hello);
  return "cookieValue";
}

在上面的程式碼中我們使用@CookieValue綁定了cookie的值到方法引數上。上面一共綁定了兩個引數,一個是明確指定要繫結的是名稱為hello的cookie的值,一個是沒有指定。使用沒有指定的形式的規則和@PathVariable、@RequestParam的規則是一樣的,即在debug編譯模式下將自動獲取跟方法引數名同名的cookie值。

(四)使用@RequestHeader註解繫結HttpServletRequest頭資訊到Controller方法引數

@RequestMapping("testRequestHeader")
public String testRequestHeader(@RequestHeader("Host") String hostAddr,
    @RequestHeader String Host, @RequestHeader String host) {
  System.out.println(hostAddr + "-----" + Host + "-----" + host);
  return "requestHeader";
}

在上面的程式碼中我們使用了@RequestHeader綁定了HttpServletRequest請求頭host到Controller的方法引數。上面方法的三個引數都將會賦予同一個值,由此我們可以知道在繫結請求頭引數到方法引數的時候規則和@PathVariable、@RequestParam以及@CookieValue是一樣的,即沒有指定繫結哪個引數到方法引數的時候,在debug編譯模式下將使用方法引數名作為需要繫結的引數。但是有一點@RequestHeader跟另外三種繫結方式是不一樣的,那就是在使用@RequestHeader的時候是大小寫不敏感的,即@RequestHeader(“Host”)和@RequestHeader(“host”)繫結的都是Host頭資訊。記住在@PathVariable、@RequestParam和@CookieValue中都是大小寫敏感的。

(五)@RequestMapping的一些高階應用

在RequestMapping中除了指定請求路徑value屬性外,還有其他的屬性可以指定,如params、method和headers。這樣屬性都可以用於縮小請求的對映範圍。

1.params屬性

params屬性用於指定請求引數的,先看以下程式碼。

@RequestMapping(value = "testParams", params = { "param1=value1", "param2",
    "!param3" })
public String testParams() {
  System.out.println("test Params...........");
  return "testParams";
}

在上面的程式碼中我們用@RequestMapping的params屬性指定了三個引數,這些引數都是針對請求引數而言的,它們分別表示引數param1的值必須等於value1,引數param2必須存在,值無所謂,引數param3必須不存在,只有當請求/testParams.do並且滿足指定的三個引數條件的時候才能訪問到該方法。所以當請求/testParams.do?param1=value1&param2=value2的時候能夠正確訪問到該testParams方法,當請求/testParams.do?param1=value1&param2=value2&param3=value3的時候就不能夠正常的訪問到該方法,因為在@RequestMapping的params引數裡面指定了引數param3是不能存在的。

2.method屬性

method 屬性主要是用於限制能夠訪問的方法型別的。

@RequestMapping(value = "testMethod", method = { RequestMethod.GET,
    RequestMethod.DELETE })
public String testMethod() {
  return "method";
}

在上面的程式碼中就使用method引數限制了以GET或DELETE方法請求/testMethod.do的時候才能訪問到該Controller的testMethod方法。

3.headers屬性

使用headers屬性可以通過請求頭資訊來縮小@RequestMapping的對映範圍。

@RequestMapping(value = "testHeaders", headers = { "host=localhost",
    "Accept" })
public String testHeaders() {
  return "headers";
}

headers屬性的用法和功能與params屬性相似。在上面的程式碼中當請求/testHeaders.do的時候只有當請求頭包含Accept資訊,且請求的host為localhost的時候才能正確的訪問到testHeaders方法。

(六)以@RequestMapping標記的處理器方法支援的方法引數和返回型別

支援的方法引數型別:

(1)HttpServlet物件,主要包括HttpServletRequest、HttpServletResponse和HttpSession物件。這些引數Spring在呼叫處理器方法的時候會自動給它們賦值,所以當在處理器方法中需要使用到這些物件的時候,可以直接在方法上給定一個方法引數的申明,然後在方法體裡面直接用就可以了。但是有一點需要注意的是在使用HttpSession物件的時候,如果此時HttpSession物件還沒有建立起來的話就會有問題。

(2)Spring自己的WebRequest物件。使用該物件可以訪問到存放在HttpServletRequest和HttpSession中的屬性值。

(3)InputStream、OutputStream、Reader和Writer。InputStream和Reader是針對HttpServletRequest而言的,可以從裡面取資料;OutputStream和Writer是針對HttpServletResponse而言的,可以往裡面寫資料。

(4)使用@PathVariable、@RequestParam、@CookieValue和@RequestHeader標記的引數。

(5)使用@ModelAttribute標記的引數。

(6)java.util.Map、Spring封裝的Model和ModelMap。這些都可以用來封裝模型資料,用來給檢視做展示。

(7)實體類。可以用來接收上傳的引數。

(8)Spring封裝的MultipartFile。用來接收上傳檔案的。

(9)Spring封裝的Errors和BindingResult物件。這兩個物件引數必須緊接在需要驗證的實體物件引數之後,它裡面包含了實體物件的驗證結果。

支援的返回型別:

(1)一個包含模型和檢視的ModelAndView物件。

(2)一個模型物件,這主要包括Spring封裝好的Model和ModelMap,以及java.util.Map,當沒有檢視返回的時候檢視名稱將由RequestToViewNameTranslator來決定。

(3)一個View物件。這個時候如果在渲染檢視的過程中模型的話就可以給處理器方法定義一個模型引數,然後在方法體裡面往模型中新增值。

(4)一個String字串。這往往代表的是一個檢視名稱。這個時候如果需要在渲染檢視的過程中需要模型的話就可以給處理器方法一個模型引數,然後在方法體裡面往模型中新增值就可以了。

(5)返回值是void。這種情況一般是我們直接把返回結果寫到HttpServletResponse中了,如果沒有寫的話,那麼Spring將會利用RequestToViewNameTranslator來返回一個對應的檢視名稱。如果檢視中需要模型的話,處理方法與返回字串的情況相同。

(6)如果處理器方法被註解@ResponseBody標記的話,那麼處理器方法的任何返回型別都會通過HttpMessageConverters轉換之後寫到HttpServletResponse中,而不會像上面的那些情況一樣當做檢視或者模型來處理。

(7)除以上幾種情況之外的其他任何返回型別都會被當做模型中的一個屬性來處理,而返回的檢視還是由RequestToViewNameTranslator來決定,新增到模型中的屬性名稱可以在該方法上用@ModelAttribute(“attributeName”)來定義,否則將使用返回型別的類名稱的首字母小寫形式來表示。使用@ModelAttribute標記的方法會在@RequestMapping標記的方法執行之前執行。

(七)使用@ModelAttribute和@SessionAttributes傳遞和儲存資料

SpringMVC支援使用@ModelAttribute和@SessionAttributes在不同的模型和控制器之間共享資料。@ModelAttribute主要有兩種使用方式,一種是標註在方法上,一種是標註在Controller方法引數上。

當@ModelAttribute標記在方法上的時候,該方法將在處理器方法執行之前執行,然後把返回的物件存放在session或模型屬性中,屬性名稱可以使用@ModelAttribute(“attributeName”)在標記方法的時候指定,若未指定,則使用返回型別的類名稱(首字母小寫)作為屬性名稱。關於@ModelAttribute標記在方法上時對應的屬性是存放在session中還是存放在模型中,我們來做一個實驗,看下面一段程式碼。

@Controller
@RequestMapping("/myTest")
public class MyController {

  @ModelAttribute("hello")
  public String getModel() {
    System.out.println("-------------Hello---------");
    return "world";
  }

  @ModelAttribute("intValue")
  public int getInteger() {
    System.out.println("-------------intValue---------------");
    return 10;
  }

  @RequestMapping("sayHello")
  public void sayHello(@ModelAttribute("hello") String hello,
      @ModelAttribute("intValue") int num,
      @ModelAttribute("user2") User user, Writer writer,
      HttpSession session) throws IOException {
    writer.write("Hello " + hello + " , Hello " + user.getUsername() + num);
    writer.write("\r");
    Enumeration enume = session.getAttributeNames();
    while (enume.hasMoreElements())
      writer.write(enume.nextElement() + "\r");
  }

  @ModelAttribute("user2")
  public User getUser() {
    System.out.println("---------getUser-------------");
    return new User(3, "user2");
  }
}

當我們請求/myTest/sayHello.do的時候使用@ModelAttribute標記的方法會先執行,然後把它們返回的物件存放到模型中。最終訪問到sayHello方法的時候,使用@ModelAttribute標記的方法引數都能被正確的注入值。執行結果如下所示:

Hello world , Hello user210

由執行結果我們可以看出來,此時session中沒有包含任何屬性,也就是說上面的那些物件都是存放在模型屬性中,而不是存放在session屬性中。那要如何才能存放在session屬性中呢?這個時候我們先引入一個新的概念@SessionAttributes,它的用法會在講完@ModelAttribute之後介紹,這裡我們就先拿來用一下。我們在MyController類上加上@SessionAttributes屬性標記哪些是需要存放到session中的。看下面的程式碼:

@Controller
@RequestMapping("/myTest")
@SessionAttributes(value = { "intValue", "stringValue" }, types = { User.class })
public class MyController {

  @ModelAttribute("hello")
  public String getModel() {
    System.out.println("-------------Hello---------");
    return "world";
  }

  @ModelAttribute("intValue")
  public int getInteger() {
    System.out.println("-------------intValue---------------");
    return 10;
  }

  @RequestMapping("sayHello")
  public void sayHello(Map<String, Object> map,
      @ModelAttribute("hello") String hello,
      @ModelAttribute("intValue") int num,
      @ModelAttribute("user2") User user, Writer writer,
      HttpServletRequest request) throws IOException {
    map.put("stringValue", "String");
    writer.write("Hello " + hello + " , Hello " + user.getUsername() + num);
    writer.write("\r");
    HttpSession session = request.getSession();
    Enumeration enume = session.getAttributeNames();
    while (enume.hasMoreElements())
      writer.write(enume.nextElement() + "\r");
    System.out.println(session);
  }

  @ModelAttribute("user2")
  public User getUser() {
    System.out.println("---------getUser-------------");
    return new User(3, "user2");
  }
}

在上面程式碼中我們指定了屬性為intValue或stringValue或者型別為User的都會放到Session中,利用上面的程式碼當我們訪問/myTest/sayHello.do的時候,結果如下:

Hello world , Hello user210

仍然沒有打印出任何session屬性,這是怎麼回事呢?怎麼定義了把模型中屬性名為intValue的物件和型別為User的物件存到session中,而實際上沒有加進去呢?難道我們錯啦?我們當然沒有錯,只是在第一次訪問/myTest/sayHello.do的時候@SessionAttributes定義了需要存放到session中的屬性,而且這個模型中也有對應的屬性,但是這個時候還沒有加到session中,所以session中不會有任何屬性,等處理器方法執行完成後Spring才會把模型中對應的屬性新增到session中。所以當請求第二次的時候就會出現如下結果:

Hello world , Hello user210

user2

intValue

stringValue

當@ModelAttribute標記在處理器方法引數上的時候,表示該引數的值將從模型或者Session中取對應名稱的屬性值,該名稱可以通@ModelAttribute(“attributeName”)來指定,若未指定,則使用引數型別的類名稱(首字母小寫)作為屬性名稱。

@Controller
@RequestMapping("/myTest")
public class MyController {

  @ModelAttribute("hello")
  public String getModel() {
    return "world";
  }

  @RequestMapping("sayHello")
  public void sayHello(@ModelAttribute("hello") String hello, Writer writer)
      throws IOException {
    writer.write("Hello " + hello);
  }
}

在上面程式碼中,當我們請求/myTest/sayHello.do的時候,由於MyController中的方法getModel使用了註解@ModelAttribute進行標記,所以在執行請求方法sayHello之前會先執行getModel方法,這個時候getModel方法返回一個字串world並把它以屬性名hello儲存在模型中,接下來訪問請求方法sayHello的時候,該方法的hello引數使用@ModelAttribute(“hello”)進行標記,這意味著將從session或者模型中取屬性名稱為hello的屬性值賦給hello引數,所以這裡hello引數將被賦予值world,所以請求完成後將會在頁面上看到Helloworld字串。

@SessionAttributes用於標記需要在Session中使用到的資料,包括從Session中取資料和存資料。@SessionAttributes一般是標記在Controller類上的,可以通過名稱、型別或者名稱加型別的形式來指定哪些屬性是需要存放在session中的。

@Controller
@RequestMapping("/myTest")
@SessionAttributes(value = { "user1", "blog1" }, types = { User.class,
    Blog.class })
public class MyController {

  @RequestMapping("setSessionAttribute")
  public void setSessionAttribute(Map<String, Object> map, Writer writer)
      throws IOException {
    User user = new User(1, "user");
    User user1 = new User(2, "user1");
    Blog blog = new Blog(1, "blog");
    Blog blog1 = new Blog(2, "blog1");
    map.put("user", user);
    map.put("user1", user1);
    map.put("blog", blog);
    map.put("blog1", blog1);
    writer.write("over.");
  }

  @RequestMapping("useSessionAttribute")
  public void useSessionAttribute(Writer writer,
      @ModelAttribute("user1") User user1,
      @ModelAttribute("blog1") Blog blog1) throws IOException {
    writer.write(user1.getId() + "--------" + user1.getUsername());
    writer.write("\r");
    writer.write(blog1.getId() + "--------" + blog1.getTitle());
  }

  @RequestMapping("useSessionAttribute2")
  public void useSessionAttribute(Writer writer,
      @ModelAttribute("user1") User user1,
      @ModelAttribute("blog1") Blog blog1, @ModelAttribute User user,
      HttpSession session) throws IOException {
    writer.write(user1.getId() + "--------" + user1.getUsername());
    writer.write("\r");
    writer.write(blog1.getId() + "--------" + blog1.getTitle());
    writer.write("\r");
    writer.write(user.getId() + "---------" + user.getUsername());
    writer.write("\r");
    Enumeration enume = session.getAttributeNames();
    while (enume.hasMoreElements())
      writer.write(enume.nextElement() + " \r");
  }

  @RequestMapping("useSessionAttribute3")
  public void useSessionAttribute(@ModelAttribute("user2") User user) {

  }
}

在上面程式碼中我們可以看到在MyController上面使用了@SessionAttributes標記了需要使用到的Session屬性。可以通過名稱和型別指定需要存放到Session中的屬性,對應@SessionAttributes註解的value和types屬性。當使用的是types屬性的時候,那麼使用的Session屬性名稱將會是對應型別的名稱(首字母小寫)。當value和types兩個屬性都使用到了的時候,這時候取的是它們的並集,而不是交集,所以上面程式碼中指定要存放在Session中的屬性有名稱為user1或blog1的物件,或型別為User或Blog的物件。在上面程式碼中我們首先訪問/myTest/setSessionAttribute.do,該請求將會請求到MyController的setSessionAttribute方法,在該方法中,我們往模型裡面添加了user、user1、blog和blog1四個屬性,因為它們或跟類上的@SessionAttributes定義的需要存到session中的屬性名稱相同或型別相同,所以在請求完成後這四個屬性都將新增到session屬性中。接下來訪問/myTest/useSessionAttribute.do,該請求將會請求MyController的useSessionAttribute(Writerwriter,@ModelAttribute(“user1”)Useruser1,@ModelAttribute(“blog1”)Blogblog)方法,該方法引數中用@ModelAttribute指定了引數user1和引數blog1是需要從session或模型中繫結的,恰好這個時候session中已經有了這兩個屬性,所以這個時候在方法執行之前會先繫結這兩個引數。執行結果如下圖所示:

2---user1

2---blog1

接下來訪問/myTest/useSessionAttribute2.do,這個時候請求的是上面程式碼中對應的第二個useSessionAttribute方法,方法引數user、user1和blog1。

用@ModelAttribute聲明瞭需要session或模型屬性注入,我們知道在請求/myTest/setSessionAttribute.do的時候這些屬性都已經新增到了session中,所以該請求的結果會如下所示:

2---user1 2---blog1 1---user blog user user1 blog1

接下來訪問/myTest/useSessionAttribute3.do,這個時候請求的是上面程式碼中對應的第三個useSessionAttribute方法,我們可以看到該方法的方法引數user使用了@ModelAttribute(“user2”)進行標記,表示user需要session中的user2屬性來注入,但是這個時候我們知道session中是不存在user2屬性的,所以這個時候就會報錯了。

(八)定製自己的型別轉換器

在通過處理器方法引數接收request請求引數繫結資料的時候,對於一些簡單的資料型別Spring會幫我們自動進行型別轉換,而對於一些複雜的型別由於Spring沒法識別,所以也就不能幫助我們進行自動轉換了,這個時候如果我們需要Spring來幫我們自動轉換的話就需要我們給Spring註冊一個對特定型別的識別轉換器。Spring允許我們提供兩種型別的識別轉換器,一種是註冊在Controller中的,一種是註冊在SpringMVC的配置檔案中。聰明的讀者看到這裡應該可以想到它們的區別了,定義在Controller中的是區域性的,只在當前Controller中有效,而放在SpringMVC配置檔案中的是全域性的,所有Controller都可以拿來使用。

1.在@InitBinder標記的方法中定義區域性的型別轉換器

我們可以使用@InitBinder註解標註在Controller方法上,然後在方法體裡面註冊資料繫結的轉換器,這主要是通過WebDataBinder進行的。我們可以給需要註冊資料繫結的轉換器的方法一個WebDataBinder引數,然後給該方法加上@InitBinder註解,這樣當該Controller中在處理請求方法時如果發現有不能解析的物件的時候,就會看該類中是否有使用@InitBinder標記的方法,如果有就會執行該方法,然後看裡面定義的型別轉換器是否與當前需要的型別匹配。

@Controller
@RequestMapping("/myTest")
public class MyController {

  @InitBinder
  public void dataBinder(WebDataBinder binder) {
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true); // 第二個引數表示是否允許為空
    binder.registerCustomEditor(Date.class, propertyEditor);
  }

  @RequestMapping("dataBinder/{date}")
  public void testDate(@PathVariable Date date, Writer writer)
      throws IOException {
    writer.write(String.valueOf(date.getTime()));
  }

}

在上面的程式碼中當我們請求/myTest/dataBinder/20121212.do的時候,Spring就會利用@InitBinder標記的方法裡面定義的型別轉換器把字串20121212轉換為一個Date物件。這樣定義的型別轉換器是區域性的型別轉換器,一旦出了這個Controller就不會再起作用。型別轉換器是通過WebDataBinder物件的registerCustomEditor方法來註冊的,要實現自己的型別轉換器就要實現自己的PropertyEditor物件。Spring已經給我們提供了一些常用的屬性編輯器,如CustomDateEditor、CustomBooleanEditor等。

PropertyEditor是一個介面,要實現自己的PropertyEditor類我們可以實現這個介面,然後實現裡面的方法。但是PropertyEditor裡面定義的方法太多了,這樣做比較麻煩。在java中有一個封裝類是實現了PropertyEditor介面的,它是PropertyEditorSupport類。所以如果需要實現自己的PropertyEditor的時候只需要繼承PropertyEditorSupport類,然後重寫其中的一些方法。一般就是重寫setAsText和getAsText方法就可以了,setAsText方法是用於把字串型別的值轉換為對應的物件的,而getAsText方法是用於把物件當做字串來返回的。在setAsText中我們一般先把字串型別的物件轉為特定的物件,然後利用PropertyEditor的setValue方法設定轉換後的值。在getAsText方法中一般先使用getValue方法取代當前的物件,然後把它轉換為字串後再返回給getAsText方法。下面是一個示例:

@InitBinder
public void dataBinder(WebDataBinder binder) {
  // 定義一個 User 屬性編輯器
  PropertyEditor userEditor = new PropertyEditorSupport() {

    @Override
    public String getAsText() {
      // TODO Auto-generated method stub
      User user = (User) getValue();
      return user.getUsername();
    }

  @Override
  public void setAsText(String userStr)
      throws IllegalArgumentException {
    // TODO Auto-generated method stub
      User user = new User(1, userStr);
      setValue(user);
    }
  };
  // 使用 WebDataBinder 註冊 User 型別的屬性編輯器
  binder.registerCustomEditor(User.class, userEditor);
}

2.實現 WebBindingInitializer 介面定義全域性的型別轉換器

如果需要定義全域性的型別轉換器就需要實現自己的WebBindingInitializer物件,然後把該物件注入到AnnotationMethodHandlerAdapter中,這樣Spring在遇到自己不能解析的物件的時候就會到全域性的WebBindingInitializer的initBinder方法中去找,每次遇到不認識的物件時,initBinder方法都會被執行一遍。

public class MyWebBindingInitializer implements WebBindingInitializer {

  @Override
  public void initBinder(WebDataBinder binder, WebRequest request) {
    // TODO Auto-generated method stub
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true);
    binder.registerCustomEditor(Date.class, propertyEditor);
  }

}

定義了這麼一個WebBindingInitializer物件之後Spring還是不能識別其中指定的物件,這是因為我們只是定義了WebBindingInitializer物件,還沒有把它交給Spring,Spring不知道該去哪裡找解析器。要讓Spring能夠識別還需要我們在SpringMVC的配置檔案中定義一個AnnotationMethodHandlerAdapter型別的bean物件,然後利用自己定義的WebBindingInitializer覆蓋它的預設屬性webBindingInitializer。

<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name = "webBindingInitializer" >
      <bean class = "com.host.app.web.util.MyWebBindingInitializer" />
  </property >
</bean>

3.觸發資料繫結方法的時間

當Controller處理器方法引數使用@RequestParam、@PathVariable、@RequestHeader、@CookieValue和@ModelAttribute標記的時候都會觸發initBinder方法的執行,這包括使用WebBindingInitializer定義的全域性方法和在Controller中使用@InitBinder標記的區域性方法。而且每個使用了這幾個註解標記的引數都會觸發一次initBinder方法的執行,這也意味著有幾個引數使用了上述註解就會觸發幾次initBinder方法的執行。

(九)使用@Value用法

Spring通過註解獲取*.porperties檔案的內容,除了xml配置外,還可以通過@value方式來獲取。

使用方式必須在當前類使用@Component或者@Controller,xml檔案內配置的是通過pakage掃描方式例如:<context:component-scanbase-package="pakage_name"/>

public class AbnormalWarningBulletinJob extends AbstractWantJob {
  // 訪問共享檔案遵循smb協議
  private static final String SMB_PREIFX = "smb:";
  
  @Autowired(required=false)
  @Qualifier
  private SmsSendService sendService;
  
  @Value("${abnormal.warning.bulletin}")
  private String smb_url;

}