SpringMVC学习笔记

知识分子没文化
2021-11-26 / 0 评论 / 1,951 阅读 / 6,642 字数 / 正在检测是否收录...

目录

一、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 项目:

01

创建基本项目结构:

02

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 枚举值或枚举值数组,常见的请求方法有 GETPOSTPUTDELETE 等。

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 中使用传递参数:

    03

在 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 注解的属性:

  1. valuename:指定请求参数的名称。如果省略,默认使用控制器方法的形参名作为参数名称。

  2. required:指定参数是否为必填项。默认值为 true,表示请求中必须包含该参数,如果请求中缺少该参数,Spring 会抛出 MissingServletRequestParameterException。如果设置为 false,则参数可以为空。

  3. 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:

    04

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:

    05

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:

    05

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 支持的返回值类型:

  • 基本类型(Stringintboolean 等)。
  • 自定义对象(UserProduct 等)。
  • 集合或数组(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 方法实现

  1. 方法参数:

    • HttpServletRequest request:当前请求对象,通过它可以获取请求的各种信息,如请求头、请求参数等。
    • HttpServletResponse response:当前响应对象,可用于设置响应头、响应状态码等。

    • Object handler:处理当前请求的处理器对象,通常是一个控制器方法。

    • Exception ex:在处理请求过程中抛出的异常对象。

  2. 方法返回值:

    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");
    }
}

addPathPatternsexcludePathPatterns 方法均支持 Ant 风格的路径匹配规则,具体语法规则如下:

  1. ?:匹配单个字符。
    • 示例:/user? 可以匹配 /user1/userA,但不能匹配 /user12
  2. \*:匹配任意数量的字符(不包括路径分隔符 /)。
    • 示例:/user/* 可以匹配 /user/123/user/profile,但不能匹配 /user/123/address
  3. `\**:匹配任意数量的字符(包括路径分隔符/`)。
    • 示例:/user/** 可以匹配 /user/123/user/123/address
  4. {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、执行流程和顺序

拦截器执行流程:

06

如果有多个拦截器,形成拦截器链,它们的执行顺序如下:

  1. preHandle:按照注册顺序依次执行,必定运行。
  2. postHandle:按照注册顺序的逆序执行,可能不运行。
  3. afterCompletion:按照注册顺序的逆序执行,可能不运行。

例如拦截器 A、B、C 按顺序注册:

  • preHandle 执行顺序:A → B。
  • postHandle 执行顺序:B → A。
  • afterCompletion 执行顺序:B → A。
3

评论 (0)

取消