Spring MVC 控制器定义与@RequestMapping详解
一、控制器定义
控制器提供访问应用程序的行为,通常通过服务接口定义或注解定义两种方法实现。 控制器解析用户的请求并将其转换为一个模型。在 Spring MVC 中一个控制器可以包含多个 Action(动作、方法)。
1.1、实现接口 Controller 定义控制器
Controller 是一个接口,处在包 org.springframework.web.servlet.mvc 下,接口中只有一个未实现的方法,具体的接口如下所示:
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
//实现该接口的类获得控制器功能与类型, 解析用户的请求并将其转换为一个模型
public interface Controller { //处理请求且返回一个模型与视图对象
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
在自定义控制器前先创建一个基于 maven 的 Web 项目,添加包的依赖,pom.xml 文件如下:
http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhangguo</groupId>
<artifactId>SpringMVC02</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.source</Encoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
如果不配置 scope,会把 jar 包发布,会跟容器里的 jar 包冲突、scope 要设置为 provided,由容器提供,不会发布(或者不配这两个依赖,在项目的 Java BuildPath 的 Libraries 里添加 Server Runtime)目前 scope 可以使用 5 个值:
compile:缺省值,适用于所有阶段,会随着项目一起发布。
provided:类似 compile,期望 JDK、容器或使用者会提供这个依赖。如 servlet.jar。
runtime:只在运行时使用,如 JDBC 驱动,适用运行和测试阶段。test,只在测试时使用,用于编译和运行测试代码。不会随项目发布。
system:类似 provided,需要显式提供包含依赖的 jar,Maven 不会在 Repository 中查找它。
创建一个名为 Foo 的类,实现接口 Controller,重写 handleRequest 方法,代码如下:
package com.zhangguo.springmvc02.controllers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/* 定义控制器 */
public class FooController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//返回一个模型视图对象,指定路径,指定模型的名称为message,值为一段字符串
return new ModelAndView("foo/index", "message", "Hello,我是通过实现接口定义的一个控制器");
}
}
在 WEB-INF/views/foo 目录下创建一个名为 index.jsp 的视图,内容如下:
<%@ page language="java" contentType="text/html;
charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8">
<title>Foo</title>
</head>
<body> ${message} </body>
</html>
修改 springmvc-servlet.xml 配置文件,增加一个控制器 bean 的声明,具体内容如下:
<?xml version="1.0" encoding="UTF-8"?>
http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<context:component-scan base-package="com.zhangguo.springmvc02" />
<mvc:default-servlet-handler />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
<bean name="/foo" class="com.zhangguo.springmvc02.controllers.FooController"></bean>
</beans>
基中 name 是访问路径,class 是自定义的控制器的全名称。运行后的结果如下:
小结:实现接口 Controller 定义控制器是较老的办法,缺点是:一个控制器中只有一个 Action,如果要多个 Action 则需要定义多个 Controller;定义的方式比较麻烦;Spring 2.5 以后采用注解的方式定义解决这引起问题。
1.2、使用注解@Controller 定义控制器
org.springframework.stereotype.Controller 注解类型用于声明 Spring 类的实例是一个控制器(在讲 IoC 时还提到了另外 3 个注解);Spring 可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证 Spring 能找到你的控制器,需要在配置文件中声明组件扫描。
创建一个名了 Bar 的类,定义为一个控制器,类的具体实现如下:
package com.zhangguo.springmvc02.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/** * 定义控制器 */
//BarController类的实例是一个控制器,会自动添加到Spring上下文中
@Controller public class BarController { //映射访问路径
@RequestMapping("/bar") public String index(Model model){ //Spring MVC会自动实例化一个Model对象用于向视图中传值
model.addAttribute("message", "这是通过注解定义的一个控制器中的Action");
//返回视图位置
return "foo/index";
}
}
还要需要修改 Spring mvc 配置文件,启用自动组件扫描功能,在 beans 中增加如下配置:
<context:component-scan base-package="com.zhangguo.springmvc02" />
base-package 属性用于指定扫描的基础包,可以缩小扫描的范围。运行结果如下:
小结:从代码与运行结果可以看出 BarController 与 FooController 同时都指定了一个视图 foo/index.jsp,但是页面结果的结果是不一样的,从这里可以看出视图是被复用的,而控制器与视图之间是弱偶合关系。
二、@RequestMapping 详解
@RequestMapping 注释用于映射 url 到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解共有 8 个属性,注解源码如下:
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 java.util.concurrent.Callable;
import org.springframework.core.annotation.AliasFor;
/** * 用于映射url到控制器类或一个特定的处理程序方法. */
//该注解只能用于方法或类型上
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping public @interface RequestMapping { /** * 指定映射的名称 */ String name() default "";
/** * 指定请求的路径映射,指定的地址可以是uri模板,别名为path */ @AliasFor("path")
String[] value() default {};
/** 别名为value,使用path更加形象 * 只有用在一个Servlet环境:路径映射URI(例如“/myPath.do”)。
* Ant风格的路径模式,同时也支持(例如,“/myPath/*.do”)。在方法层面,在主要的映射在类型级别表示相对路径(例如,“edit.do”)
* 的支持。路径映射的URI可能包含占位符(例如“/$ {}连接”) */ @AliasFor("value")
String[] path() default {};
/** * 指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. 收窄请求范围 The
* HTTP request methods to map to, narrowing the primary mapping: GET, POST,
* HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. */ RequestMethod[] method() default {};
/** * 映射请求的参数,收窄请求范围 The parameters of the mapped request, narrowing the
* primary mapping. */ String[]params() default {};
/** * 映射请求头部,收窄请求范围 The headers of the mapped request, narrowing the primary
* mapping. RequestMapping(value = "/something", headers =
* "content-type=text/*") */ String[] headers() default {};
/** * 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html,收窄请求范围 The
* consumable media types of the mapped request, narrowing the primary
* mapping. */ String[] consumes() default {};
/** * 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 The producible media types
* of the mapped request, narrowing the primary mapping. produces =
* "text/plain" produces = {"text/plain", "application/*"} produces =
* "application/json;
charset=UTF-8" */ String[] produces() default {};
}
从上面的源码可以发现除了 name 基本都是数组类型,在设置时我们可以指定单个值,如@RequestMapping(value="/foo");也可以同时指定多个值如:@RequestMapping(value={"/foo","/bar"})。
2.1、value 属性指定映射路径或 URL 模板
指定请求的实际地址,指定的地址可以是 URL 模板,正则表达式或路径占位,该属性与 path 互为别名关系,@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。该属性是使用最频繁,最重要的一个属性,如果只指定该属性时可以把 value 略去。Spring Framework 4.2 引入了一流的支持声明和查找注释属性的别名。@AliasFor 注解可用于声明一双别名属性,来给注解的属性起别名, 让使用注解时, 更加的容易理解(比如给 value 属性起别名, 更容易让人理解)。先看一个官网的示例:
@Controller
@RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook;
@Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET) public Map get() { return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value = "/{day}", method = RequestMethod.GET) public Map getForDay(@PathVariable @DateTimeFormat(iso = ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value = "/new", method = RequestMethod.GET) public AppointmentForm getNewForm() { return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST) public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
2.1.1、指定具体路径字符
2.1.1.1 只注解方法
@Controller public class FooBarController {
@RequestMapping("/action1") public String action1(){ return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/action1
2.1.1.2 同时注解类与方法
@Controller
**@RequestMapping(****"/foobar") **public class FooBarController {
** @RequestMapping(****"/action1") **public String action1(){ return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/foobar/action1
需要先指定类的路径再指定方法的路径
2.1.1.3 当 value 为空值
注解在方法上时,如果 value 为空则表示该方法为类下默认的 Action。
@Controller
@RequestMapping("/foobar") public class FooBarController {
@RequestMapping("/action1") public String action1(Model model){ //在模型中添加属性message值为action1,渲染页面时使用
model.addAttribute("message", "action1");
return "foo/index";
}
**@RequestMapping** public String action2(Model model){ //在模型中添加属性message值为action2,渲染页面时使用
model.addAttribute("message", "action2");
return "foo/index";
}
}
访问 action2 的路径是:http://localhost:8087/SpringMVC02/foobar,如果加上 action2 就错误了。
注解在类上时,当 value 为空值则为默认的控制器,可以用于设置项目的起始页。
@Controller
**@RequestMapping** public class FooBarController {
@RequestMapping("/action1") public String action1(Model model){ //在模型中添加属性message值为action1,渲染页面时使用
model.addAttribute("message", "action1");
return "foo/index";
}
** @RequestMapping **public String action2(Model model){ //在模型中添加属性message值为action2,渲染页面时使用
model.addAttribute("message", "action2");
return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/,同时省去了控制器名与 Action 名称,可用于欢迎页。
访问 action1 的路径是:http://localhost:8087/SpringMVC02/action1
2.1.2、路径变量占位,URI 模板模式
在 Spring MVC 可以使用@PathVariable 注释方法参数的值绑定到一个 URI 模板变量。
@RequestMapping("/action3/{p1}/{p2}") public String action3(@PathVariable int p1,@PathVariable int p2,Model model){
model.addAttribute("message", p1+p2);
return "foo/index";
}
运行结果:
使用路径变量的好处:使路径变得更加简洁;获得参数更加方便,框架会自动进行类型转换。通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到 action,如这里访问是的路径是/action3/1/a,则路径与方法不匹配,而不会是参数转换失败。
2.1.3、正则表达式模式的 URI 模板
@RequestMapping(value="/action4/{id:\\d{6}}-{name:[a-z]{3}}") public String action4(@PathVariable int id,@PathVariable String name,Model model){
model.addAttribute("message", "id:"+id+" name:"+name);
return "foo/index";
}
正则要求 id 必须为 6 位的数字,而 name 必须为 3 位小写字母,访问结果如下:
2.1.4、矩阵变量@MatrixVariable
矩阵变量可以出现在任何路径段,每个矩阵变量用“;”分隔。例如:“/汽车;颜色 = 红;年 =2012”。多个值可以是“,”分隔“颜色 = 红、绿、蓝”或变量名称可以重复“颜色 = 红;颜色 = 绿色;颜色 = 蓝”,如下所示:
// GET /pets/42;q=11;r=22
@RequestMapping(value = "/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11
}
// 矩阵变量
@RequestMapping(value = "/action5/{name}") public String action5(Model model,
@PathVariable String name, //路径变量,用于获得路径中的变量name的值
@MatrixVariable String r,
@MatrixVariable(required = true) String g, //参数g是必须的
@MatrixVariable(defaultValue = "99", required = false) String b) { //参数b不是必须的,默认值是99
model.addAttribute("message", name + " is #" + r + g + b);
return "foo/index";
}
//Get http://localhost:8087/SpringMVC02/action5/the%20book%20color;r=33;g=66
//the book color is #336699
默认是不允许使用矩阵变量的,需要设置配置文中的 RequestMappingHandlerMapping 的属性 removeSemicolonContent 为 false;在 annotation-driven 中增加属性 enable-matrix-variables="true",修改后的 springmvc-servlet.xml 文件如下:
<mvc:annotation-driven enable-matrix-variables="true" />
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="removeSemicolonContent" value="false" />
bean>
访问结果如下:
2.1.5、Ant 风格路径模式
@RequestMapping 注解也支持 ant 风格的路径模式,如/myPath/.do,/owners//pets/{petId},示例代码如下:
//Ant风格路径模式
@RequestMapping(value = "/action6/*.do") public String action6(Model model){
model.addAttribute("message","Ant风格路径模式");
return "foo/index";
}
运行结果:
当然还有关于路径匹配的规则,特殊的优先级高过一般的,更多规则可以参考官方帮助。
ANT 通配符有三种:
2.1.6、@RequestMapping 来处理多个 URI
你可以将多个请求映射到一个方法上去,只需要添加一个带有请求路径值列表的 @RequestMapping 注解就行了。
@RestController
@RequestMapping("/home") public class IndexController {
@RequestMapping(value = { "", "/page", "page*", "view/*",
"**/msg" })
String indexMultipleMapping() { return "Hello from index multiple mapping.";
}
}
如你在这段代码中所看到的,@RequestMapping 支持统配符以及 ANT 风格的路径。前面这段代码中,如下的这些 URL 都会由 indexMultipleMapping() 来处理:
localhost:8080/home
localhost:8080/home/
localhost:8080/home/page
localhost:8080/home/pageabc
localhost:8080/home/view/
localhost:8080/home/view/view
2.2、method 属性指定谓词类型
用于约束请求的谓词类型,可以收窄请求范围。指定请求谓词的类型如 GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE,如下代码所示:
//谓词类型
@RequestMapping(value = "/action6",method={RequestMethod.POST,RequestMethod.DELETE}) public String action6(Model model) {
model.addAttribute("message", "请求谓词只能是POST与DELETE");
return "foo/index";
}
要访问 action7 请求谓词类型必须是 POST 或者为 DELETE,当我们从浏览器的 URL 栏中直接请求时为一个 GET 请求,则结果是 405,如下所示:
如果将 POST 修改为 GET 则正常了,如下所示:
//谓词类型
@RequestMapping(value = "/action6",method=RequestMethod.GET) public String action6(Model model) {
model.addAttribute("message", "请求谓词只能是GET");
return "foo/index";
}
Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的请求默认都会是 HTTP GET 类型的。
为了能降一个请求映射到一个特定的 HTTP 方法,你需要在 @RequestMapping 中使用 method 来声明 HTTP 请求所使用的方法类型,如下所示:
@RestController
@RequestMapping("/home") public class IndexController {
@RequestMapping(method = RequestMethod.GET)
String get() { return "Hello from get";
}
@RequestMapping(method = RequestMethod.DELETE)
String delete() { return "Hello from delete";
}
@RequestMapping(method = RequestMethod.POST)
String post() { return "Hello from post";
}
@RequestMapping(method = RequestMethod.PUT)
String put() { return "Hello from put";
}
@RequestMapping(method = RequestMethod.PATCH)
String patch() { return "Hello from patch";
}
}
在上述这段代码中, @RequestMapping 注解中的 method 元素声明了 HTTP 请求的 HTTP 方法的类型。
所有的处理处理方法会处理从这同一个 URL( /home)进来的请求, 但要看指定的 HTTP 方法是什么来决定用哪个方法来处理。
例如,一个 POST 类型的请求 /home 会交给 post() 方法来处理,而一个 DELETE 类型的请求 /home 则会由 delete() 方法来处理。
你会看到 Spring MVC 将使用这样相同的逻辑来映射其它的方法。
2.3、consumes 属性指定请求的 Content-Type
@RequestMapping 注解的 produces 和 consumes 这两个元素来缩小请求映射类型的范围,达到处理生产和消费对象的目的。
指定处理请求的提交内容类型(Content-Type),例如 application/json, text/html,收窄请求范围,如果用户发送的请求内容类型不匹配则方法不会响应请求,具体使用如下代码所示:
package com.zhangguo.springmvc02.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/home") public class HomeController { // 请求内容类型必须为text/html,注意浏览器默认没有指定Content-type
@RequestMapping(value = "/action8",consumes="text/html") public String action8(Model model) {
model.addAttribute("message", "请求的提交内容类型(Content-Type)是text/html");
return "foo/index";
}
}
在 action8 的注解中约束发送到服务器的 Content-Type 必须是 text/html 类型,如果类型不一致则会报错(415),测试结果如下:
从两个图的对比可以看出当内容类型为 text/plain 时报客户端错误 415,当内容类型为 text/html 时则响应正常,响应的结果如下:
_请求的提交内容类型(Content-Type)是 text/html _
注意:可以使用!号,如 consumes="!text/html"
2.4、produces 属性指定响应的 Content-Type,约束 Accept 类型
指定返回的内容类型,仅当 request 请求头中的(Accept)类型中包含该指定类型才返回,方法才处理客户端的请求否则会报 406 错误,常用设置如下:
produces = "text/plain" //客户端只接收纯文本
produces = {"text/plain", "application/*"} //客户端接收纯文本与 application/*类型的内容
produces = "application/json;
charset=UTF-8" //客户端接收 JSON 且编码为 utf-8
//客户端接收json且编码为utf-8,多数浏览器Accept设置的为*/*,接收任意类型
@RequestMapping(value = "/action9",produces="application/json;
charset=UTF-8") public String action9(Model model) {
model.addAttribute("message", "客户端可以接收的类型是application/json;
charset=UTF-8");
return "foo/index";
}
运行结果:
注意:可以使用!号,如 produces="!text/html"
2.5、params 属性指定请求中必须有特定参数与值
映射请求的参数,收窄请求范围。可以限制客户端发送到服务器的请求参数为某些特定值或不为某些值,如下代码所示:
//请求的参数必须包含id=215与name不等于abc
@RequestMapping(value = "/action10",params={"id=215","name!=abc"}) public String action10(Model model) {
model.addAttribute("message", "请求的参数必须包含id=215与name不等于abc");
return "foo/index";
}
运行结果如下:
name 的值如没有指定也是通过的;可以使用不等于;
2.6、headers 属性指定请求中必须有特定 header 值
映射请求头部,收窄请求范围。约束客户端发送的请求头部信息中必须包含某个特定的值或不包含某个值,作用范围明显大于前面讲过的几种,示例代码如下:
//请求头部信息中必须包含Host=localhost:8088
@RequestMapping(value = "/action11",headers="Host=localhost:8088") public String action11(Model model) {
model.addAttribute("message", "请求头部信息中必须包含Host=localhost:8088");
return "foo/index";
}
运行结果:
修改 Host 为 8087 时运行就正常了:
这里同样可以使用!号;可以使用通配符如:Content-Type="application/*"
2.7、name 属性指定名称
为当前映射指定一个名称,不常用,一般不会指定。
2.8、path 属性指定路径
先看源码中的 path 与 value,定义如下:
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
从 Spring 4.2 开始引入了@AliasFor 注解,可以实现属性的别名,如 value 本身并没有特定的含义,而 path 会更加具体,能见名知义,通俗说可以认为两者在使用中是一样的如:@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。示例代码如下:
//映射访问路径为/action12或/myaction,指定映射名称为actionTest
@RequestMapping(path ={"/action12","/myaction"},name="actionTest") public String action12(Model model) {
model.addAttribute("message", "映射访问路径为/action12或/myaction,指定映射名称为actionTest");
return "foo/index";
}
运行结果:
2.9、@RequestMapping 快捷方式
Spring 4.3 引入了方法级注解的变体,也被叫做 @RequestMapping 的组合注解。组合注解可以更好的表达被注解方法的语义。它们所扮演的角色就是针对 @RequestMapping 的封装,而且成了定义端点的标准方法。
例如,@GetMapping 是一个组合注解,它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。
方法级别的注解变体有如下几个:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMappin
如下面两个 action 就是基本等价的:
@RequestMapping(value = "/action3",method = RequestMethod.GET) public String action3(Model model){
model.addAttribute("msg","action3 get请求");
return "hi";
}
@GetMapping("/action4") public String action4(Model model){
model.addAttribute("msg","action4 get请求");
return "hi";
}
action4 的写法要简单一些,GetMapping 与 RequestMapping 的具体用法一样,源码如下:
View Code
2.9.1、小黄鸭调试法
场景一:我们都有过向别人(甚至可能向完全不会编程的人)提问及解释编程问题的经历,但是很多时候就在我们解释的过程中自己却想到了问题的解决方案,然后对方却一脸茫然。
场景二:你的同行跑来问你一个问题,但是当他自己把问题说完,或说到一半的时候就想出答案走了,留下一脸茫然的你。
其实上面两种场景现象就是所谓的小黄鸭调试法(Rubber Duck Debuging),又称橡皮鸭调试法,它是我们软件工程中最常使用调试方法之一。
此概念据说来自《程序员修炼之道》书中的一个故事,传说程序大师随身携带一只小黄鸭,在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码,然后很快就将问题定位修复了。
谢谢原作者,博客转自:https://www.cnblogs.com/best/p/5659596.html