springmvc 框架学习

什么是 SpringMVC 框架

Spring MVC 是 Spring 框架的核心模块之一,基于 Java Servlet API 构建的 Web 层解决方案 。它实现了 MVC 设计模式(Model-View-Controller),专为开发灵活、松耦合的 Web 应用程序而设计。

在控制层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。之所以能做到这一点,是因为 SpringMVC 具备如下显著优势:

  • Spring 家族原生产品,与 IOC 容器等基础设施无缝对接。
  • 表述层各细分领域需要解决的问题全方位覆盖 ,提供全面解决方案
  • 代码清新简洁,大幅度提升开发效率。
  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可。
  • 性能卓著,尤其适合现代大型、超大型互联网项目要求。

核心组件架构

组件 功能说明
DispatcherServlet 前端控制器,统一处理 HTTP 请求与响应流程(核心调度器
HandlerMapping 映射请求 URL 到具体的控制器(Controller)
Controller 业务逻辑处理器,处理请求并返回模型数据
ViewResolver 解析逻辑视图名到具体视图实现(如 JSP、Thymeleaf 等)
Model 数据容器,在控制器和视图间传递业务数据

SpringMVC 处理请求流程

SpringMVC 请求处理流程可分为以下7个核心步骤,这是企业级应用开发必须掌握的关键机制:

  1. HTTP请求到达

    用户发起请求后,首先被 DispatcherServlet(前端控制器)拦截。这是整个流程的入口,类似"总调度中心",负责协调各组件工作。

  2. 处理器映射定位

    HandlerMapping 通过请求 URL 查找对应的 Controller。例如:

    java 复制代码
    @Controller
    @RequestMapping("/user")
    public class UserController {
        @GetMapping("/profile") // 映射到/user/profile路径
        public String getProfile() { ... }
    }
  3. 适配器执行处理

    HandlerAdapter 根据找到的 Controller 信息,通过反射机制调用具体的处理方法。这里会处理:

    • 参数绑定(@RequestParam@PathVariable
    • 数据验证(@Valid
    • 内容协商(JSON/XML 格式转换)
  4. 业务逻辑处理

    Controller 方法执行实际业务操作,典型处理包括:

    java 复制代码
    @PostMapping("/create")
    public ModelAndView createUser(@Valid User user) {
        userService.save(user);
        return new ModelAndView("success", "user", user);
    }
  5. 返回模型视图

    处理方法返回 ModelAndView 对象,其中包含:

    1. 视图名称(逻辑视图)
    2. 模型数据(将传递给视图层的数据)
  6. 视图解析渲染

    ViewResolver 将逻辑视图名解析为具体视图实现(JSP/Thymeleaf 等),例如:

    properties 复制代码
    spring.mvc.view.prefix=/WEB-INF/views/
    spring.mvc.view.suffix=.jsp
  7. 响应输出

    视图引擎结合模型数据进行渲染,最终生成 HTML/JSON 等响应内容,通过 DispatcherServlet 返回给客户端。

关键增强机制

  • 拦截器链:HandlerInterceptor 实现 AOP 式处理(权限校验、日志记录)。
  • 异常处理:@ControllerAdvice 统一处理全局异常。
  • 内容协商:根据 Accept 头自动选择 JSON/XML 等响应格式。

SpringMVC 涉及组件理解:

  1. DispatcherServlet:SpringMVC 提供,我们需要使用 web.xml 配置使其生效,它是整个流程处理的核心,所有请求都经过它的处理和分发![ CEO ]
  2. HandlerMapping:SpringMVC 提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效,它内部缓存 handler(controller 方法)和 handler 访问路径数据,被 DispatcherServlet 调用,用于查找路径对应的 handler![秘书]
  3. HandlerAdapter:SpringMVC 提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效,它可以处理请求参数和处理响应数据数据,每次 DispatcherServlet 都是通过 handlerAdapter 间接调用 handler,他是 handler 和 DispatcherServlet 之间的适配器![经理]
  4. Handler:handler 又称处理器,他是 Controller 类内部的方法简称,是由我们自己定义,用来接收参数,向后调用业务,最终返回响应结果![打工人]
  5. ViewResovler:SpringMVC 提供,我们需要进行 IoC 配置使其加入 IoC 容器方可生效!视图解析器主要作用简化模版视图页面查找的,但是需要注意,前后端分离项目,后端只返回 JSON 数据,不返回页面,那就不需要视图解析器!所以,视图解析器,相对其他的组件不是必须的![财务]

快速开始

  1. 创建一个 maven web 项目

    1. 创建一个 maven 空项目,下载插件 JBLJavaToWeb 把 java 模块工程转化为 web 模块工程。
    2. 将 maven 空项目转为 web 模块。(需要上面的插件)
  2. 导入依赖

xml 复制代码
<dependencies>
    <!-- springioc相关依赖  -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.6</version>
    </dependency>
    <!-- web相关依赖  -->
    <!-- 在 pom.xml 中引入 Jakarta EE Web API 的依赖 -->
    <!--
        在 Spring Web MVC 6 中,Servlet API 迁移到了 Jakarta EE API,因此在配置 DispatcherServlet 时需要使用
         Jakarta EE 提供的相应类库和命名空间。错误信息 "'org.springframework.web.servlet.DispatcherServlet'
         is not assignable to 'javax.servlet.Servlet,jakarta.servlet.Servlet'" 表明你使用了旧版本的
         Servlet API,没有更新到 Jakarta EE 规范。
    -->
    <dependency>
        <groupId>jakarta.platform</groupId>
        <artifactId>jakarta.jakartaee-web-api</artifactId>
        <version>9.1.0</version>
        <scope>provided</scope>
    </dependency>
    <!-- springwebmvc相关依赖  -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>6.0.6</version>
    </dependency>
</dependencies>
  1. 创建 controller 层
java 复制代码
@Controller
public class HelloController {

	/**
	 * handler就是controller内部的具体方法
	 * @RequestMapping("/hello") 就是用来向handlerMapping中注册的方法注解!
	 * @ResponseBody 代表向浏览器直接返回数据!
	 */
	@RequestMapping("/hello")
	@ResponseBody
	public String hello(){
		System.out.println("HelloController.hello");
		return "hello springmvc!!";
	}
}
  • handler 就是 controller 内部的具体方法。

  • @RequestMapping("/hello") 是浏览器访问路径,用来向 handlerMapping 中注册的方法注解。

  • @ResponseBody 代表向浏览器直接返回字符串数据,不去走视图解析。

  1. 创建主配置类,作用:

    1. 将 controller 层注入到 IoC 容器中。
    2. 导入 handlerMappinghandlerAdapter 为容器组件。
    java 复制代码
    @Configuration
    @ComponentScan("com.yigongsui")
    public class MyConfig {
    
    	@Bean
    	public HandlerMapping handlerMapping() {
    		return new RequestMappingHandlerMapping();
    	}
    	
    	@Bean
    	public HandlerAdapter handlerAdapter() {
    		return new RequestMappingHandlerAdapter();
    	}
    
    }
  2. 配置 web 环境

    正常需要编写 web.xml 文件配置 servlet 映射,这里我们使用 java 类去配置:

    java 复制代码
    public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    	/**
    	 * 指定service / mapper层的配置类
    	 */
    	@Override
    	protected Class<?>[] getRootConfigClasses() {
    		return null;
    	}
    
    	/**
    	 * 指定springmvc的配置类
    	 * @return
    	 */
    	@Override
    	protected Class<?>[] getServletConfigClasses() {
    		return new Class<?>[] { MyConfig.class };
    	}
    
    	/**
    	 * 设置dispatcherServlet的处理路径!
    	 * 一般情况下为 / 代表处理所有请求!
    	 */
    	@Override
    	protected String[] getServletMappings() {
    		return new String[] { "/" };
    	}
    }
  3. 配置 tomcat 需要10版本以上

这是项目的访问路径

  1. 打开浏览器,访问 localhost:8080/hello

SpringMVC 核心注解

请求映射注解

控制请求到处理方法的映射关系,定义接口的访问路径和 HTTP 方法。

  1. @RequestMapping

作用:通用请求映射,可定义路径、HTTP方法、请求头等。

示例

java 复制代码
@RequestMapping(value = "/books", method = RequestMethod.GET)
public List<Book> listBooks() { ... }
  1. 快捷注解(基于 HTTP 方法)
  • @GetMapping:处理 GET 请求

    java 复制代码
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) { ... }
  • @PostMapping@PutMapping@DeleteMapping@PatchMapping

    优势 :代码更简洁,避免 method 参数冗余。

参数处理注解

从请求中提取参数并绑定到方法参数。

  1. @RequestParam

作用:获取 URL 参数或表单数据。

参数

  • name/value:参数名
  • required:是否必传(默认 true
  • defaultValue:默认值

示例

java 复制代码
@GetMapping("/search")
public List<Product> search(
    @RequestParam(name = "keyword", defaultValue = "") String query) { ... }
  1. @PathVariable

作用:获取 URL 路径中的变量(RESTful 风格)。

示例

java 复制代码
@DeleteMapping("/orders/{orderId}")
public void deleteOrder(@PathVariable String orderId) { ... }
  1. @RequestBody

作用:将 HTTP 请求体(如 JSON)反序列化为 Java 对象。

用途:接收 POST/PUT 请求中的复杂数据。

java 复制代码
@PostMapping("/employees")
public Employee createEmployee(@RequestBody Employee employee) { ... }

响应处理注解

控制方法返回值的处理方式。

  1. @ResponseBody

作用:将返回值直接写入响应体(而非视图解析)。

组合注解@RestController = @Controller + @ResponseBody

java 复制代码
@RestController
public class ApiController {
    @GetMapping("/status")
    public String getStatus() {
        return "OK";
    }
}
  1. @ResponseStatus

作用:自定义 HTTP 响应状态码。

java 复制代码
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException { ... }

模型与视图注解

处理视图模板渲染和数据传递。

  1. @ModelAttribute

作用

  • 方法级:在控制器方法执行前自动添加数据到模型。
  • 参数级:从模型获取已存在的属性。

示例

java 复制代码
@ModelAttribute("categories")
public List<String> loadCategories() {
    return Arrays.asList("Electronics", "Books");
}
  1. @SessionAttributes

作用:在多个请求间共享模型数据(存储到 Session)。

java 复制代码
@Controller
@SessionAttributes("cart")
public class CartController {
    @ModelAttribute("cart")
    public Cart initCart() {
        return new Cart();
    }
}

配置与扫描注解

定义组件扫描和配置规则。

  1. @Controller

作用:标记类为控制器组件,配合视图解析使用。

  1. @ControllerAdvice

作用:全局控制器增强(统一异常处理、模型数据预处理)。

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        return new ResponseEntity<>("Error occurred", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
  1. @EnableWebMvc

作用:启用 Spring MVC 默认配置(需在配置类使用)。

  1. 引入默认配置类

    自动导入 DelegatingWebMvcConfiguration 类,该配置类会注册 Spring MVC 的基础组件:

    • 处理器映射器(RequestMappingHandlerMapping
    • 处理器适配器(RequestMappingHandlerAdapter
    • 视图解析器(InternalResourceViewResolver
    • 消息转换器(如 JSON 处理的 MappingJackson2HttpMessageConverter
    • 数据绑定、类型转换、验证等基础设施。
  2. 启用关键功能

    • 支持 @Controller 注解的请求处理方法
    • 自动注册 @RequestBody@ResponseBody@Valid 等注解的处理逻辑
    • 默认静态资源处理器(如 /resources/** 路径映射)
java 复制代码
@Configuration
@EnableWebMvc
@ComponentScan("com.yigongsui")
public class WebConfig implements WebMvcConfigurer { ... }

其他重要注解

  1. @CrossOrigin

作用:处理跨域请求(可标注类或方法)。

java 复制代码
@CrossOrigin(origins = "https://example.com")
@RestController
public class ApiController { ... }
  1. @InitBinder

作用:自定义请求参数绑定规则。

java 复制代码
@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}

重要注解速查

注解 典型用途 作用范围
@RequestMapping 定义通用请求映射规则 类/方法
@GetMapping 处理 GET 请求 方法
@RequestParam 获取 URL 或表单参数 方法参数
@PathVariable 获取 URL 路径变量 方法参数
@RequestBody 解析请求体到 Java 对象 方法参数
@ResponseBody 直接返回数据而非视图 方法/类
@ModelAttribute 模型数据预处理 方法/参数
@ControllerAdvice 全局异常处理与数据绑定

最佳实践建议

  1. RESTful 设计 :优先使用 @GetMapping/@PostMapping 等快捷注解。
  2. 参数校验 :结合 @Valid 与 Hibernate Validator 进行数据验证。
  3. 响应标准化 :统一使用 @RestController 返回 JSON 数据。
  4. 异常处理 :通过 @ControllerAdvice 实现全局错误响应。

SpringMVC 接收数据

访问路径设置

使用 @RequestMapping 注解设置访问路径,它的作用就是将请求的 URL 地址和处理请求的方式(handler 方法)关联起来,建立映射关系。

SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的方法来处理这个请求。

  1. 精确路径匹配

    使用具体路径,可以设置多个:

    java 复制代码
    @RequestMapping({"/user/login","/user/login1"})

    /user 这个 / 可加可不加。

  2. 模糊路径匹配

    1. /* :代表一层路径:/user/*可以是 /user/a/user/b
    2. /**:代表一层或多层路径
  3. 注解可以放在方法上以及类上

    放在类上时,类下的所有方法的路径实际上是方法路径 + 类路径。

    java 复制代码
    @Controller
    @RequestMapping("/user")
    public class UserController {
    
    	@RequestMapping("/login")
    	public void login() {
    
    	}
    
    }

    路径就是 /user/login

    方法上必须要有 @RequestMapping 注解,不能省略掉。

  4. 请求方法设置:

    1. HTTP 协议定义了八种请求方式,在 SpringMVC 中封装到了下面这个枚举类:
    java 复制代码
    public enum RequestMethod {
      GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
    }
    1. 默认情况是任意请求方法都可以访问,可使用 @RequestMappingmethod 属性指定具体的请求方法,可以指定多个。
    java 复制代码
    @RequestMapping(value = "/login",method = RequestMethod.GET)
    @RequestMapping(value = "/login",method = {RequestMethod.GET,RequestMethod.POST})
  5. 进阶注解

还有 @RequestMapping 的 HTTP 方法特定快捷方式变体,用于指定具体的请求方法:

  • @GetMapping 等价于 @RequestMapping(value = ,method = RequestMethod.GET) 以下同理。
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

这些注解只能作用在方法上,不能放在类上。

param 和 json 参数接收

两者参数比较

在 HTTP 请求中,我们可以选择不同的参数类型,如 param 类型和 json 类型。下面对这两种参数类型进行区别和对比:

  1. 参数编码

    param 类型的参数会被编码为 ASCII 码。例如,假设 name=john doe,则会被编码为 name=john%20doe。而 json 类型的参数会被编码为 UTF-8。

  2. 参数顺序

    param 类型的参数没有顺序限制。但是,json 类型的参数是有序的。json 采用键值对的形式进行传递,其中键值对是有序排列的。

  3. 数据类型

    param 类型的参数仅支持字符串类型、数值类型和布尔类型等简单数据类型。而 json 类型的参数则支持更复杂的数据类型,如数组、对象等。

  4. 嵌套性

    param 类型的参数不支持嵌套。但是,json 类型的参数支持嵌套,可以传递更为复杂的数据结构。

  5. 可读性

    param 类型的参数格式比 json 类型的参数更加简单、易读。但是,json 格式在传递嵌套数据结构时更加清晰易懂。

总的来说,param 类型的参数适用于单一的数据传递,而 json 类型的参数则更适用于更复杂的数据结构传递。根据具体的业务需求,需要选择合适的参数类型。在实际开发中,常见的做法是:在 GET 请求中采用 param 类型的参数,而在 POST 请求中采用 json 类型的参数传递。

param 参数接收

  1. 直接接收:只要形参参数的名字和类型与 param 参数的名字和类型相同即可接收。

    java 复制代码
    @RestController
    public class UserController {
    
    	@GetMapping("/login")
    	public String login(String name,int age) {
    		System.out.println("name = " + name);
    		System.out.println("age = " + age);
    		return "name ->" + name + ",age ->" + age;
    	}
    
    }

    如果没有传递相应参数,则接收的参数为 null,不会报错(参数类型为基本数据类型,例如 int,会报错,因为不能为 null)。

  2. 注解接收:

    使用 @RequestParam 注解接收参数,可以指定传递的参数名。

    java 复制代码
    @GetMapping("/login")
    public String login(@RequestParam("name") String name, @RequestParam("age") int age)

    默认必须传递参数,否则会报错,可以通过 requiredfalse 表示可以不接收参数,可以通过 defaultValue 属性设置默认值。

    java 复制代码
    @GetMapping("/login")
    public String login(@RequestParam(value = "name",required = false,defaultValue = "zhangsan") String name, @RequestParam("age") int age)
  3. 接收集合参数

    必须使用 @RequestParam 指定参数名称

    java 复制代码
    @GetMapping("/login")
    public String login(@RequestParam("name") List<String> names) 

    访问 localhost:8080/login?name=zhangsan&name=1111 测试。

  4. 接收实体类参数

    传递参数名必须和实体类属性名相同。

    java 复制代码
    public class User {
    	
    	private String name;
    	private int age;
    	
    }
    
    @GetMapping("/login")
    public String login(User user) {
      return user.toString();
    }

    访问 localhost:8080/login?name=zhangsan&age=18 测试。

动态路径参数接收

开发中,有时参数也会放在路径上,例如 /login/用户名/密码,这是路径参数。

路径传递参数是一种在 URL 路径中传递参数的方式。在 RESTful 的 Web 应用程序中,经常使用路径传递参数来表示资源的唯一标识符或更复杂的表示方式。

对于这种形式的参数,不能使用 param 参数方式接收,要在路径上加上 {} 代表路径参数,同时使用注解 @PathVariable 接收参数。

java 复制代码
@GetMapping("login/{username}/{password}")
public String login(@PathVariable String username, @PathVariable String password) {
  return username + ":" + password;
}

该注解使用同 @RequestParam

浏览器访问 localhost:8080/login/zhangsan/123456 测试。

json 数据接收

前端参数为 json 数据:

json 复制代码
{
  "name":"zhangsan",
  "age":18
}

后端接收参数需要满足以下几个条件:

  1. 需要用实体类去接收,同时使用 @RequestBody 注解来将 JSON 数据转换为 Java 对象。

@RequestBody 注解表示当前方法参数的值应该从请求体中获取,并且需要指定 value 属性来指示请求体应该映射到哪个参数上。

  1. 需要引入 jackson 依赖。
xml 复制代码
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>
  1. 需要在 handlerAdpater 配置 json 转化器,只要在配置类上加上注解 @EnableWebMvc 即可。

代码示例:

java 复制代码
// 实体类
@Data
public class User {
	
	private String name;
	private int age;
	
}

// 配置类
@Configuration
@ComponentScan("com.yigongsui")
@EnableWebMvc
public class MyConfig {

	@Bean
	public HandlerMapping handlerMapping() {
		return new RequestMappingHandlerMapping();
	}

	@Bean
	public HandlerAdapter handlerAdapter() {
		return new RequestMappingHandlerAdapter();
	}

}

// controller
@RestController
public class UserController {

	@PostMapping("/login")
	public String login(@RequestBody User user) {
		return user.toString();
	}

}

前端传递 json 数据通常是 post 请求方式。

测试使用 postman 或 apifox 去测试。

@EnableWebMvc 注解介绍

基本作用

@EnableWebMvc 是 Spring MVC 的注解,用于启用 Spring MVC 的默认配置 。它等价于 XML 配置中的 <mvc:annotation-driven/>,主要功能包括:

  • 注册 RequestMappingHandlerMapping(处理 @RequestMapping 注解)
  • 注册 RequestMappingHandlerAdapter(支持 @RequestBody@ResponseBody 等)
  • 配置默认的 HTTP 消息转换器(如 JSON、XML 转换)
  • 启用数据绑定、验证、格式化等基础功能。

有了这个注解,主配置类就不需要注册 HandlerMappingHandlerAdapter 为组件了。

核心原理

@EnableWebMvc 通过导入 DelegatingWebMvcConfiguration 类实现功能:

java 复制代码
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}

该类继承自 WebMvcConfigurationSupport,提供默认的 MVC 配置逻辑。

接收 cookie 参数,在参数上添加注解 @CookieValue("cookieName"),value 是 cookie 的名字。

接收请求头参数,使用注解 @RequestHeader("Host"),value 是请求头的名字。

原生 api 对象参数

对于原生的 api 对象,只要在参数直接获取即可,下面是所有的原生 api 对象。

Controller method argument 控制器方法参数 Description
jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse 请求/响应对象
jakarta.servlet.http.HttpSession 强制存在会话。因此,这样的参数永远不会为 null
java.io.InputStream, java.io.Reader 用于访问由 Servlet API 公开的原始请求正文。
java.io.OutputStream, java.io.Writer 用于访问由 Servlet API 公开的原始响应正文。
@PathVariable 接收路径参数注解
@RequestParam 用于访问 Servlet 请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。
@RequestHeader 用于访问请求标头。标头值将转换为声明的方法参数类型。
@CookieValue 用于访问Cookie。Cookie 值将转换为声明的方法参数类型。
@RequestBody 用于访问 HTTP 请求正文。正文内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap 共享域对象,并在视图呈现过程中向模板公开。
Errors, BindingResult 验证和数据绑定中的错误信息获取对象!

示例:

java 复制代码
/**
 * 如果想要获取请求或者响应对象,或者会话等,可以直接在形参列表传入,并且不分先后顺序!
 * 注意: 接收原生对象,并不影响参数接收!
 */
@GetMapping("api")
@ResponseBody
public String api(HttpSession session , HttpServletRequest request,
                  HttpServletResponse response){
    String method = request.getMethod();
    System.out.println("method = " + method);
    return "api";
}

SpringMVC 响应数据

页面跳转控制

返回模版视图

  1. 返回视图名称(传统模式)
java 复制代码
@Controller
@RequestMapping("/demo")
public class DemoController {
    @GetMapping("/view")
    public String showView(Model model) {
        model.addAttribute("message", "Hello Thymeleaf");
        return "demoPage"; // 对应src/main/resources/templates/demoPage.html
    }
}
  • 配合视图解析器(如 Thymeleaf、JSP)。
  • 数据通过 Model 对象传递。
  1. ModelAndView 对象
java 复制代码
@Controller
public class MavController {
    @GetMapping("/mav")
    public ModelAndView getMAV() {
        ModelAndView mav = new ModelAndView("viewName");
        mav.addObject("key", "value");
        return mav;
    }
}
  • 可同时控制视图和数据。
  • 适合需要灵活设置响应头的场景。

转发和重定向

区别
特性 转发 (Forward) 重定向 (Redirect)
请求次数 1次(服务器内部跳转) 2次(客户端重新发起请求)
URL变化 不变化 变化为新的URL
数据共享 共享原始请求的request域属性 不共享,需通过RedirectAttributes或Session传递
性能 更高(无额外网络开销) 较低(需二次请求)
适用场景 同一应用内跳转 跨应用跳转、防表单重复提交
实现方式
  1. 转发(Forward)
  • 语法 :在控制器方法返回值前添加 forward: 前缀。

  • 示例

    java 复制代码
    @GetMapping("/forwardExample")
    public String forwardExample() {
        return "forward:/targetPath";  // 转发到/targetPath
    }
  • 特点

    • 目标路径为应用内相对路径,无需包含上下文路径。
    • 共享原始请求的所有参数和属性。
  1. 重定向(Redirect)
  • 语法 :在返回值前添加 redirect: 前缀。

  • 示例

    java 复制代码
    @PostMapping("/redirectExample")
    public String redirectExample(RedirectAttributes attributes) {
        attributes.addAttribute("param", "value");  // 通过URL传参
        attributes.addFlashAttribute("flashParam", "tempValue");  // 通过Flash属性传参
        return "redirect:/targetPath";  // 重定向到/targetPath
    }
  • 特点

    • 目标路径可为绝对路径 (如 redirect:http://external.com)或应用内路径。
    • 使用 RedirectAttributes 传递参数:
      • addAttribute():参数通过 URL 拼接(暴露在地址栏)。
      • addFlashAttribute():参数暂存 Session仅在下一次请求有效(适合敏感数据)。

返回 json 数据

需要使用注解 @ResponseBody 避免返回走视图解析。

返回 json 数据只要返回实体类即可。

java 复制代码
@GetMapping("/user")
@ResponseBody
public User getUser() {
  User user = new User();
  user.setName("zhangsan");
  user.setAge(18);
  return user;
}

返回静态资源

需要在 springmvc 配置类配置:

java 复制代码
@EnableWebMvc  //json数据处理,必须使用此注解,因为他会加入json处理器
@Configuration
@ComponentScan(basePackages = "com.atguigu.controller") //TODO: 进行controller扫描
//WebMvcConfigurer springMvc进行组件配置的规范,配置组件,提供各种方法! 前期可以实现
public class SpringMvcConfig implements WebMvcConfigurer {

    //配置jsp对应的视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //快速配置jsp模板语言对应的
        registry.jsp("/WEB-INF/views/",".jsp");
    }
    
    //开启静态资源处理 <mvc:default-servlet-handler/>
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

RESTFul 风格

核心概念

  1. REST 定义

    REST(Representational State Transfer)由 Roy Fielding 在 2000 年博士论文提出,核心特征:

    • 资源为中心:每个 URI 对应唯一资源。
    • 统一接口:标准 HTTP 方法操作资源。
    • 无状态通信:服务端不保存客户端状态。
    • 可缓存性:明确标记响应是否可缓存。
    • 分层系统:客户端无需了解直接连接的服务端。
  2. 资源标识

    • 使用 URI 定位资源。示例:

      GET /api/v1/books/123(获取ID=123的图书)

      DELETE /api/v2/users/456(删除ID=456的用户)

    • 避免动词式 URL(如/getUser?id=1

HTTP 方法规范

方法 幂等性 安全 语义 典型响应状态码
GET 获取资源 200 OK, 404 Not Found
POST 创建新资源 201 Created
PUT 完整更新资源 200 OK, 204 No Content
PATCH 部分更新资源 200 OK
DELETE 删除资源 204 No Content

状态码标准使用

  • 2xx 成功
    • 200 OK:常规成功。
    • 201 Created:资源创建成功(应包含Location头)。
    • 204 No Content:成功但无返回体。
  • 4xx 客户端错误
    • 400 Bad Request:请求格式错误。
    • 401 Unauthorized:身份验证失败。
    • 403 Forbidden:权限不足。
    • 404 Not Found:资源不存在。
  • 5xx 服务端错误
    • 500 Internal Server Error:未处理的异常。

HATEOAS 约束

HATEOAS (Hypermedia As The Engine Of Application State,超媒体作为应用状态的引擎 )是 REST 架构风格的核心约束之一。它要求客户端与服务的交互完全通过动态解析服务端返回的超媒体内容来完成,而无需依赖预先约定的接口文档。

典型实现:

json 复制代码
{
  "id": 123,
  "name": "示例图书",
  "_links": {
    "self": { "href": "/books/123" },
    "publisher": { "href": "/publishers/5" }
  }
}

设计规范

  1. 版本控制

    • URL 路径中:/api/v1/resource
    • HTTP 头中:Accept: application/vnd.myapi.v1+json
  2. 过滤与分页

    http 复制代码
    GET /products?color=red&price_from=100&page=2&size=20
  3. 响应格式

    推荐 JSON 格式,包含标准结构:

    json 复制代码
    {
      "code": 200,
      "data": { /* 业务数据 */ },
      "message": "操作成功",
      "timestamp": 1629091200
    }

与传统 RPC 风格对比

维度 RESTful RPC 风格
通信协议 HTTP 不限
核心元素 资源 操作/方法
接口设计 基于资源 + HTTP 方法 自定义方法名
典型 URL /orders/456 /getOrder?id=456
状态管理 无状态 可能维护会话状态

Spring MVC 实现示例

java 复制代码
@RestController
@RequestMapping("/api/v1/books")
public class BookController {
    
    @GetMapping("/{id}")
    public ResponseEntity<Book> getBook(@PathVariable Long id) {
        Book book = bookService.findById(id);
        return ResponseEntity.ok().body(book);
    }

    @PostMapping
    public ResponseEntity<Void> createBook(@RequestBody Book book) {
        Book saved = bookService.save(book);
        URI location = ServletUriComponentsBuilder
                        .fromCurrentRequest()
                        .path("/{id}")
                        .buildAndExpand(saved.getId())
                        .toUri();
        return ResponseEntity.created(location).build();
    }

    @PutMapping("/{id}")
    public ResponseEntity<Book> updateBook(@PathVariable Long id, 
                                          @RequestBody Book book) {
        // 实现更新逻辑
        return ResponseEntity.ok(updatedBook);
    }
}

最佳实践

  1. URI 设计原则

    • 使用名词复数形式:/users 而非 /getUsers
    • 层级关系:/departments/{deptId}/employees
  2. 版本管理

    建议在 URL 中包含版本号:/api/v2/resource

  3. 安全规范

    • 敏感操作使用 HTTPS。
    • 权限控制:结合 JWT 或 OAuth2。
  4. 文档化

    推荐使用 OpenAPI 规范(Swagger):

    java 复制代码
    @Operation(summary = "获取图书详情")
    @ApiResponse(responseCode = "200", description = "成功获取图书")
    @GetMapping("/{id}")
    public Book getBook(@Parameter(description = "图书ID") @PathVariable Long id) {
        // ...
    }

常见误区

  • 错误使用 HTTP 方法(如用 GET 执行删除)。
  • 忽略状态码语义(所有响应都返回 200)。
  • 过度设计嵌套资源层级(超过三级)。
  • 忽略缓存控制头(Cache-Control,ETag)。

通过遵循 RESTful 规范,可以构建出高度可维护、可扩展且易于理解的 API 系统。实际开发中建议结合 OpenAPI 工具链实现文档自动化。

全局异常处理机制

核心作用

解决分散的异常处理问题,实现:

  1. 统一异常响应格式(如 JSON 错误报文)。
  2. 集中管理异常处理逻辑。
  3. 避免重复的 try-catch 代码。
  4. 规范 HTTP 状态码返回。

实现方式

  1. @ControllerAdvice + @ExceptionHandler(主流方案)
java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {

    // 处理特定异常
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse(404, ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    // 兜底处理所有未明确捕获的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAll(Exception ex) {
        ErrorResponse error = new ErrorResponse(500, "系统内部错误");
        return ResponseEntity.internalServerError().body(error);
    }
}
  1. 实现 HandlerExceptionResolver 接口
java 复制代码
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
                                       HttpServletResponse response,
                                       Object handler, Exception ex) {
        if (ex instanceof BusinessException) {
            response.setStatus(400);
            writeJsonResponse(response, new ErrorResponse(400, ex.getMessage()));
        }
        return new ModelAndView(); // 已处理异常,返回空视图
    }

    private void writeJsonResponse(HttpServletResponse response, Object body) {
        // 实现 JSON 序列化输出
    }
}

关键注解详解

  1. @ControllerAdvice
    • 作用范围:所有带 @Controller 的类。
    • 可限定包范围:@ControllerAdvice(basePackages = "com.example.api")。
    • 支持响应体自动转换:配合 @ResponseBody 或使用 @RestControllerAdvice。
  2. @ExceptionHandler
    • 方法参数可获取:异常对象、请求对象、响应对象等。
    • 支持多异常类型:@ExceptionHandler({NullPointerException.class, IllegalArgumentException.class})
    • 优先级:具体异常 > 父类异常。

统一错误响应体设计

推荐标准结构(JSON 示例):

json 复制代码
{
  "timestamp": "2023-08-15T10:23:45Z",
  "status": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "请求的图书ID 123不存在",
  "path": "/api/books/123"
}

对应 Java 类:

java 复制代码
@Data
@AllArgsConstructor
public class ErrorResponse {
    private LocalDateTime timestamp;
    private int status;
    private String code;
    private String message;
    private String path;

    public ErrorResponse(HttpStatus status, String code, String message, String path) {
        this.timestamp = LocalDateTime.now();
        this.status = status.value();
        this.code = code;
        this.message = message;
        this.path = path;
    }
}

最佳实践

  1. 异常分类处理

    java 复制代码
    // 业务异常(返回 400)
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex, WebRequest request) {
        return buildResponse(HttpStatus.BAD_REQUEST, ex.getErrorCode(), ex.getMessage(), request);
    }
    
    // 数据校验异常(返回 422)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {
        String errorMsg = ex.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return buildResponse(HttpStatus.UNPROCESSABLE_ENTITY, "VALIDATION_FAILED", errorMsg);
    }
  2. 日志记录规范

    java 复制代码
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
        log.error("全局异常: {}", ex.getMessage());
        ex.printStackTrace(); // 生产环境应移除
        return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, "SERVER_ERROR", "系统繁忙");
    }
  3. 结合 HTTP 状态码

    异常类型 推荐状态码 说明
    权限不足 403 Forbidden
    参数校验失败 422 Unprocessable Entity
    认证失败 401 Unauthorized
    资源不存在 404 Not Found

常见问题解决方案

  1. 处理文件上传异常

    java 复制代码
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<ErrorResponse> handleFileSizeLimitExceeded() {
        return new ResponseEntity<>(
            new ErrorResponse(413, "FILE_TOO_LARGE", "文件大小超过限制"),
            HttpStatus.PAYLOAD_TOO_LARGE
        );
    }
  2. 兼容传统 JSP 视图

    java 复制代码
    @ExceptionHandler(CustomViewException.class)
    public ModelAndView handleViewException(CustomViewException ex) {
        ModelAndView mav = new ModelAndView("error/customError");
        mav.addObject("errorMsg", ex.getMessage());
        return mav;
    }
  3. 处理异步请求异常

    java 复制代码
    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public ResponseEntity<ErrorResponse> handleAsyncTimeout() {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body(new ErrorResponse(503, "ASYNC_TIMEOUT", "请求超时"));
    }

扩展技巧

  1. 自定义异常元注解

    java 复制代码
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ErrorMetadata {
        HttpStatus status() default HttpStatus.BAD_REQUEST;
        String errorCode();
    }
    
    @ErrorMetadata(status = HttpStatus.NOT_FOUND, errorCode = "USER_NOT_FOUND")
    public class UserNotFoundException extends RuntimeException {
        // ...
    }
  2. 自动扫描异常元数据

    java 复制代码
    @ExceptionHandler
    public ResponseEntity<ErrorResponse> handleAnnotatedException(Exception ex) {
        ErrorMetadata metadata = ex.getClass().getAnnotation(ErrorMetadata.class);
        if (metadata != null) {
            return buildResponse(metadata.status(), metadata.errorCode(), ex.getMessage());
        }
        return handleGlobalException(ex);
    }

通过合理设计全局异常处理机制,可使系统具备:

  • 清晰的错误分类体系。
  • 统一的客户端响应格式。
  • 完善的错误日志追踪。
  • 灵活的可扩展性。

拦截器使用

拦截器核心概念

**拦截器(Interceptor)**是 Spring MVC 框架中用于在请求处理的不同阶段插入自定义逻辑的组件。它作用于 Controller 层,可拦截 HTTP 请求并在以下三个关键节点执行操作:

方法名 执行时机 返回值作用
preHandle Controller 方法执行前 true 放行,false 终止流程
postHandle Controller 方法执行后,视图渲染前 修改 ModelAndView 对象
afterCompletion 视图渲染完成后 资源清理(如关闭流)

拦截器与过滤器的区别

特性 拦截器(Interceptor) 过滤器(Filter)
作用范围 Spring MVC 框架内 Servlet 规范,所有 Web 请求
依赖关系 依赖 Spring 容器 不依赖 Spring
控制粒度 针对 Controller 请求 所有 HTTP 请求
获取上下文 可通过 HandlerMethod 获取方法信息 仅能获取 Servlet API 对象

实现自定义拦截器

步骤1:创建拦截器类

java 复制代码
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        // 示例:检查用户登录状态
        HttpSession session = request.getSession();
        if (session.getAttribute("user") == null) {
            response.sendRedirect("/login");
            return false; // 拦截请求
        }
        return true; // 放行
    }

    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response,
                           Object handler,
                           ModelAndView modelAndView) {
        // 可添加全局模型数据
        modelAndView.addObject("version", "1.0.0");
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, 
                                Exception ex) {
        // 记录请求完成时间
        long startTime = (Long) request.getAttribute("startTime");
        System.out.println("请求耗时:" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

步骤2:配置拦截器(Java Config方式)

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor())
                .addPathPatterns("/**")              // 拦截所有路径
                .excludePathPatterns("/login", "/static/**"); // 排除登录页和静态资源
        
        registry.addInterceptor(new LogInterceptor()).order(1); // 设置多个拦截器顺序
    }
}

拦截器执行流程

假设配置了两个拦截器 Interceptor1Interceptor2

  1. preHandle 执行顺序:Interceptor1 → Interceptor2
  2. postHandle 执行顺序:Interceptor2 → Interceptor1
  3. afterCompletion 执行顺序:Interceptor2 → Interceptor1

典型应用场景

  1. 身份验证

    java 复制代码
    public boolean preHandle(...) {
        if (!isAuthenticated(request)) {
            response.sendError(401, "Unauthorized");
            return false;
        }
        return true;
    }
  2. 日志记录

    java 复制代码
    public void afterCompletion(...) {
        log.info("[{}] {} - {}ms", 
            request.getMethod(),
            request.getRequestURI(),
            System.currentTimeMillis() - startTime);
    }
  3. 性能监控

    java 复制代码
    public boolean preHandle(...) {
        request.setAttribute("startTime", System.currentTimeMillis());
        return true;
    }
  4. 全局数据注入

    java 复制代码
    public void postHandle(...) {
        modelAndView.addObject("currentUser", getCurrentUser());
    }

注意事项

  1. 路径匹配规则

    • /** 匹配所有路径(含子路径)。
    • /api/* 仅匹配单层路径。
    • /admin/** 匹配/admin下的所有层级。
  2. 避免循环拦截

    不要配置拦截器拦截自身的错误页面(如/error)。

  3. 性能优化

    preHandle中优先进行轻量级检查,耗时操作建议异步处理。

通过合理使用拦截器,可以实现横切关注点(Cross-Cutting Concerns)的统一处理,提升代码的可维护性。实际开发中建议结合具体业务需求设计拦截逻辑。

参数校验

添加依赖

使用 Hibernate Validator 实现校验(Bean Validation 规范):

xml 复制代码
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.0.Final</version>
</dependency>

在实体类定义校验规则

通过注解声明字段约束:

java 复制代码
public class User {
    @NotBlank(message = "用户名不能为空")
    private String name;

    @Email(message = "邮箱格式错误")
    private String email;

    @Min(value = 18, message = "年龄必须≥18")
    private Integer age;
}

在Controller触发校验

使用 @Valid@Validated 注解触发校验:

java 复制代码
@PostMapping("/register")
public String register(@Valid @RequestBody User user, BindingResult result) {
    if (result.hasErrors()) {
        // 处理校验失败逻辑
        return "error";
    }
    // 业务逻辑
    return "success";
}

处理校验错误

通过 BindingResult 获取错误详情:

java 复制代码
if (result.hasErrors()) {
    List<ObjectError> errors = result.getAllErrors();
    errors.forEach(error -> System.out.println(error.getDefaultMessage()));
}

常用校验注解

注解 作用
@NotNull 字段不能为 null
@NotBlank 字符串非空且去空格后非空
@NotEmpty 集合 size 不能为0
@Size 长度范围(min/max)
@Pattern 正则表达式匹配
@Future 日期必须在未来

自定义校验(示例:手机号格式)

  1. 定义注解
java 复制代码
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
    String message() default "手机号格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  1. 实现校验逻辑
java 复制代码
public class PhoneValidator implements ConstraintValidator<Phone, String> {
    private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && PHONE_PATTERN.matcher(value).matches();
    }
}

分组校验(场景:区分新增/更新操作)

  1. 定义分组接口
java 复制代码
public interface CreateGroup {}
public interface UpdateGroup {}
  1. 在实体类指定分组
java 复制代码
public class Product {
    @Null(groups = CreateGroup.class)  // 新增时id必须为空
    @NotNull(groups = UpdateGroup.class) // 更新时id不能为空
    private Long id;
}
  1. Controller 指定分组
java 复制代码
@PostMapping("/create")
public String create(@Validated(CreateGroup.class) @RequestBody Product product) {
    // ...
}

注意事项

  1. 嵌套对象校验 :对对象内的字段使用 @Valid 递归触发校验。
  2. 集合校验 :在 List 参数前加 @Valid 校验每个元素。
  3. 性能优化:避免过度复杂的校验逻辑影响性能。

通过以上步骤,Spring MVC 的参数校验能有效拦截非法数据,提升系统健壮性。

相关推荐
每次的天空4 分钟前
Android第四次面试(Java基础篇)
java·面试·职场和发展
晚风_END20 分钟前
kubernetes|云原生|部署单master的kubernetes 1.25.5版本集群完全记录(使用contained 运行时)
java·运维·开发语言·云原生·容器·golang·kubernetes
鲨鱼辣椒_TUT29 分钟前
Quartz知识点总结
java·quartz
码农的天塌了44 分钟前
JVM(Java虚拟机)的核心组成
java·jvm·java虚拟机
Y第五个季节1 小时前
Maven 简单了解
java·开发语言·maven
电子艾号哲1 小时前
STC89C52单片机学习——第26节: [11-2]蜂鸣器播放音乐
单片机·嵌入式硬件·学习
网络研究院1 小时前
Oracle 公布 Java 的五大新功能
java·oracle·编程·更新·功能
丁一郎学编程2 小时前
数据结构知识点1
java·数据结构·算法
牛马baby2 小时前
Java高频面试之集合-15
java·开发语言·面试
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS智慧生活商城系统(JAVA毕业设计)
java·vue.js·spring boot