Spring学习手册(16)—— 定义Controller
HelloSpringMVC文章带我们一起学习如何使用了SpringMVC框架:创建Web项目工程、增加项目依赖Jar包、定义web.xml和HelloWeb-servler.xml配置文件、定义控制器(Controller)和定义视图文件,最终将该项目部署到Tomcat服务器上,我们完成了第一个SpringMVC项目。本文我们深入学习如何定义一个Controller。
一 、控制器(Controller)
通过服务器接口来定义应用程序行为,而控制器(后称Controller)为用户提供访问这些行为的功能。Controller将拦截用户输入并通过逻辑处理后转换为模型(Model),这些模型将会被视图(View)渲染成用户可见的展现方式。Spring可以让我们轻易实现各种类型的Controller。
Spring2.5之后提供了一套基于注解的方式来定义和实现Controller功能,这里我们也主要学习注解的方式定义和实现Controller。
为了让SpringMVC自动检测并实例化我们用注解定义的Controller,我们需要在增加以下配置:
<?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="..."/>
<!-- ... -->
</beans>
其中base-package
为你使用注解定义的的Controller的包路径。
二、定义Controller
2.1 最简单的Controller
@Controller
注解可以指定一个类作为服务器的控制器(Controller),当然我们还需要@RequestMapping
注解来映射具体的控制器处理方法。有了这两个注解我们就可以完成最简单的一个Controller的定义了。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
如上,我们用@Controller
将HelloWorldController标示为一个Controller,然后使用@RequestMapping
将helloWorld方法与访问请求路径/helloWorld
关联,这样当用户使用http://127.0.0.1:8080/helloWorld
访问网站时(我们假设部署在本机且应用部署在根目录),将会被helloWorld方法处理,在Model内填充HelloWorld数据,并且返回一个String,其值为"helloWorld",它将被影射到名为helloWorld的视图View(我们后面会学习到)。
2.2 请求方法映射(@RequestMapping
)
我们使用@RequestMapping
注解将URLs的请求路径映射到一个类或者一个特定的处理方法上。也就是说@RequestMapping
注解可以使用在类上也可以使用在具体的方法上。作用在类上的@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";
}
}
以上代码出自PetCare项目,我们来一起学习分析下这段代码。
- 类级别的注解
@RequestMapping("/appointments")
注解在AppointmentsController类上,该控制器将匹配所有以/appointments
开始的请求路径; - 方法级别的注解
@RequestMapping(method = RequestMethod.GET)
作用在get()方法上,则请求路径为/appointments
的GET请求会被影射到该方法上; - 方法级别的注解
@RequestMapping(path = "/new", method = RequestMethod.GET)
作用在getNewForm()上,则请求路径为/appointments/new
的GET请求会被影射到该方法上; - 方法级别的注解
@RequestMapping(method = RequestMethod.POST)
作用在add()方法上,则请求路径为/appointments
的POST请求会被该方法处理;
......
也许你已经注意到@RequestMapping(path = "/{day}", method = RequestMethod.GET)
的注解,这个会在后面请求参数进行讲解,这里可暂时不用太在意。
当然,类级别的@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());
}
}
如上代码所示,请求路径/
会由welcomeHandler()方法处理,而请求路径/vets
会由vetsHandler()方法处理。也许你注意到上面的所有的@RrequestMapping
没有设置GET或POST,这种情况下,会匹配所有类型请求。
2.3 组合的@RequestMapping变量
Spring 4.3引入了方法级别的组合@RequestMapping变量,它能极大的简化影射配置并且在语义更易理解:
-
@GetMapping
:相当于@RequestMapping( method = RequestMethod.GET)
-
@PostMapping
:相当于@RequestMapping( method = RequestMethod.POST)
-
@PutMapping
: Restful风格下的Put请求,相当于@RequestMapping(method= RequestMethod.PUT)
-
@DeleteMapping
:Restful风格下的Delete请求,相当于@RequestMapping(method= RequestMethod.DELETE)
-
@PatchMapping
:相当于@RequestMapping(method= RequestMethod.PATCH)
在大多数的Web应用中,GetMapping和PostMapping使用更为普遍。
三、高级映射路径参数使用
本节我们讲学习如何从 @RequestMapping
里设置的URL中获取我们想得到的信息。主要采用@PathVariable
和@MatrixVariable
两种方式实现。
3.1 使用@PathVariable
获取URL模版参数
1. 最简单的方式
我们可以使用URL模版
(URL templates)方便的获取URL中的某一部分,如我们使用@RequestMapping(/owners/{userId})
注解某一Controller的方法,当我们使用如下URL访问服务时http://www.example.com/owners/fred
,(假设我们服务部署域名为www.example.com),那么userId的参数的值就为fred。让我们看个例子,来进一步理解。
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
如上代码所示,当访问.../owners/fred
时,findOwner方法的参数ownerId的值将为fred。我们使用@PathVariable
注解标示方法的参数,并且明确指明该参数对应URL中的ownerId
所代表的值。当然,当应用编译包含debugging信息且URL中的模版名和方法参数名相同时,可省略@PathVariable
的明确指明,如下所示:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
当然像@ RequestMapping
可以作用于类上,我们同样可以从类上获得URL模版值信息,如下代码所示:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
2. URL中存在多个模版时
当URL中存在多个模版时,我们可以在方法中使用多个@PathVariable
来标示形参,如下代码所示:
@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";
}
当我们具体的访问路径被影射到findPet方法上时,URL中的{ownerId}和{petId}位置的值会自动映射到findPet方法上参数ownerId和petId的值。
当然,当URL中模版过多时,我们可以使用任意数量的@PathVariable
来解决,但是这样导致方法参数列表过长,我们可以使用如下方式来解决该问题:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable Map<String,String> urlTemplates, Model model) {
String ownerId = urlTemplates.get("ownerId");
String petId = urlTemplates.get("petId");
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
我们使用@PathVariable
注解来注解Map<String,String>
参数,这样Spring会自动把所有的URL模版填充到该Map中,key为模版名,value为值。
3.URL模版上使用正则表达式
有时候我们需要更明确的URL模版值信息,如我们提交的请求路径为/spring-web/spring-web-3.0.5.jar
,我们希望获得版本号和后缀信息,我们可以使用如下方式来获取:
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
如上代码,参数version将被'3.0.5'填充,参数extension将被'jar'填充。
4. 最佳路径匹配
当一个URL匹配多个模版时,我们按照如下顺序选择最佳匹配:
- 含有更少数量URL参数和通配符的获得最佳匹配:如"/hotels/{hotel}/"含有1个URL参数和1个通配符,而"/hotels/{hotel}/"含有1个URL参数和2个通配符。因此若URL匹配该两种模式,则最终匹配到"/hotels/{hotel}/";
- 若两个URL模版拥有相同数量的URL参数和通配符,则URL模版最长的为最佳匹配:如"/foo/bar"和"/foo/"拥有相同数量的URL参数和通配符,但是由于"/foo/bar*"更长(更多的字符),所以其为最佳匹配;
- 当URL模版含有相同的URL参数和通配符数量且长度相等时,则更少通配符的为最佳匹配:如“/hotels/{hotel}”和“/hotels/*”比较,前者为最佳匹配;
特殊规则
- 默认映射路径
/**
将弱于任何一个模式,如“/api/{a}/{b}/{c}”为最佳匹配; - 如前缀模版
/public/**
弱于任何一个不含邮两个通配符的模版,如“/public/path3/{a}/{b}/{c}”相对于“/public/**”,前者为最佳匹配。
3.2 Matrix Variables
RFC 3986中定义URL中的某一段路径中可包含一系列键值对。这些键值对可在任意路径后面跟随,多个键值对件可食用";"(分号)隔开,如:"/cars;color=red;year=2012"。当一个键(名)含有多个值时,可私用","(逗号)隔开,如"color=red,green,blue"或者使用"color=red;color=green;color=blue"方式。
接下来让我们通过几个例子来掌握如何使用Matrix Variables:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
如上代码,我们使用@MatrixVariable
来获取到请求路径中q的值。
// 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
}
如上代码就略显复杂了些,该URL路径中不同的路径部门中包含相同名字的q,这个时候我们@MatrixVariable
中使用name来表明获取名为q的值,且路径由pathVar来定义。
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
如上代码,我们可以为q设置默认值,当请求路径中不包含名为q的信息时,则q将被赋值默认值。
// 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]
}
我们同样可以将@MatrixVariable
来注解Map类型的参数,这样Spring会自动将所有的Matrix 值填充到该Map。当然如果你在@MatrixVariable
中设置pathVar,则只会填充改路径块下的Matrix值。
<?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>
如上,我们使用<mvc:annotation-driven enable-matrix-variables="true"/>
使得Spring支持matrix variables
。
四、总结
本文我们学习了Controller的定义以及使用@RequestMapping
定义映射方法。除了简单的将请求路径映射到制定的Controller外,我们可以通过使用路径模版和Matrix Variables方式来使处理方法获得请求路径传入的某些值。接下来我们将详细学习@RequestMapping定义的映射方法(允许的参数类型以及返回类型等)。