在使用 Spring MVC 开发 web 应用程序时, 控制器 Controller 的开发非常重要, 虽然说视图 (JSP 或者是 Thymeleaf) 也很重要, 因为它才是直接呈现给用户的, 不过由于现在前端越来越重要, 很多公司都开始采用前后端分离的开发模式, 所以我们暂时可以将精力放在开发控制器上.
使用 Spring MVC 开发控制器主要使用以下 7 个注解:
- @Controller
- @RequestMapping
- @ResponseBody
- @RequestParam
- @PathVariable
- @RequestBody
- @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 种使用形式:
应用在方法级别, 如上面的代码中展示的那样.
应用在类级别, 当控制器在类级别上添加 @RequestMapping 注解时, 这个注解会应用到控制器的所有处理器方法上, 处理器方法上的 @RequestMapping 注解会对类级别上的 @RequestMapping 注解的声明进行补充.
@RequestMapping 注解常用的 3 个参数如下所示:
value: 指定映射的 URL 地址, 如 index
method: 指定映射的请求类型, 如 GET 请求, POST 请求等
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 实战》
9. 最后
来源: https://www.cnblogs.com/zwwhnly/p/11572302.html