深入理解 Spring MVC Controller —— 请求映射

简介: 前言基于注解的 Spring MVC 的项目中,Controller 应该是我们接触最多的类了,这里提到的 Controller 并非是某一个具体的接口或类,而是一种概念,只要我们我们定义的类中包含了处理请求的方法,这个类就可以称为 Controller,而处理请求的方法被称为处理器方法。

前言


基于注解的 Spring MVC 的项目中,Controller 应该是我们接触最多的类了,这里提到的 Controller 并非是某一个具体的接口或类,而是一种概念,只要我们我们定义的类中包含了处理请求的方法,这个类就可以称为 Controller,而处理请求的方法被称为处理器方法。


由于 Controller 的内容较多,因此我打算将它拆成几块做讲解,相信看完这几篇文章之后,你会对 Controller 有更深入的认识,本篇要介绍的主要是 Controller 的定义及请求映射部分。


Spring MVC 请求处理流程中的 Controller

在 Spring MVC 系列的开篇文章中,就已经介绍了 Spring MVC 处理请求的流程,后面的文章基本上都围绕着这个流程,本篇也不例外。


26.png


我们先来看下 Controller 在 Spring MVC 请求处理流程中所扮演的角色。


Servlet 容器将请求派发至 DispatcherServlet 之后,DispatchServlet 使用 HandlerMapping 查找处理请求的 Handler,这里的 HandlerMapping 是一个接口,不同的实现支持不同的 Handler 实现,Handler 而不与具体的接口或者类耦合,最终由不同的 HandlerAdapter 适配后调用 Handler 处理请求。


处理器方法正是 Spring MVC 中 Handler 的一种,由 Controller 类中的方法定义,也就是说 Controller 是 Handler 的一个容器。


这里支持处理器方法的 HandlerMapping 实现是 RequestMappingHandlerMapping,适配处理器方法的 HandlerAdapter 实现是 RequestMappingHandlerAdapter,感兴趣的朋友可以自行查阅相关源码。


Controller 定义


实际上 Spring MVC 内部确实有一个 Controller 接口,也是 Spring MVC 中的 Handler 之一,将自定义的类实现这个接口,然后再进行相关配置就可以处理请求了。不过这个接口并非本篇所介绍的重点,如不做特殊说明,本篇所指的 Controller 都是用户自定义的不限制必须实现某个接口的容纳处理器方法的类。


@Controller 注解方式定义 Controller

定义一个 Controller,通常具有两种方式。


第一种方式是在自定义的类上加上 @Controller 注解,这样我们定义的类就成为一个 Controller 类了,这个 Controller 也将自动注册为 bean。示例代码如下。


@Controller
public class CustomController {
}


@RestController 注解方式定义 Controller

第二种方式是在自定义的类上加上 @RestController 注解,这个注解是 @Controller 与 @ResponseBody 注解的结合,用于返回 json 或 xml 格式的响应体,而不是一个 html 页面。示例代码如下。


@RestController
public class CustomController {
}


@RequestMapping 注解方式定义 Controller

除了常规的方式,可能很多人没注意到,Spring 还有另一种方式定义 Controller,只需要在我们自定义的类上加上 @RequestMapping 注解或者 @RequestMapping 元标注的注解,然后注册为 bean,我们自定义的类也能成为一个 Controller。


直接标注 @RequestMapping 示例代码如下。


@Component
@RequestMapping("/custom")
public class CustomController {
}


@RequestMapping 元标注示例代码如下。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping
public @interface CustomMapping {
    @AliasFor(annotation = RequestMapping.class)
    String name() default "";
    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};
    @AliasFor(annotation = RequestMapping.class)
    String[] path() default {};
}
@Component
@CustomMapping("/custom")
public class CustomController {
}


请求映射


处理器方法定义

Controller 本身并不是一个处理器,而是处理器方法的容器,如果想要处理请求还需要定义处理器方法,处理器方法的签名没有具体规定,Spring 会自动对方法参数和参数返回值进行处理,后面的文章也会介绍到。


假设我们想要做一个登陆功能,登陆成功后跳转到成功页面,我们可以定义处理器方法 login 如下。


@Controller
public class UserController {
    ModelAndView login(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("username", request.getParameter("username"));
        modelAndView.setViewName("success");
        return modelAndView;
    }
}


请求映射配置


那么 Spring 怎么知道访问哪个请求路径调用我们的 login 处理器方法呢?这就需要我们通过注解告诉 Spring 处理器方法映射的请求是哪个。最初的 Spring MVC 使用 @RequestMapping 进行请求映射。示例代码如下。


@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping(value = "/login")
    ModelAndView login(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("username", request.getParameter("username"));
        modelAndView.setViewName("success");
        return modelAndView;
    }
}


可以看到,我们的 Controller 类和处理器方法上都添加了 @RequestMapping 注解,并且指定了请求的路径。Spring 会自动将 Controller 类上的路径和处理器方法上的路径进行拼接,当浏览器向我们的服务发起 /user/login 的请求,就会使用我们定义的处理器方法处理请求。


Servlet 只支持对路径进行匹配,而 Spring MVC 中的处理器方法除了支持路径匹配,还支持其他的映射方式,下面分别介绍。


路径匹配


处理器方法请求映射中,路径匹配的设置方式很简单,直接指定 @RequestMapping 的 value 或 path 属性就可以了,如上面的示例。


Servlet 路径匹配方式


路径匹配是最基本的匹配方式,对于 Servlet 而言,主要有4种路径匹配方式。


精确匹配,如 /user/login,仅匹配 /user/login 路径的请求。

前缀模糊匹配,如 /user/*,匹配 /user 开头的所有请求,如 /user、/user/login。

后缀模糊匹配,如 *.html,匹配所有以 .html 结尾的请求,如 /home.html。

默认匹配 /,/ 支持所有的请求路径,也是容器处理静态资源的 Servlet 所使用的路径,可以在 web.xml 中自定义映射 / 路径的 Servlet 覆盖容器的默认行为。

Spring MVC 路径匹配方式

通常情况下,我们为 DispatchServlet 配置映射的请求路径是 / 或者 /*,这样所有的请求都会派发到 DispatchServlet。那么 DispatchServlet 又如何派发请求到 Handler 呢?


DispatchServlet 支持的 ant 风格的路径匹配方式,具体如下。

1. 精确匹配。如 /user/login。


2. 模糊匹配


如 /user/*,* 表示任意不包含 / 的路径名称,可以接受的请求包括 /user/login、/user/logout 等等。

如 /user/**/login,** 表示任意层次的路径,可以接受的请求包括 /user/login、/user/aaa/login、/user/aaa/bbb/login 等等。

如 /use?/login,? 表示单个字符,可接受的请求包括 /user/login、/usee/login 等等。

如 /user/{name}/login,name 表示某一段请求路径,Spring 可以将 name 解析为变量,可接受的请求包括 /user/aaa/login、/user/bbb/login、/user/ 等等,此时 name 的值为 aaa、bbb。

如 /user{name}/login,name 表示矩阵变量,可接受的请求包括 /user;name=zhangsan/login、/user;name=lisi/login 等等。

默认情况下,我们定义的映射路径应该是 DispatcherServlet 映射路径中模糊匹配的部分,如果我们定义 DispatcherServlet 映射路径为 /user/*,那么我们只需要为处理器方法指定 /login 映射路径就可以匹配 /user/login 请求。


DispatcherServlet 路径匹配实现


DispatchServlet 映射路径模糊匹配部分获取的思路是 请求 URI - 上下文路径 - DispatcherServlet url-pattern 精确匹配部分,如请求 URI 是 /context/user/login,上下文路径是 /context,DispatcherServlet url-pattern 是 /user/*,此时 映射路径 = /context/user/login - /context - /user = /login,也就是说我们为处理器方法配置 /login 映射路径就可以了。


各个部分获取方式如下:


请求 URI:request.getRequestURI(),获取到的是未解码的字符串,和 HTTP 协议中的一致,例如请求路径是 /context/根路径/user/login,这个方法获取到的实际是编码后的 /context/%E6%A0%B9%E8%B7%AF%E5%BE%84/user/login。

上下文路径:request.getContextPath(),获取到的同样是未解码的字符串,例如上下文配置为 /上下文,这个方法获取到的实际是编码后的 /%E4%B8%8A%E4%B8%8B%E6%96%87。

url-pattern 精确匹配部分:request.getServletPath(),这个方法获取到的是解码后的字符串,例如 url-pattern 配置为 /登录/*,获取到的值是 /登录。

路径匹配是请求映射最复杂的部分,Spring 考虑了路径匹配遇到的各种特殊情况,Spring 5.2 版本默认的行为如下。


由于项目中定义的上下文路径、请求路径可能包含非 ASCII 字符,通过代码获取到的可能和定义的不一致,因此 Spring 会对获取到的上下文路径、请求路径先进行解码处理。

由于请求路径中可能包含矩阵变量,因此 Spring 默认会将获取到的请求路径去除矩阵变量部分,例如请求路径是 /user;name=zhangsan/login,处理后的路径是 /user/login。

由于请求路径中可能包含用户误输入的连续的 /,因此 Spring 会将请求路径中连续的 / 替换为单个 /,例如请求路径是 /user//login,处理后的路径是 /user/login。

由于请求可能来自 request.getRequestDispatcher("/include").include(request, response),因此 Spring 会优先从 request attribute 中获取各种路径信息。

如果 DispatchServlet 配置是应用的默认页面路径,如 /index.html,访问 / 时容器也会将请求派发到 DispatchServlet,此时请求路径与 url-pattern 不匹配,Spring 也会做额外处理。

此外 Spring 还会根据请求 URI 与配置的映射路径解析出路径变量,如果设置查找路径时不去除矩阵变量,Spring 还能解析出矩阵变量,之后便会将路径变量和矩阵变量存至 request 的属性中,便于后续获取。


方法匹配


HTTP 请求方式常用的有 4 种,分别是 GET、POST、PUT、DELETE,可以在 @RequestMapping 的 method 属性中指定处理器方法支持的请求方式,示例代码如下。


@RequestMapping(value = "/login",method = RequestMethod.POST)


对于这几个常用的方法,如果每次都使用 method 属性指定方法,会增加很多手工编写代码的工作量,Spring 提供了与请求方法对应的几个注解来替代 @RequestMapping,包括 @GetMapping、@PostMapping、@DeleteMapping、@PutMapping,使用方式与 @RequestMapping 相同,原理是 Spring 的注解编程模型,Spring 会自动将注解和元注解组合为我们需要的注解。


查询参数匹配


Spring 还支持查询字符串中的参数的匹配方式,如果不匹配查询字符串中的参数,处理器方法也不会对请求进行处理。参数匹配设置方式如下。


@PostMapping(value = "/login", params = {"name", "age=20", "!sex", "address!=中国"})


其中,name 表示查询字符串中必须包含 name 参数,age=20 表示查询字符串中的 age 参数值必须是 20,!sex 表示查询字符串中不能包含 sex 参数,address!=中国 表示查询字符串中存在 address 参数且值不能等于 中国。


请求头匹配


和查询参数相似,Spring 支持匹配某些请求头,匹配后处理器方法才会处理请求。

请求头匹配设置方式如下。


@PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)


响应内容类型匹配


通常可以指定响应的内容类型,如果响应的内容类型请求不接受,那么处理器方法同样不会处理请求。如果只想让处理器方法产生 application/json 类型的响应内容,可以做如下的配置。


@PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)


自定义注解


假如我们想要指定接收和处理的内容类型都是 application/json,我们需要做如下的配置。


@PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)


如果处理的请求比较少还能接受,如果处理的请求比较多,我们要写很多遍 consumes/produces 参数,增加了我们的工作量。利用 Spring 的注解编程模型,我们可以定义自己的 @RequestMapping 注解,如果我们只想接收和产生 application/json 内容类型,我们可以如下自定义注解。


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public @interface JsonRequestMapping {
    @AliasFor(annotation = RequestMapping.class)
    String name() default "";
    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};
    @AliasFor(annotation = RequestMapping.class)
    String[] path() default {};
    @AliasFor(annotation = RequestMapping.class)
    String[] params() default {};
    @AliasFor(annotation = RequestMapping.class)
    String[] headers() default {};
}


然后将这个注解添加到处理器方法上即可,Spring 会自动处理我们的注解。


@Controller
@RequestMapping("/user")
public class UserController {
    @JsonRequestMapping(value = "/login")
    ModelAndView login(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("username", request.getParameter("username"));
        modelAndView.setViewName("success");
        return modelAndView;
    }
}


总结

本篇主要介绍了 Controller 的定义与处理器方法的请求映射,其中路径匹配是 Spring 最复杂的部分。了解请求映射之后,下一步我们要关注的就是处理器方法对请求参数的接收,Spring 同样提供了各种各样的接收方式,下篇将进行介绍。如果你有 Spring 的问题,欢迎留言交流。


目录
相关文章
|
1月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
36 1
Spring MVC——项目创建和建立请求连接
|
26天前
|
前端开发 Java 测试技术
深入剖析:Spring Boot Controller中请求处理方法的访问修饰符
【10月更文挑战第21天】 在Spring Boot应用中,Controller类中的请求处理方法通常用于处理HTTP请求。这些方法的访问修饰符(private或public)对方法的行为和可访问性有着重要影响。本文将深入探讨在Controller中使用private和public修饰符的区别,以及它们对Spring MVC框架的影响。
25 8
|
1月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
前端开发 Java Spring
【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart
【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart
28 2
|
1月前
|
JSON 前端开发 Java
【Spring】“请求“ 之传递 JSON 数据
【Spring】“请求“ 之传递 JSON 数据
87 2
|
1月前
|
前端开发 Java Spring
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
114 2
|
1月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
57 2
|
1月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
180 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
123 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
2月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
下一篇
无影云桌面