1. 程式人生 > >第二章 Spring MVC基本配置

第二章 Spring MVC基本配置

本章目標

  • @Controlol註解
  • @RequestMaping註解
  • Servlet API
  • 請求引數的處理
  • 返回結果的處理

為什麼使用註解

  • 目前越來越多的主流框架都支援註解,同樣Spring也支援基於註解的"零配置"。
  • 註解相比XML的優勢:
    • 它可以充分利用 Java 的反射機制獲取類結構資訊,這些資訊可以有效減少配置的工作。
    • 註解和 Java 程式碼位於一個檔案中,更加便於維護。
    • 注意:必須在Spring 2.5版本後才能使用註解。

使用註解

註解方式將Bean的定義資訊和Bean實現類結合在一起,Spring提供的註解有

  • @Component:一個普通的Bean類。
  • @Repository :用於標註持久層DAO類
  • @Service :用於標註業務層類
  • @Controller :用於標註控制器類

示例

// 與在XML配置檔案中編寫<bean id="userDao" class="dao.impl.UserDao" /> 等效
@Repository("userDao") // Spring有預設的命名策略,使用非限定類名,第一個字母小寫,也可以在註解中使用value屬性指定元件的名稱。
public class UserDao implements IDao {

}

@Controller

  • 作用:將當前類作為一個控制器類

  • 如果使用註解方式,必須在Spring中新增自動掃描的路徑。

  <!-- 配置Spring MVC自動掃描的路徑 -->
  <context:component-scan base-package="com.znsd.controller" />
  • 不適用註解,手動新增控制器的路徑
  <!-- 手動新增控制器的路徑 -->
  <bean class="com.lxit.controller.MyController"/>

@RequestMapping

  • Spring MVC使用@RequestMapping註解為控制器指定請求的URL。
  • 在控制器的類定義及方法定義處都可以標記。
    • 類定義處:提供初步的對映資訊,為該類下所有請求方法新增字首。
    • 方法定義處:提供進一步的對映資訊,提供方法的請求路徑。
  • DispacherServlet截獲請求後,就通過控制器上的@RequestMapping提供的對映資訊確認請求所對應的處理方法。

@RequestMapping示例

  • 為類新增RequestMapping更改請求路徑
  @Controller
  @RequestMapping("world")
  public class HelloWorld { 
    @RequestMapping("/hello")
    public String hello(){
        System.out.println("hello spring mvc");
        return "hello";
    }
  }
  • 訪問請求路徑
  http://localhost:8080/SpringMVCDemo/world/hello

HTTP請求

  • 標準的HTTP請求 路徑。

image

對映請求引數,請求方法和請求頭

  • @RequestMapping除了可以使用請求URL對映請求外,還可以使用請求方法,請求引數和請求頭對映請求。
  • @RequestMapping的value、method、params及heads分別表示請求url,請求方法,請求引數及請求頭的對映條件,他們之間是與的關係,聯合使用多個條件可以讓請求更加精確化。
  • params和headers支援簡單的表示式。
    • param1:表示請求必須包含param1的請求引數。
    • !param1:表示請求不能包含param1的請求引數。
    • param1!=value1:表示請求引數param1不能等於value1。
    • {"param1=value1","param2"}:表示請求引數param1必須等於value1,必須包含請求引數param2。

請求方法,請求引數,請求頭示例

  • 設定請求地址為test1,以post方式提交,必須帶一個引數userid
  @RequestMapping(value="/test1", method = RequestMethod.POST, params = "userid")
  public String test1(){
    System.out.println("test1");
    return "hello";
  }
  • 設定請求地址為test2,請求頭contenttype屬性必須以text/開頭。
  @RequestMapping(value = "/test2", headers = "contentType=text/*")
  public String test2(){
    System.out.println("test2");
    return "hello";
  }

使用@RequestMapping對映請求

  • @RequestMapping還支援Ant風格的URL。

  • Ant支援3中萬用字元

    • ?:匹配檔案中一個字元。
    • *:匹配檔案中任意字元。
    • **:匹配多層路徑。
  /user/*/create:匹配/user/aaa/create,/user/bbb/crate
  /user/**/create:匹配/user/create,/user/aaa/bbb/create
  /user/create??:匹配/user/createaa,/user/createbb

引數的處理

  • @PathVariable:URL模版方式
  • @RequestParam:獲取請求引數
  • @RequestHeader:獲取請求頭內容

@PathVariable

  • @PathVariable:用來對映URL中的佔位符。對映的變數名必須和佔位符中的名稱一致。
  @RequestMapping("/delete/{userid}")
  public String delete(@PathVariable("userid")int userid) {
    System.out.println("userid=" + userid);
    return "hello";
  }

20180705161846

@RequestParam

  • @RequtParam:獲取頁面傳遞過來的引數,GET和POST都支援。
  @RequestMapping("/testRequestParam")
  public String testRequestParam(@RequestParam("username")String username, @RequestParam("age")int age){
    System.out.println("username=" + username);
    System.out.println("age=" + age);
    return "hello";
  }

20180705162050

@RequestParam

RequestParam有三個屬性:

  • value:指定引數的名稱
  • required:指定引數是否為必填
  • defaultValue:指定引數的預設值

當引數與傳遞的引數同名時,可以省略@Requestparam

@RequestHeader

  • 請求頭包含了若干的屬性,用來獲取客戶端的資訊。通過@RequestHeader可以用來獲取請求頭中的資訊
  @RequestMapping("/testRequestHeader")
  public String testRequestHeader(@RequestHeader("Accept-Language") String language){
    System.out.println("language=" + language);
    return "hello";
  }

20180705162244

@CookieValue

  • @CookieValue:用來獲取客戶端Cookie資訊。
  @RequestMapping("/testCookieValue")
  public String testCookieValue(@CookieValue("JSESSIONID")String sessionid){
    System.out.println("sessionid=" + sessionid);
    return "hello";
  }

20180705162449

實體物件繫結請求引數

  • Spring MVC會直接將頁面元素和實體物件進行匹配,自動轉換為實體物件。並且支援級聯屬性。如address.city等。
  <form action="world/user/create" method="post">
    姓名:<input type="text" name="username"><br>
    密碼:<input type="text" name="userpass"><br>
    年齡:<input type="text" name="age"><br>
    <input type="submit" value="提交" />
  </form>
  public class User {
    private String username;
    private String userpass;
    private int age;
  }
  @RequestMapping(value = "/user/create", method = RequestMethod.POST)
  public String createUser(User user) {
    System.out.println(user.toString());
    return "hello";
  }

練習:SpringMVC實現使用者註冊

使用Spring MVC實現使用者註冊

小結

  • 從頁面傳遞引數的方式
  • @PathVariable
  • @RequestParam
  • @RequestHeader
  • @CookieValue
  • POJO

Servlet API

Spring MVC可以使用Servlet API作為請求方法的引數。

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • Local
  • InputStream
  • OutputStream
  • Read
  • Write

示例

  • 將ServletAPI作為方法的引數,方法中就可以使用api所對應的方法。
  @RequestMapping("/testServletAPI")
  public String testServletAPI(HttpServletRequest request,
  HttpServletResponse response,HttpSession session){
    return "hello";
  }

org.springframework.web.servlet.mvc.annotation.ServletHandlerMethodInvoker原始碼分析

@Override
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception {
    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);

    if (ServletRequest.class.isAssignableFrom(parameterType) ||
        MultipartRequest.class.isAssignableFrom(parameterType)) {
        Object nativeRequest = webRequest.getNativeRequest(parameterType);
        if (nativeRequest == null) {
            throw new IllegalStateException(
                "Current request is not of type [" + parameterType.getName() + "]: " + request);
        }
        return nativeRequest;
    }
    else if (ServletResponse.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        Object nativeResponse = webRequest.getNativeResponse(parameterType);
        if (nativeResponse == null) {
            throw new IllegalStateException(
                "Current response is not of type [" + parameterType.getName() + "]: " + response);
        }
        return nativeResponse;
    }
    else if (HttpSession.class.isAssignableFrom(parameterType)) {
        return request.getSession();
    }
    else if (Principal.class.isAssignableFrom(parameterType)) {
        return request.getUserPrincipal();
    }
    else if (Locale.class == parameterType) {
        return RequestContextUtils.getLocale(request);
    }
    else if (InputStream.class.isAssignableFrom(parameterType)) {
        return request.getInputStream();
    }
    else if (Reader.class.isAssignableFrom(parameterType)) {
        return request.getReader();
    }
    else if (OutputStream.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        return response.getOutputStream();
    }
    else if (Writer.class.isAssignableFrom(parameterType)) {
        this.responseArgumentUsed = true;
        return response.getWriter();
    }
    return super.resolveStandardArgument(parameterType, webRequest);
}

處理模型資料

Spring MVC提供一下幾種途徑返回模型資料:

  • ModeAndView:將處理方法的返回型別設定為ModeAndView,方法體即可通過該模型物件新增模型資料。
  • Map及Model形參:當形參為Map,Model,ModelMap時,處理方法返回時,Map中的資料會自動新增到模型中。
  • @SessionAttributes:將模型中的某個屬性儲存到Session中,以便多個請求之間共享這個屬性。
  • @ModelAttribute:方法形參標記該註解後,形參物件就會放到模型中。

@ModelAndView

  • 控制器處理方法如果返回ModelAndView,即包含檢視資訊,也包含模型資訊。

  • 構造方法:提供了多種構造方法的過載

  • 新增模型資料:

    • ModelAndView addObject(String attributeName, Object attributeValue);
    • ModelAndView addAllObjects(Map modelMap) ;
  • 新增檢視

    • void setView(View view);
    • void setViewName(String viewName);
  @RequestMapping("/testModelAndView")
  public ModelAndView testModelAndView(){
    ModelAndView modelAndView = new ModelAndView("hello");
    //新增單個值
    modelAndView.addObject("h","Hello Spring MVC");     
    return modelAndView;
  }

Map形參

  • Spring MVC在內部使用了一個Model介面儲存模型資料。

  • Spring MVC在呼叫方法前會建立一個隱含的模型物件作為資料模型的儲存容器。如果傳入的引數為Map或者Model型別,SpringMVC會自動將物件儲存到模型資料中。

  @RequestMapping("/testMap")
  public String testMap(Map<String,Object> map){
    map.put("mapdata", "map data");
    return "hello";
  }

@SessionAttributes

  • 如果希望在多個請求之間共享某個模型的資料,則可以在控制器類上標註一個@SessionAttributes,SpringMVC會將對應的屬性儲存到Session中。
  • @SessionAttributes除了可以通過屬性名指定需要放到會話中的屬性外,還可以通過模型屬性的物件型別指定哪些型別放到Session中。
    • @SessionAttributes(types=User.class) :將隱含模型中所有型別為User屬性新增到Session中。
    • @SessionAttributes(value={"user1","user2"}):將user1物件和user2放入Session中。
    • @SessionAttributes(types={User.class,Dept.class})
    • @SessionAttributes(value={"user1","user2"},types={Dept.class}):
  • @SessionAttributes只能用來修飾類。

@SessionAttributes

  • 儲存使用者到Session中
  @SessionAttributes("user")
  @Controller
  @RequestMapping("world")
  public class HelloWorld {
    @RequestMapping("/testSession")
    public String testSession(Map<String,Object> map){
        User user = new User();
        user.setUsername("zhangsan");
        user.setUserpass("123");
        user.setAge(20);
        map.put("user", user);
        return "hello";
    }
  }
  • @SessionAttributes註解只能放在類的前面,而不能放在方法前。

@ModelAttribute

  • 在方法定義上定義@ModelAttribute註解:
    • SpringMVC呼叫方法前,會逐個呼叫方法上標註了@ModelAttributes的方法。
    • 將@ModelAttributes中的屬性儲存到map中,可以在執行表單提交生成物件之前,替換執行方法同名的形參。
  • 在方法的形參上定義@ModelAttribute註解:
    • 可以從隱含物件中獲取隱含的模型中獲取物件,再將請求引數繫結到物件中,再傳入形參。
    • 將方法形參物件新增到模型中。
  • 執行流程:
    • 執行@ModelAttribute註解修飾的方法:從資料庫中取出物件,把物件放入到Map中,鍵為:user
    • Spring MVC 從Map中取出User物件,並把表單的請求引數賦給該User物件對應的屬性。
    • Spring MVC 把上述物件傳入目標方法的引數。引數名稱必須和@ModelAttribute 的Map中儲存的user物件同名。

@ModelAttribute 例子

@ModelAttribute
public User getUser(){
    User user = new User();
    System.out.println("呼叫 getUser 方法");
    //預設儲存名字為類名首字母小寫的user物件到Request中
    return user;
}

@ModelAttribute
public void getUserById(Integer id,Map<String,Object> map){
    User myuser = new User();
    map.put("myuser", myuser);
    //手動指定user物件的名稱,到Request中
    System.out.println("呼叫 getUser 方法");
}

@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("user")User user){
    System.out.println(user);
    return "hello";
}

由@SessionAttributes引發的異常

  • 當類使用@SessionAttributes修飾,而方法中使用了和SessionAttributes修飾同名的對映引數,確沒有新增@ModelAttribute修飾時,則會報錯。

image

解決方案

  • 引數前使用@ModelAttribute修改對映的名稱。
  • 和@SessionAttributes中的名稱不同。

總結

  • @Controlol註解
  • @RequestMaping註解
  • Servlet API
  • 請求引數的處理
  • 返回結果的處理