Spring入門(十三):Spring MVC常用注解講解


在使用Spring MVC開發Web應用程序時,控制器Controller的開發非常重要,雖然說視圖(JSP或者是Thymeleaf)也很重要,因為它才是直接呈現給用戶的,不過由於現在前端越來越重要,很多公司都開始采用前后端分離的開發模式,所以我們暫時可以將精力放在開發控制器上。

使用Spring MVC開發控制器主要使用以下7個注解:

  1. @Controller
  2. @RequestMapping
  3. @ResponseBody
  4. @RequestParam
  5. @PathVariable
  6. @RequestBody
  7. @RestController

接下來,我們依次講解每個注解的使用方法。

1. @Controller

先回顧下上篇博客中新建的簡單控制器HelloController:

package chapter05.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HelloController {
    @RequestMapping(value = "index", method = RequestMethod.GET)
    public String hello() {
        // 這里返回的邏輯視圖名
        return "index";
    }
}

這里@Controller注解的作用是用來聲明控制器,它的源碼如下所示:

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    String value() default "";
}

這里值得注意的是,@Controller注解使用了@Component注解,而@Component注解我們並不陌生,它用來聲明一個Bean。

雖然有些書中說可以把@Controller注解替換為@Component注解,運行沒有差別,只是表意性差一點,但是如果你將上面代碼中的@Controller注解修改為@Component注解,然后重新打包發布到Tomcat,會發現訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/index時,報如下所示的404錯誤:

@Component注解還原為@Controller注解,然后重新打包發布到Tomcat,再次訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/index時,訪問正常:

所以,在Spring MVC中聲明控制器時,推薦使用@Controller注解。

注意事項:程序員在閱讀技術書籍時,要多思考,多嘗試,因為書籍中講解的,很可能是錯的。

2. @RequestMapping

@RequestMapping注解用來映射Web請求,它有2種使用形式:

  1. 應用在方法級別,如上面的代碼中展示的那樣。
  2. 應用在類級別,當控制器在類級別上添加@RequestMapping注解時,這個注解會應用到控制器的所有處理器方法上,處理器方法上的@RequestMapping注解會對類級別上的@RequestMapping注解的聲明進行補充。

@RequestMapping注解常用的3個參數如下所示:

  1. value:指定映射的URL地址,如index
  2. method:指定映射的請求類型,如GET請求、POST請求等
  3. produces:指定返回的response的媒體類型和字符集,如application/json;charset=UTF-8。

指定method值時使用org.springframework.web.bind.annotation.RequestMethod枚舉:

package org.springframework.web.bind.annotation;

public enum RequestMethod {
    GET,
    HEAD,
    POST,
    PUT,
    PATCH,
    DELETE,
    OPTIONS,
    TRACE;

    private RequestMethod() {
    }
}

指定produces值時一般使用org.springframework.http.MediaType類下的常量:

public static final String APPLICATION_JSON_VALUE = "application/json";
public static final MediaType APPLICATION_JSON_UTF8 = valueOf("application/json;charset=UTF-8");
public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";

為了更好的理解,我們在HelloController類上添加如下代碼:

package chapter05.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping(value = "index", method = RequestMethod.GET)
    public String hello() {
        // 這里返回的邏輯視圖名
        return "index";
    }
}

重新打包並部署到Tomcat中,此時的訪問地址從之前的http://localhost:8080/spring-action-1.0-SNAPSHOT/index變成了http://localhost:8080/spring-action-1.0-SNAPSHOT/hello/index,如下所示:

@RequestMapping注解的value屬性還支持接受一個String類型的數組,如下所示:

@RequestMapping({"/hello", "/index"})
public class HelloController {
    // 省略其它代碼
}

此時也可以通過地址http://localhost:8080/spring-action-1.0-SNAPSHOT/index/index進行訪問:

3. @ResponseBody

在上面的代碼中,我們的方法是返回邏輯視圖名index,然后由視圖解析器最終找到運行時的/WEB-INF/classes/views/index.jsp視圖,但有時我們不需要返回一個頁面,而是直接返回數據給到前端。

此時我們可以使用@ResponseBody注解,該注解可以放在返回值前或者方法上,用於將返回值放在response體內,而不是返回一個頁面。

為了更好的理解,我們新建個DemoAnnoController控制器如下所示:

package chapter05.controller;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
@RequestMapping("/anno")
public class DemoAnnoController {
    @RequestMapping(value = "/index", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
    public @ResponseBody
    String index(HttpServletRequest request) {
        return "url:" + request.getRequestURI() + " can access";
    }
}

重新打包並部署到Tomcat中,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/index,效果如下所示:

也可以將@ResponseBody注解放在方法上,如下所示:

@RequestMapping(value = "/index", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String index(HttpServletRequest request) {
    return "url:" + request.getRequestURI() + " can access";
}

4. @RequestParam

@RequestParam注解用於接收URL中的參數信息。

為了更好的理解 ,我們在DemoAnnoController控制器中添加如下方法:

@RequestMapping(value = "/requestParam", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
@ResponseBody
public String passRequestParam(@RequestParam("id") Long id, @RequestParam("name") String name, HttpServletRequest request) {
    return "url:" + request.getRequestURI() + " can access,id: " + id + ",name=" + name;
}

重新打包並部署到Tomcat中,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam?id=1&name=zwwhnly ,效果如下所示:

注意事項:上面示例中,Url中的參數名稱和方法中的變量名稱完全一致,所以可以省略掉@RequestParam注解,不過為了代碼的易讀性,建議保留@RequestParam注解。

如果不傳遞參數,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam,則會提示如下信息:

或者只傳遞其中1個參數,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam?id=1,則會提示如下信息:

由此也說明,使用了@RequestParam注解的參數,在Url中必須傳遞。

不過,@RequestParam注解提供了defaultValue屬性,可以給參數指定默認值,比如我們給參數id設置默認值1,給參數name設置默認值zwwhnly,然后訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam,效果如下所示:

或者訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam?id=2,效果如下所示:

不過,還是有一個異常場景需要注意,就是Url中傳遞的參數和方法中定義的參數類型不匹配,比如我們將id的值傳錯,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/requestParam?id=zwwhnly&name=zwwhnly,會看到如下報錯信息:

5. @PathVariable

@PathVariable注解也是用於接收URL中的參數信息,不過和@RequestParam注解稍有不同。

@PathVariable注解用於解析Url中的路徑參數,如https://www.cnblogs.com/zwwhnly/中的zwwhnly部分,而@RequestParam注解用於解析Url中的查詢參數,如https://i.cnblogs.com/posts?page=2中的page部分。

為了更好的理解 ,我們在DemoAnnoController控制器中添加如下方法:

@RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8")
public @ResponseBody
String demoPathVar(@PathVariable("str") String str, HttpServletRequest request) {
    return "url:" + request.getRequestURI() + " can access,str: " + str;
}

重新打包並部署到Tomcat中,訪問地址http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/pathvar/zwwhnly ,效果如下所示:

注意事項:如果@PathVariable注解中指定value屬性的話,它會假設占位符的名稱與方法的參數名相同。

因為這里方法的參數名正好與占位符的名稱相同,所以我們可以去掉@PathVariable注解的value屬性:

@RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8")
public @ResponseBody
String demoPathVar(@PathVariable String str, HttpServletRequest request) {
    return "url:" + request.getRequestURI() + " can access,str: " + str;
}

6. @RequestBody

@RequestBody注解允許request的參數在request體中,而不是直接鏈接在地址后面,該注解放在參數前。

為了更好的理解 ,我們在DemoAnnoController控制器中添加如下方法:

@RequestMapping(value = "/obj", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public String passObj(@RequestBody DemoObj demoObj, HttpServletRequest request) {
    return "url:" + request.getRequestURI() + " can access,demoObj id:" + demoObj.getId() +
            " demoObj name:" + demoObj.getName();
}

重新打包並部署到Tomcat中,然后使用Postman工具調用接口http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/passObj,效果如下所示:

7. @RestController

@RestController是一個組合注解,它組合了@Controller注解和@ResponseBody注解,源碼如下所示:

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Controller;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}

因此,如果某個控制器中所有的方法都只是返回數據而不是頁面的話,就可以使用@RestController注解。

為了更好的理解 ,我們舉個具體的示例。

首先,在pom.xml中添加如下依賴,用於對象和json之間的轉換:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.9</version>
</dependency>

然后新建控制器DemoRestController如下所示:

package chapter05.controller;

import chapter05.model.DemoObj;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rest")
public class DemoRestController {
    @RequestMapping(value = "/getjson", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public DemoObj getjson(@RequestBody DemoObj demoObj) {
        return new DemoObj(demoObj.getId(), demoObj.getName());
    }
}

因為使用@RestController注解,相當於同時使用了@Controller注解和@ResponseBody注解,所以上面的代碼等價於下面的代碼:

package chapter05.controller;

import chapter05.model.DemoObj;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@ResponseBody
@RequestMapping("/rest")
public class DemoRestController {
    @RequestMapping(value = "/getjson", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public DemoObj getjson(@RequestBody DemoObj demoObj) {
        return new DemoObj(demoObj.getId(), demoObj.getName());
    }
}

重新打包並部署到Tomcat中,然后使用Postman工具調用接口http://localhost:8080/spring-action-1.0-SNAPSHOT/rest/getjson,效果如下所示:

8. 源碼及參考

源碼地址:https://github.com/zwwhnly/spring-action.git,歡迎下載。

Craig Walls 《Spring實戰(第4版)》

汪雲飛《Java EE開發的顛覆者:Spring Boot實戰》

原創不易,如果覺得文章能學到東西的話,歡迎點個贊、評個論、關個注,這是我堅持寫作的最大動力。

如果有興趣,歡迎添加我的微信:zwwhnly,等你來聊技術、職場、工作等話題(PS:我是一名奮斗在上海的程序員)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM