目录
一、SpringMVC介绍
Spring MVC 是 Spring 框架中的一个模块,专门用于构建基于模型-视图-控制器(Model-View-Controller,MVC)设计模式的 Web 应用程序。
相关依赖坐标:
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
在 IDEA 中根据 “maven-archetype-webapp” 模板创建 Webapp 项目:
创建基本项目结构:
在 config
包里面新建初始化 Servlet 的配置类:
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
再新建 SpringMVC 配置类:
@Configuration
@ComponentScan("com.wlplove.controller")
public class SpringMvcConfig {
}
二、映射请求路径
2.1、使用@ResquestMapping进行路径映射
@RequestMapping
是 Spring MVC 中将 HTTP 请求映射到控制器方法的注解,它用于类级别或方法级别,可以根据请求的 URL、HTTP 请求方法(GET、POST 等)、请求参数、请求头等信息来匹配对应的处理逻辑。
@RequestMapping
注解应用在方法上,可以为该方法映射 URL 请求路径:
public class UserController {
@RequestMapping("/save")
public String save() {
// ...
}
}
也可以应用在类上,为该类中的所有方法设置基础的 URL 请求路径:
@RequestMapping("/user")
public class UserController {
@RequestMapping("/save")
public void save() {
// ...
}
@RequestMapping("/delete")
public void delete() {
// ...
}
}
2.2、@ResquestMapping的属性
2.2.1、value或path
这两个属性作用相同,用于指定 URL 请求路径。用一个字符串映射一个路径,也可以用一个字符串数组映射多个路径。
@RequestMapping(value = "/save")
@RequestMapping(path = "/save")
// 映射多个路径
@RequestMapping(value = {"/save", "/saveUser"})
@RequestMapping(path = {"/save", "/saveUser"})
一般也可以简写:
@RequestMapping("/save")
// 映射多个路径
@RequestMapping({"/save", "/saveUser"})
2.2.2、method
method
属性用于指定处理的 HTTP 方法。可以是一个 RequestMethod
枚举值或枚举值数组,常见的请求方法有 GET
、POST
、PUT
、DELETE
等。
public class UserController {
// save方法只处理POST和GET请求,路径为/save
@RequestMapping(value = "/save", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public String save() {
// ...
}
}
2.2.3、params
用于指定请求中必须包含的请求参数和值。可以使用一些特殊的表达式,如 paramName
表示请求中必须包含 paramName
参数;!paramName
表示请求中不能包含 paramName
参数,paramName=value
表示请求中 paramName
参数的值必须为 value
。
// 请求中必须包含name参数
@RequestMapping(value = "/save", params = "name")
// 请求中不能包含name参数
@RequestMapping(value = "/save", params = "!name")
// 请求中name参数的值必须是123
@RequestMapping(value = "/save", params = "name=123")
// 请求中name参数的值不能是123
@RequestMapping(value = "/save", params = "name!=123")
2.2.4、headers
用于指定请求中必须包含的请求头及其值,用法与 params
类似。
// 只有当请求/download路径的Accept头的值为application/pdf时,downloadPdf方法才会被调用
@RequestMapping(value = "/download", headers = "Accept=application/pdf")
@ResponseBody
public String downloadPdf() {
// ...
}
2.2.5、consumes
用于指定请求的内容类型(Content-Type),即客户端发送给服务器的数据的格式。
// uploadJson方法只处理Content-Type为application/json的请求
@RequestMapping(value = "/upload", consumes = "application/json")
@ResponseBody
public String uploadJson() {
// ...
}
2.2.6、produces
用于指定响应的内容类型(Accept),即服务器返回给客户端的数据的格式。
// getHtml方法返回的响应内容类型为text/html
@RequestMapping(value = "/get", produces = "text/html")
@ResponseBody
public String getHtml() {
return "<html><body>Hello</body></html>";
}
2.3、@ResquestMapping的衍生注解
为了简化代码,Spring 还提供了一些 @RequestMapping
的衍生注解,它们实际上是 @RequestMapping(method = xxx)
的特定形式:
@GetMapping
:等同于@RequestMapping(method = RequestMethod.GET)
,用于处理 HTTP Get 请求@PostMapping
:等同于@RequestMapping(method = RequestMethod.POST)
,用于处理 HTTP Post 请求@PutMapping
:等同于@RequestMapping(method = RequestMethod.PUT)
,用于处理 HTTP Put 请求@DeleteMapping
:等同于@RequestMapping(method = RequestMethod.DELETE)
,用于处理 HTTP Delet 请求@PatchMapping
:等同于@RequestMapping(method = RequestMethod.PATCH)
,用于处理 HTTP Patch 请求
三、请求参数传递
3.1、GET/POST请求传递基础参数
-
GET 请求的参数通过 URL 传递,附加在 URL 后面,形式为查询字符串(Query String),例如:
http://localhost:8080/save?name=Jerry&age=13
-
POST 请求数据通过请求体(Request Body)传递,不会显示在 URL 中,在 Postman 中使用传递参数:
在 Java 的控制器方法中使用相同名称的形参,SpringMVC 可以自动将请求参数绑定到控制器方法中同名的形参上:
@Controller
public class UserController {
// 如果要限制使用特定请求方法(POST/GET),将RequestMapping换成PostMapping或者GetMapping即可
@RequestMapping("/save")
@ResponseBody
public String save(String name, int age) {
// ...
}
}
除此之外,SpringMVC 也支持将请求参数映射到不同名称的形参上,需要用 @RequestParam
注解指定请求参数绑定到控制器方法的哪个形参上:
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam("name")String userName, @RequestParam("age")int userAge) {
// ...
}
}
@RquestParam
注解的属性:
value
或name
:指定请求参数的名称。如果省略,默认使用控制器方法的形参名作为参数名称。
required
:指定参数是否为必填项。默认值为true
,表示请求中必须包含该参数,如果请求中缺少该参数,Spring 会抛出MissingServletRequestParameterException
。如果设置为false
,则参数可以为空。
defaultValue
:指定参数的默认值。如果请求中未提供该参数,则使用该默认值。
3.2、传递复杂参数
3.2.1、日期类型
在请求中传递特定格式的日期:
http://localhost:8080/save?date=2023-10-12
在控制层方法的形参前面加上 @DateTimeFormat
注解并指定日期格式,SpringMVC 就可以将同名请求参数转换为日期格式:
@RequestMapping("/dateParse")
@ResponseBody
public String dateParse(@DateTimeFormat(pattern = "yyyy-MM-dd") Date date) {
// ...
}
3.2.2、引用类型
例如存在一个 User
对象,其中包含一个引用类型的成员 address
:
public class Address {
// 省
private String province;
// 市
private String city;
}
public class User {
// 姓名
private String name;
// 年龄
private String age;
// 地址——引用对象
private Address address;
}
控制器方法中使用 User
对象接收参数:
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(User user) {
// ...
}
}
那么在请求中传递参数时保持参数名与控制器方法形参名相同即可(不相同时使用 @RequestParam
绑定):
-
GET:
http://localhost:8080/save?name=Jerry&age=13&address.province=河南&address.city=开封
-
POST:
3.2.3、数组
控制器方法中使用数组接收参数:
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(String[] nameArray) {
// ...
}
}
那么在请求中传递参数时都使用相同名称的参数即可:
-
GET:
http://localhost:8080/save?nameArray=Jerry&nameArray=Tom&nameArray=Mike
-
POST:
3.2.4、集合(List/Set/Map)
控制器方法中使用集合接收参数(形参前需要加 @RequestParam
注解):
@Controller
public class UserController {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam List<String> nameList) {
// ...
}
}
在请求中传递参数的方式与数组相同:
-
GET:
http://localhost:8080/save?nameArray=Jerry&nameArray=Tom&nameArray=Mike
-
POST:
3.3、传递Json数据
添加处理 Java 对象与 JSON 数据相互转换的依赖坐标:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
在 SpringMVC 配置上添加 @EnableWebMvc
注解开启 Spring MVC 的默认配置(其中包括 Java 对象与 JSON 数据的相互转换):
@Configuration
@ComponentScan("com.wlplove.controller")
@EnableWebMvc
public class SpringMvcConfig {
// ...
}
发送的 JSON 数据:
{
"name": "Tom",
"age": 12
}
控制器方法中用集合或者对象接收时形参前加上 @RequestBody
注解将请求体中的数据绑定到控制器方法的参数上:
@RequestMapping("/json")
@ResponseBody
public String json2Dto(@RequestBody User user) {
// ...
}
四、响应
4.1、返回Json数据
需要添加 Jackson 的依赖:
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
在控制器方法上使用 @ResponseBody
注解,告知 SpringMVC 将控制层方法的返回值直接写进 HTTP 响应体,而不是解析为视图。最后 SpringMVC 通过 HttpMessageConverter
接口将返回值转换为适当的格式(如 JSON、XML等)。
@RequestMapping("/returnJson")
@ResponseBody
public User returnJson() {
User user = new User();
user.setAge(20);
user.setName("QWERT");
return user;
}
@ResponseBody
支持的返回值类型:
- 基本类型(
String
、int
、boolean
等)。 - 自定义对象(
User
、Product
等)。 - 集合或数组(
List<User>
、String[]
等)。
4.2、封装返回结果
新建一个返回结果对象:
public class Result {
/** 状态码 */
private Integer code;
/** 返回内容 */
private String msg;
/** 数据对象 */
private Object data;
public Result(Integer code, Object data) {
this.code = code;
this.data = data;
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// get和set方法
}
自定义返回编码:
public class Code {
public static final Integer OK = 000000;
public static final Integer ERR =999999;
// 其他编码
}
对 controller 方法的返回值都统一成这个返回对象:
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/{id}")
@ResponseBody
public Result findById(@PathVariable Integer id) {
User user = userMapper.selectById(id);
Integer code = user != null ? Code.OK : Code.ERR;
String msg = user != null ? "数据查询成功" : "数据查询失败";
return new Result(code, msg, user);
}
// ...
}
五、REST风格
5.1、简介
REST
(Representational State Transfer)风格是一种软件架构风格,用于设计网络应用程序,其核心概念旨在通过 HTTP 协议实现客户端与服务器之间简洁、可扩展且高效的交互。
传统风格资源描述形式:
http://lcoalhost/user/getById?id=1
http://localhost/user/saveUser
REST风格描述形式:
http://localhost/user/1
http://localhost/user
REST 风格是一种设计接口的约定方式,并非规范。
5.2、规则
REST 使用统一的接口与服务器进行交互,基于 HTTP 协议的标准方法:
- GET 用于获取资源
- POST 用于创建资源
- PUT 用于更新资源
- DELETE 用于删除资源
5.3、相关注解
5.3.1、@RestController
@RestController
是 Spring 4.0 引入的一个组合注解,它是 @Controller
和 @ResponseBody
的结合体。其主要作用是将一个控制器类标记为使用 REST 风格,使用 @RestController
注解的类中的所有处理方法默认都会将返回值直接写入 HTTP 响应体,以 JSON、XML 等数据格式返回,而不是返回视图进行渲染。
5.3.2、@PathVariable
@PathVariable
是 Spring MVC 中用于将 URL 中的模板变量绑定到控制器方法形参上的注解。它通常用于 RESTful API 中,从 URL 路径中提取参数值。
如根据 ID 查询用户的控制器方法:
@GetMapping("/users/{id}")
public String findById(@PathVariable Long id) {
// ...
}
那么对于 GET 请求链接 http://localhost/users/123456
,该控制器可以通过 @PathVariable
将 “123456” 绑定到形参 id
上。
也支持在路径中定义多个路径变量:
http://localhost/users/123456/orders/987654321
在控制器方法中接收多个参数:
@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(@PathVariable Long userId, @PathVariable Long orderId) {
// ...
}
Spring MVC 5.3 及以上版本支持可选路径变量。如果路径变量是可选的,可以设置属性 required = false
:
@GetMapping("/users/{id}")
public User getUser(@PathVariable(required = false) Long id) {
if (id == null) {
// 返回所有用户
return userService.getAllUsers();
}
// 返回指定id的用户
return userService.getUserById(id);
}
那么:
http://localhost/users id为null,返回所有用户
http://lcoalhost/users/123 返回id为123的用户
5.3.3、@RequestParam
详见 3.1。
5.3.4、@RequestBody
详见 3.3。
5.4、Rest风格开发示例
5.3.1、查询
// 查询全部
@GetMapping("/users")
public xxxResponse findAll() {
// ...
}
// 根据主键查询
@GetMapping("/users/{id}")
public xxxResponse findById(@PathVariable Long id) {
// ...
}
5.3.2、新增
@PostMapping("/users")
public xxxResponse add(@RequestBody User user) {
// ...
}
5.3.3、修改(更新)
@PutMapping("/users")
public xxxResponse update(@RequestBody User user) {
//...
}
5.3.4、删除
@DeleteMapping("/delete/{id}")
public xxxResponse delete(@PathVariable Long id) {
// ...
}
六、异常处理
6.1、自定义异常
新建一个自定义异常类 BusinessException
:
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
// get和set方法
}
自定义异常编码:
public class ErrorCode {
public static final Integer TIMEOUT_ERROR = 10001;
// 其他异常编码
}
在业务层代码中抛出异常:
throw new BusinessException(ErrorCode.TIMEOUT_ERROR, "请求超时,请稍后再试");
6.2、处理异常的方式
6.2.1、使用@ControllerAdvice和@ExceptionHandler
@ControllerAdvice
是 Spring 框架提供的一个用于定义全局异常处理、数据绑定和数据预处理等逻辑的注解。在配合标注了 @ExceptionHandler
注解的方法使用时,可以在拦截到对应的异常之后终止原 controller 方法的执行,并转入该方法执行设定好的异常处理逻辑。
在 controller 包中新建一个全局异常处理类 GlobalExceptionHandler
,在类中的方法上面使用 @ExceptionHandler
注解指定这个方法要处理哪种异常:
@ControllerAdvice
public class GlobalExceptionHandler {
// Exception是所有异常的父类,在此表示拦截到所有的异常都会进行处理
@ExceptionHandler(Exception.class)
// 方法参数中加入这种异常类型的形参,就可以获取到这个异常的信息
public ResponseEntity<String> doException(Exception ex) {
return new ResponseEntity<>("异常信息: " + ex.getMessage());
}
// 只处理RuntimeException异常
@ExceptionHandler(RuntimeException.class)
public Response<T> doRuntimeException(RuntimeException ex){
// RuntimeException异常处理逻辑
}
// 只处理IOException异常
@ExceptionHandler(IOException.class)
public Response<T> doIOException(IOException ex){
// IOException异常处理逻辑
}
// ...
}
如果在控制器中使用 Rest 风格的开发,可以使用 @RestControllerAdvice
注解,@RestControllerAdvice
是 @ControllerAdvice
和 @ResponseBody
的组合注解,类中的方法返回值会自动进行 JSON 或 XML 等格式的序列化,直接作为响应体返回给客户端。
6.2.2、实现HandlerExceptionResolver接口
HandlerExceptionResolver
是 Spring Web 框架中的一个接口,它提供了一种统一处理控制器(@Controller
)中抛出的异常的机制。通过实现这个接口,开发者可以自定义异常处理逻辑,以满足不同的业务需求,比如返回特定格式的错误响应、记录异常日志等。
处理异常主要通过该接口中的 resolveException
方法实现
-
方法参数:
HttpServletRequest request
:当前请求对象,通过它可以获取请求的各种信息,如请求头、请求参数等。
-
HttpServletResponse response
:当前响应对象,可用于设置响应头、响应状态码等。 -
Object handler
:处理当前请求的处理器对象,通常是一个控制器方法。 -
Exception ex
:在处理请求过程中抛出的异常对象。
-
方法返回值:
ModelAndView
是 Spring MVC 中用于封装视图和模型数据的类。如果返回null
,表示该异常解析器没有处理该异常,Spring 将继续尝试其他的异常解析器。如果返回一个非空的ModelAndView
对象,Spring 会根据其中的视图信息渲染相应的视图,并将模型数据传递给视图。
新建一个异常处理类 CustomExceptionResolver
,用 @Component
注解声明这是一个 Spring Bean,实现 HandlerExceptionResolver
接口中的 resolveException
方法:
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView modelAndView = new ModelAndView();
if (ex instanceof IllegalArgumentException) {
modelAndView.setViewName("error/400"); // 跳转到 400 错误页面
modelAndView.addObject("errorMessage", "非法参数: " + ex.getMessage());
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
} else {
modelAndView.setViewName("error/500"); // 跳转到 500 错误页面
modelAndView.addObject("errorMessage", "服务器内部错误: " + ex.getMessage());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
return modelAndView;
}
}
七、拦截器
在 Spring 中,拦截器是利用 AOP(面向切面编程)的思想实现的一种机制,用在请求处理的不同阶段(如请求到达 controller 方法之前、controller 方法执行之后、视图渲染之前等)执行自定义逻辑,在以下的场景中广泛使用:
- 权限验证:在请求到达控制器之前,检查用户是否具有访问该资源的权限,如验证用户登录状态。
- 日志记录:记录请求的相关信息,如请求的 URL、参数、处理时间等,方便后续的调试和监控。
- 性能监控:统计请求的处理时间,找出性能瓶颈。
- 请求参数处理:对请求参数进行统一的预处理,如编码转换、参数验证等。
7.1、创建拦截器
在 controller 包中新建一个包 interceptor,创建拦截器类 ProjectInterceptor
,实现 HandlerInterceptor
接口,并用 @Component
声明为一个 bean:
@Component
public class ProjectInterceptor implements HandlerInterceptor {
// 在控制器处理请求之前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 通过handler对象经过反射可以获取到原controller方法
System.out.println("preHandle: 请求到达控制器之前");
// 可以在此处进行权限验证、日志记录等操作
// 如果返回 false,请求将被中断,不再执行postHandle与afterCompletion
return true;
}
// 在控制器处理请求之后、视图渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle: 控制器处理请求之后");
// 可以在此处修改模型数据或视图
}
// 在视图渲染完成之后调用(无论请求是否成功)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion: 视图渲染完成之后");
// 可以在此处进行资源清理、日志记录等操作
}
}
7.2、注册和配置拦截器
在 config 包下新建一个配置类 SpringMvcSupport
,继承 WebMvcConfigurationSupport
类,并重写 addInterceptors
方法,在这个方法中注册和配置拦截器:
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
// 注入之前定义好的拦截器类
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor)
// 指定要拦截的路径,可以传入多个路径
.addPathPatterns("/**")
// 指定要排除的路径,可以传入多个路径
.excludePathPatterns("/login", "/register");
}
}
addPathPatterns
和excludePathPatterns
方法均支持 Ant 风格的路径匹配规则,具体语法规则如下:
?
:匹配单个字符。
- 示例:
/user?
可以匹配/user1
、/userA
,但不能匹配/user12
。\*
:匹配任意数量的字符(不包括路径分隔符/
)。
- 示例:
/user/*
可以匹配/user/123
、/user/profile
,但不能匹配/user/123/address
。- `\
**:匹配任意数量的字符(包括路径分隔符
/`)。
- 示例:
/user/**
可以匹配/user/123
、/user/123/address
。{variable}
:匹配路径变量。
- 示例:
/user/{id}
可以匹配/user/123
,并将123
绑定到路径变量id
除了新建一个 SpringMvcSupport
配置类来配置拦截器的方法外,还可以让原来的 SpringMvc 配置类来实现 WebMvcConfigurer
接口中的 addInterceptors
方法,也能达到相同的效果:
@Configuration
@ComponentScan("com.xxx.controller")
@EnableWebMvc
public class SpringMvcSupport implements WebMvcConfigurer {
// 注入之前定义好的拦截器类
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor)
// 指定要拦截的路径,可以传入多个路径
.addPathPatterns("/**")
// 指定要排除的路径,可以传入多个路径
.excludePathPatterns("/login", "/register");
}
}
7.3、执行流程和顺序
拦截器执行流程:
如果有多个拦截器,形成拦截器链,它们的执行顺序如下:
preHandle
:按照注册顺序依次执行,必定运行。postHandle
:按照注册顺序的逆序执行,可能不运行。afterCompletion
:按照注册顺序的逆序执行,可能不运行。
例如拦截器 A、B、C 按顺序注册:
preHandle
执行顺序:A → B。postHandle
执行顺序:B → A。afterCompletion
执行顺序:B → A。
评论 (0)