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匹配多个模版时,我们按照如下顺序选择最佳匹配:

  1. 含有更少数量URL参数和通配符的获得最佳匹配:如"/hotels/{hotel}/"含有1个URL参数和1个通配符,而"/hotels/{hotel}/"含有1个URL参数和2个通配符。因此若URL匹配该两种模式,则最终匹配到"/hotels/{hotel}/";
  2. 若两个URL模版拥有相同数量的URL参数和通配符,则URL模版最长的为最佳匹配:如"/foo/bar"和"/foo/"拥有相同数量的URL参数和通配符,但是由于"/foo/bar*"更长(更多的字符),所以其为最佳匹配;
  3. 当URL模版含有相同的URL参数和通配符数量且长度相等时,则更少通配符的为最佳匹配:如“/hotels/{hotel}”和“/hotels/*”比较,前者为最佳匹配;
特殊规则
  1. 默认映射路径/**将弱于任何一个模式,如“/api/{a}/{b}/{c}”为最佳匹配;
  2. 如前缀模版/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定义的映射方法(允许的参数类型以及返回类型等)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,641评论 6 525
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,754评论 3 408
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 173,075评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,369评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,386评论 6 402
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,800评论 1 317
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,122评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,130评论 0 281
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,689评论 1 327
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,693评论 3 348
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,804评论 1 356
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,399评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,128评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,528评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,705评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,409评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,878评论 2 368

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,756评论 18 139
  • 翻译自Spring官方文档 4.1.2版本 相关文章: Spring参考手册 1 Spring Framework...
    liycode阅读 693评论 0 2
  • Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servl...
    alexpdh阅读 2,654评论 0 3
  • 1、Spring MVC请求流程 (1)初始化:(对DispatcherServlet和ContextLoderL...
    拾壹北阅读 1,952评论 0 12
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,881评论 6 342