Spring MVC: Controller
Controller—— annotation-based programming
基本知识点:
-
@Controller,@RequestMapping
,@PathVariable,@RequestParam
,@ModelAttribute
, and so on.- @Controller注解
- The
@Controller
annotation indicates that a particular class serves the role of a controller. - spring MVC会通过DispatcherServlet来查找@Controller标注的类,并且在这样的类中查找匹配的@RequestMapping注解,之后将请求交给对应的handler处理
- 但是,要想DispatcherServlet能够识别@Controller这个标注,还需要先添加一些配置才行。需要配置Spring-context对应的配置文件,使得Spring的ApplicationContext自动去扫描明确配置的包,识别@Controller之类的注解,并且将扫描识别到的Controller类实例化所得beans注册到 the dispatcher’s context.(具体配置方法参见本文“”编程思路“”部分的step3)
- The
- @RequestMapping注解
- 该注解可以放在整个类上,也可以放在类的成员方法上
- the class-level annotation maps a specific request path (or path pattern) onto a form controller
- additional method-level annotations narrowing the primary mapping for a specific HTTP method request method ("GET", "POST", etc.)or an HTTP request parameter condition.
- HTTP request有多种提交方式,如GET/POST/PUT/DELETE等等,method-level @RequestMapping可以指定某个成员方法只接收并处理某种提交方式提交的HTPP请求,有两种方法实现这种限制:
- 方法一,使用@RequestMapping及其参数来实现这种限制,如
@RequestMapping(method = RequestMethod.GET)
.....Controller类中的一个成员函数...方法二,直接使用@RequestMapping的变种注解Controller类中的某个method,语法如下
-
@GetMapping ...controller类中的某个成员函数...
除了@GetMapping之外,@RequestMapping还有多个类似的变种,具体包括
@GetMapping、
@PostMapping、
@PutMapping、
@DeleteMapping、
@PatchMapping
- 方法一,使用@RequestMapping及其参数来实现这种限制,如
- 关于@RequestMapping的用法示例,参见本文“”例程“”部分实例二,或者直接参见ReferenceDoc
- @RequestMapping中配置的URL,其实就是一个字符串。URL中有许多需要注意的知识点
- URL支持多种Pattern,如支持URI Template,还支持Ant-style Pattern URL
- URI Template Pattern URL:实际上就是包含有{varName}的URL,例如:
http://www.example.com/users/{userId}
contains the variable userId. - Ant-pattern URL:例如/myPath/*.do
- @RequestMapping中还支持URI Template和Ant-style的混用,如/owners/*/pets/{petId}
- @RequestMapping的URL中还可以使用正则表达式来限定request URL的格式:
- 语法:{varName:正则表达式},example1:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}{extension:\.[a-z]+}")上述正则表达式将URL限定为如下格式:
"/spring-web/spring-web-3.0.5.jar",并且将最后一部分拆分出三个variable,也就是说使用上述正则表达式实现了一个含有三个variable的URI Template
- 语法:{varName:正则表达式},example1:
- URL中支持占位符${...}
- URL中支持后缀模式匹配,如/text1.* 匹配/text1.pdf也匹配/text1.doc
-
@MatrixVariable
- URL中也支持Matrix Variables:可以在URL的任何一个片段中追加一个matrix,这个matrix含有多个var-value对儿
- 默认情况下spring MVC的URL是不支持matrix variables的,如果想要在URL中使用matrix variables,必须先更改相应的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <mvc:annotation-driven enable-matrix-variables="true"/> </beans>
- example1, "/cars;color=red;year=2012" matrix中含有var-value对儿,多个var-value之间用“;”分号分割
- example2, "color=red,green,blue" matrix的一个var有多个value,一个var的多个value之间用“”,“”逗号分隔
-
-
- or the variable name may be repeated
"color=red;color=green;color=blue"
.
- or the variable name may be repeated
-
-
-
// GET /pets/42;q=11;r=22 @GetMapping("/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
Since all path segments may contain matrix variables,这种情况下需要指明你想用的matrix中的var是属于URI Template的哪个segment:
// GET /owners/42;q=11/pets/21;q=22 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable(name="q", pathVar="ownerId") int q1, @MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
还可以定义matrix variable的required和defaultValue属性:
// GET /pets/42 @GetMapping("/pets/{petId}") public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 }
可以使用Map集合来接收URL中所有或者一部分matrix中的var
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable MultiValueMap<String, String> matrixVars, @MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] }
-
其实关于URL中的matrix variables,还有很多语法知识,详情参见reference doc 的spring MVC模块的
“22.3.2节中Matrix Variables部分”的内容
- 默认情况下spring MVC的URL是不支持matrix variables的,如果想要在URL中使用matrix variables,必须先更改相应的配置:
- URL中也支持Matrix Variables:可以在URL的任何一个片段中追加一个matrix,这个matrix含有多个var-value对儿
- @PathVariable
- URI Template Patterns
- 上面已经讲述了@RequestMapping的意义以及用法,知道这个注解就是用于URL请求和handler进行匹配的,这个里面涉及到URL,那么URL中有一些知识点需要注意
- 除了可以使用常量URL之外,@RequestMapping中还可以使用变量URL(变量URL又称作URI Template Patterns)
- URI Template Patterns:
- 里面含有使用{}括起来的variable,该variable的值是可变的
- 例:the URI Template
http://www.example.com/users/{userId}
contains the variable userId. - URI Template中的variable可以绑定到controller类的method的函数参数上:springMVC的controller类中可以使用@RequestMapping中URI Template的variable,只需要在Controller类相应method的函数参数上加上@PathVariable注解即可将URL中的variable绑定到controller类中某个method的函数参数上,如下面的例子
//第一种写法:只有URI Template中variable的名称和函数参数的名称一致时,才可以省略@PathVariable括号中的内容
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; }上面的程序将@GetMapping中URI Template中的变量ownerId绑定到了controller类的findOwner()函数的ownerId:String参数上了,所以controller类的findOwner()函数的函数体中就可以通过其函数参数来使用URI Template中的variable的值.除了上述程序中的写法,下面的写法也能完成上述程序的功能:
//第二种写法:因为URI Template中variable的名称和函数参数的名称不一致,所以不可以省略@PathVariable括号中的内容
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted省略 }
这两种写法都是将URI Template中的variable绑定到controller中method的函数参数上,但是写法不同,第一种写法更简洁,但是只有controller成员函数参数名称和URI Template中variable的名称一致时,才可以使用第一种写法
- URI Template中可以有多个variable,如下面的例子:
@GetMapping("/owners/{ownerId}/pets/{petId}") public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { Owner owner = ownerService.findOwner(ownerId); Pet pet = owner.getPet(petId); model.addAttribute("pet", pet); return "displayPet"; }
例二:
@Controller @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
- 使用URI Template中的variable绑定到controller函数参数上时应该注意类型转换(URI Template中variable是string类型的,但是它想要绑定的controller的method的函数参数类型可以是int/float/Object/map等类型的,这时要注意类型转换):但是web application中,由URL传递而来的都是字符串,包括form中的参数也是先转变成string再附加至URL中,再传递给相应的controller,上面已经讲过,绑定到controller的某个method的URL是可变的,即可以使用URI Template绑定到controller的某个method上,且URI Template中的variable可以在controller的method中使用,只要将URI Template中的variable绑定到函数参数上即可。但是我们的controller的成员函数的参数类型可能不是string,而是其他基本类型如int、short、long,也可能是类对象类型,也可能是Map集合类型等等,spring framework可以将URL中string类型的variable自动转变成method中函数参数类型或者抛出TypeMismatchException,至于spring framework支持string类型到哪些类型的自动转换,以及如何将URI Template中string类型的variable变成其他method 参数类型,详情参见reference doc的 the section called “Method Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.
- URI Template中可以加入正则表达式,用于限制request中相应variable的value的格式或者用于将URL中一部分内容拆分成若干个variable:
- 语法是:
{varName:regex}, 也即{变量名:正则表达式}
- example1,
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}{extension:\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String extension) { // ... }
Consider the URL
"/spring-web/spring-web-3.0.5.jar",最后一部分内容中包含三个variable,也相当于最后一部分内容通过正则表达式被拆分成三部分了
- 语法是:
- URI Template Patterns
编程思路:
-
- 概述:编写Controller类的时候,可以基于注解进行编程( 即 annotation-based programming)
- 基于
@RequestMapping
,@RequestParam
,@ModelAttribute
, and so on.等注解进行编写Controller类 - 使用基于注解的方式来编写Controller类有许多好处,具体包括:
- annotion-based programming不需要继承特殊的类,也不需要实现特殊的接口,可以直接编写Controller类
- 基于注解的方式下编写Controller类的时候,也不需要依赖 Servlet or Portlet APIs。
- 综上所述:基于注解的方式下编写Controller类的时候,可以不用了解任何的API,只需要了解
@RequestMapping
,@RequestParam
,@ModelAttribute等注解的意思,就可以编写出一个有效的Controller供DispactherServlet
d调用
- 基于
- step1,决定以 annotation-based programming来编写Controller类
- step2,了解并熟记基于注解方式编写Controller类所涉及的所有注解的意义及用法,包括@Controller、@RequestMapping等 step3,配置Spring配置文件,使得Spring-context的ApplicationContext能够识别@Controller之类的注解,并且扫描相应的程序包,将Controller实例化所得Beans注册到SpringMVC的DispatcherServlet的context中,从而处理相应请求
- 在spring-context 的配置文件中添加如下配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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.xsd"> <context:component-scan base-package="org.springframework.samples.petclinic.web"/> <!--上述配置,使得Spring的ApplicationContext能够扫描你的project中的org.springframework.samples.petclinic.web包中的所有java程序,并且识别@Controller之类的注解,并且将识别的Controller注册到DispatcherServlet的beans.xml中,使得DispatcherServlet能够识别这些Controller--> </beans>
- step4,编写Controller类并且使用@Controller注释
- step5,编写Controller类中的成员函数,并且在类以及method上添加@RequestMapping注解(或者
@GetMapping、
@PostMapping、
@PutMapping、
@DeleteMapping、
@PatchMapping注解
),同时,根据实际需要选择是否添加@PathVariable以及@MatrixVariable注解 - step6,编写Controller类的成员函数:
-
- 第一步,首先明确method所支持的函数参数类型、函数返回值类型等等
- 第一步,首先明确method所支持的函数参数类型、函数返回值类型等等
-
- 概述:编写Controller类的时候,可以基于注解进行编程( 即 annotation-based programming)
例程:
-
- GitHub上有一些web applications实例,including MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare, and others.
- 上述实例的地址: Available in the spring-projects Org on Github,
- 这些实例的Controller都是基于MVC注解方式编写的(即annotion-based programming)
-
基于注解的Controller类,实例一
-
@Controller public class HelloWorldController { @RequestMapping("/helloWorld") public String helloWorld(Model model) { model.addAttribute("message", "Hello World!"); return "helloWorld"; } }
-
基于注解的Controller类,实例二:用于举例讲述@RequestMapping的用法
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @RequestMapping(method = RequestMethod.GET) public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @RequestMapping(path = "/{day}", method = RequestMethod.GET) public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @RequestMapping(path = "/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"; } }
In the above example,
@RequestMapping
is used in a number of places. The first usage is on the type (class) level, which indicates that all handler methods in this controller are relative to the/appointments
path. Theget()
method has a further@RequestMapping
refinement【细化】: it only acceptsGET
requests, meaning that an HTTPGET
for/appointments
invokes【调用】 this method. Theadd()
has a similar refinement, and thegetNewForm()
combines the definition of HTTP method and path into one, so thatGET
requests forappointments/new
are handled by that method.The
getForDay()
method shows another usage of@RequestMapping
: URI templates. (See the section called “URI Template Patterns”). - 上述代码和下面的代码等价(下面的代码使用@RequestMapping的变种@Get
Mapping、
@PostMapping、
@PutMapping、
@DeleteMapping、
@PatchMapping等简化@RequestMapping注解
)@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @GetMapping public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @GetMapping("/{day}") public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @GetMapping("/new") public AppointmentForm getNewForm() { return new AppointmentForm(); } @PostMapping public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; } }
-
A
@RequestMapping
on the class level is not required. Without it, all paths are simply absolute, and not relative. The following example from the PetClinic sample application shows a multi-action controller using@RequestMapping
:@Controller public class ClinicController { private final Clinic clinic; @Autowired public ClinicController(Clinic clinic) { this.clinic = clinic; } @RequestMapping("/") public void welcomeHandler() { } @RequestMapping("/vets") public ModelMap vetsHandler() { return new ModelMap(this.clinic.getVets()); } }
The above example does not specify
GET
vs.PUT
,POST
, and so forth, because@RequestMapping
maps all HTTP methods by default. Use@RequestMapping(method=GET)
or@GetMapping
to narrow the mapping.
- GitHub上有一些web applications实例,including MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare, and others.