SpringMvc常见问题
什么是SpringMVC?
SpringMVC 是 Spring Framework 提供的一个基于 MVC(Model-View-Controller)设计模式的 Web 框架,主要用来简化 Java Web 应用的开发。
核心思想:
前端控制器 DispatcherServlet 统一接收所有请求,然后分发给对应的处理器
Controller(控制器) 处理业务逻辑,调用 Service/DAO,最后返回数据或页面。
Model(模型) 保存数据(比如 JavaBean、Map)。
View(视图) 负责展示数据(比如 JSP、Thymeleaf、Freemarker)。
工作流程:
用户请求
用户在浏览器输入 URL 或点击页面发送请求,比如
/user/list
。这个请求会被 DispatcherServlet 拦截(因为在
web.xml
或 Spring Boot 中配置了/*
或/
拦截)。
- 请求分发
DispatcherServlet 不直接处理业务,而是调用 HandlerMapping。
HandlerMapping 根据 请求 URL、请求方式(GET/POST)、注解(@RequestMapping),找到应该调用的 Controller 方法。
- 调用处理器(Controller)
DispatcherServlet 拿到对应的处理器(Controller 方法)后,会通过 HandlerAdapter(处理器适配器) 来真正调用这个方法。
这样 DispatcherServlet 就不需要关心方法的具体写法,只需要交给适配器处理。
- Controller 执行业务逻辑
Controller 方法执行,比如调用 Service → DAO → 数据库,得到数据。
返回结果有两种:
页面渲染:返回
ModelAndView
(包含数据和视图名)。接口数据:直接返回对象,配合
@ResponseBody
转 JSON。
- 结果处理
如果是 视图返回:
DispatcherServlet 会把 ModelAndView 交给 ViewResolver(视图解析器)。
ViewResolver 把视图名(如
"userList"
)解析成真正的物理页面(如/WEB-INF/views/userList.jsp
或templates/userList.html
)。
- 如果是 数据返回:
DispatcherServlet 会调用 HttpMessageConverter,把对象序列化成 JSON/XML,再返回给浏览器。
响应用户
如果是视图:View 负责渲染,把数据填充进页面模板,返回完整 HTML 给浏览器。
如果是 JSON:直接把 JSON 串写入响应体,返回给浏览器。
SpringMVC 主要组件
1. DispatcherServlet(前端控制器)
整个流程的入口和核心。
负责拦截所有请求,把请求分发给合适的 Controller,并最终返回响应。
2. HandlerMapping(处理器映射器)
根据请求的 URL,找到对应的 Controller 方法。
比如请求
/user/list
,HandlerMapping 会找到UserController.list()
。
3. HandlerAdapter(处理器适配器)
调用具体的 Controller 方法。
它能适配不同类型的处理器(普通方法、注解方法等),保证 DispatcherServlet 可以统一调用。
4. Controller(控制器)
核心业务逻辑的执行者。
通过注解
@Controller
+@RequestMapping
定义请求处理方法。返回 ModelAndView(数据+视图),或 JSON(配合
@ResponseBody
)。
5. ModelAndView(模型与视图对象)
Controller 返回的数据和视图名。
Model
:封装要展示的数据(如用户列表)。View
:指定展示的页面(如userList.jsp
或templates/userList.html
)。
6. ViewResolver(视图解析器)
把 Controller 返回的视图名解析成真正的视图对象。
比如返回
"userList"
,视图解析器可能会拼接路径/WEB-INF/views/userList.jsp
。
7. View(视图)
最终的展示层,比如 JSP、Thymeleaf、Freemarker。
负责把数据渲染成 HTML 页面,返回给浏览器。
8. HandlerExceptionResolver(异常处理器)
用来处理 Controller 或其他组件中出现的异常。
可以统一跳转到错误页面或返回 JSON 错误信息。
springmvc和springboot的区别
- 定义
- Spring MVC 是 Spring 框架中的一个模块,专门用来做 Web 开发,实现前后端分离的 MVC 架构。它负责处理请求、路由分发、控制器调用、视图解析等功能,是传统 Spring Web 项目的核心。
- Spring Boot 则是一个 快速开发框架,它不是替代 Spring MVC,而是基于 Spring(包括 Spring MVC)提供自动配置、快速启动和开箱即用的特性,目的是简化 Spring 项目的搭建和开发流程。
- 配置
Spring MVC:传统 Spring 项目需要手动配置大量 XML 或 Java 配置,包括 DispatcherServlet、视图解析器、拦截器、数据源、事务管理等。
Spring Boot:提供 自动配置(Auto-Configuration)和默认约定,只要引入依赖,很多组件可以开箱即用,减少手动配置。比如只引入
spring-boot-starter-web
就自动包含 Spring MVC、Tomcat 容器、JSON 解析器等。
项目启动
Spring MVC:通常需要部署到外部 Servlet 容器(如 Tomcat、Jetty),通过
web.xml
或WebApplicationInitializer
配置 DispatcherServlet,启动稍复杂。Spring Boot:内嵌容器(如 Tomcat、Jetty),可以直接运行
main
方法启动整个应用,不需要外部部署,快速方便。
Spring MVC 里用到的注解
控制层相关注解
注解 | 作用 |
---|---|
@Controller | 标记一个类是 控制器,返回页面时用 |
@RestController | @Controller + @ResponseBody ,返回 JSON 或 XML |
@RequestMapping | 映射请求路径,可以标在类或方法上 |
@GetMapping / @PostMapping / @PutMapping / @DeleteMapping | 更细分的请求方式映射,等价于 @RequestMapping(method = RequestMethod.GET) 等 |
@ResponseBody | 把方法返回值直接作为响应体(自动转 JSON/XML),而不是解析为视图 |
@RequestBody | 用来接收前端传来的 JSON/XML 请求体,自动绑定到对象上 |
@PathVariable | 从 URL 路径中取值(Restful 风格) |
@RequestParam | 从请求参数(?id=1)中取值 |
@SessionAttributes | 指定某些 Model 数据自动存入 HttpSession |
@ModelAttribute | 方法参数绑定时,用于接收前端表单数据;也可用于在方法执行前向 Model 添加数据 |
依赖注入和 Bean 管理相关
注解 | 作用 |
---|---|
@Component | 声明一个 Bean,交给 Spring 容器管理 |
@Service | 声明一个业务层的 Bean(语义化更清晰,本质是 @Component ) |
@Repository | 声明一个 DAO 层的 Bean(支持持久层异常转换) |
@Controller | 声明一个控制层的 Bean(同样是 @Component ) |
@Autowired | 自动注入依赖(按类型) |
@Qualifier | 配合 @Autowired 按名称注入 |
@Resource | JSR-250 提供的依赖注入注解,可以按名称或类型注入 |
@Value | 给字段注入配置文件中的值 |
数据校验相关
注解 | 作用 |
---|---|
@Valid | 启用 JSR-303 数据校验 |
@Validated | 启用数据校验,支持分组校验(Spring 提供) |
@NotNull / @NotEmpty / @NotBlank | 约束字段不能为空 |
@Size(min, max) | 限制字符串、集合大小 |
@Email | 验证邮箱格式 |
@Pattern(regexp="...") | 正则校验 |
配置相关
注解 | 作用 |
---|---|
@Configuration | 声明一个配置类(等价于 xml 配置) |
@Bean | 在 @Configuration 类中声明一个 Bean |
@ComponentScan | 扫描指定包,自动注册 Bean |
@EnableWebMvc | 开启 Spring MVC 功能 |
@PropertySource | 指定外部配置文件路径 |
@Import | 引入其他配置类 |
@Controller
和 @RestController
有什么区别?
@Controller
主要用于返回视图,一般配合模板引擎(如 JSP、Thymeleaf)使用,通过返回逻辑视图名,然后由 ViewResolver
解析成最终的页面。它本身不负责将对象直接转换为 JSON 或 XML,如果需要返回 JSON,需要在方法上加 @ResponseBody
注解。
@RestController
是 @Controller
与 @ResponseBody
的组合注解,用于构建 RESTful 风格的接口,直接将方法返回的对象写入响应体,并自动转换为 JSON 或 XML。使用 @RestController
时,不需要再在每个方法上额外添加 @ResponseBody
,适合开发 API 接口或前后端分离的项目。总的来说,@Controller
更适合返回页面,@RestController
更适合返回数据。
Spring MVC 中如何统一处理异常?
使用 @ExceptionHandler
在单个 Controller 中处理异常
@Controller
public class UserController {@GetMapping("/user/{id}")
public User getUser(@PathVariable int id) {
if (id <= 0) {
throw new IllegalArgumentException("用户ID非法");
}
return userService.getUserById(id);
}@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument(IllegalArgumentException ex, Model model) {
model.addAttribute("error", ex.getMessage());
return "error"; // 返回错误页面
}
}
使用 @ControllerAdvice
全局处理异常
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public Map<String, Object> handleIllegalArgument(IllegalArgumentException ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("message", ex.getMessage());
return result; // 返回 JSON 错误信息
}@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String, Object> handleException(Exception ex) {
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("message", "服务器内部错误");
return result;
}
}
Spring MVC 拦截器和过滤器有什么区别?
过滤器属于 Servlet 层,由 Servlet 容器管理,它可以拦截整个 web 应用的请求和响应,不依赖 Spring,因此不仅可以处理 Spring MVC 的请求,也可以处理静态资源或其他 Servlet 请求。过滤器一般实现 javax.servlet.Filter
接口,通过 web.xml
配置或者使用 @WebFilter
注解注册。
拦截器属于 Spring MVC 层,由 Spring 容器管理,只作用于经过 DispatcherServlet 调度的请求,也就是说它只能拦截 Spring MVC 的 Controller 请求。拦截器实现 HandlerInterceptor
接口,通过配置 WebMvcConfigurer.addInterceptors()
注册。拦截器可以在 Controller 方法调用前、调用后以及视图渲染后执行逻辑,而过滤器的逻辑是围绕整个请求链执行,先执行请求再执行响应。
@Requestbody,@RequestParam,@PathVariable的区别
@RequestBody—— 请求体参数:把 HTTP 请求体(body)中的数据,自动转换成 Java 对象
@RequestParam—— 查询参数 / 表单参数:从 URL 查询字符串(就是 ?key=value
这种形式的参数) 或 表单参数里取值,绑定到方法参数上。
@PathVariable—— 路径参数:从 URL 路径里的占位符取值,绑定到方法参数上。比如/user/123
表示获取 id 为 123 的用户。
@RequestMapping 注解有什么作用?
@RequestMapping
是 SpringMVC 核心注解之一,主要用于 请求映射,也就是把 HTTP 请求 URL 和 Controller 方法关联起来。@RequestMapping
就是 告诉 HandlerMapping:这个 URL 应该对应哪个 Controller方法没有 @RequestMapping
或者映射不匹配,HandlerMapping 就找不到对应的 Controller,HandlerAdapter 也没法调用。
标注在 类上:为该 Controller 下的所有方法 统一指定请求前缀。
标注在 方法上:指定具体的请求路径、请求方式、请求参数等条件。
GET、POST、PUT、DELETE 等方法的区别
GET —— 获取资源:用于从服务器获取资源数据,它的主要作用是让客户端能够请求服务器提供的资源而不改变服务器的状态。GET 请求一般通过 URL 查询参数(query string)传递数据,浏览器地址栏可见,因此适合简单、非敏感的数据查询。GET 请求是安全且幂等的,重复请求不会修改服务器的数据,常用于获取列表、查看用户信息或搜索内容。
POST —— 创建资源:用于向服务器提交数据,通常会在服务器端创建新的资源或处理数据。POST 请求的数据放在请求体中,可以是 JSON、表单或 XML,因此适合传输复杂或敏感信息。POST 请求不幂等,重复提交可能导致多条记录被创建,它的主要用途是新增资源或提交表单信息,但在实际开发中,也可以用于获取复杂查询结果或传输大量数据,这种情况下虽然是获取信息,但仍通过 POST 来保证安全和数据量的完整传输。
PUT —— 更新资源:更新服务器上的已有资源,它的作用是修改资源的全部或部分内容。PUT 请求的数据也放在请求体中,通常是 JSON 或 XML。与 POST 不同,PUT 是幂等的,重复请求不会多次改变资源的状态,也就是说,无论执行多少次相同的 PUT 请求,服务器的最终状态都是一致的。PUT 常用于修改用户信息、更新订单状态或替换某条记录的内容。
DELETE —— 删除资源:用于从服务器删除资源,它的作用是移除服务器上指定的资源。DELETE 请求一般通过 URL 指定要删除的对象,也可以通过请求体传递额外数据。DELETE 是幂等的,重复请求不会产生额外效果,删除操作通常是不可逆的。它常用于删除用户、文章、订单等资源。
Spring MVC中的数据验证
前端发送请求 ------> DispatcherServlet 拦截请求 -----------> HandlerMapping 根据 URL、请求方式、请求参数等找到对应的处理方法---------->DispatcherServlet 交给 HandlerAdapterr 根据方法参数类型进行 数据绑定 ------------> 数据绑定(表单数据 则使用 DataBinder 自动绑定到 POJO 或方法参数;JSON 数据则 使用 HttpMessageConverter反序列化成对象)--------->方法参数上使用 @Valid
或 @Validated
注解,触发验证器(Validator)检查对象属性是否符合约束条件(Validator 能校验,是因为它可以通过反射读取对象字段和注解,并知道每个注解对应的校验规则) ---------->验证结果会存入 BindingResult 或 Errors 对象------------->根据验证结果,Controller 可以返回错误信息或继续执行业务逻辑。
常用注解(JSR-303)
注解 | 说明 | 示例 |
---|---|---|
@NotNull | 属性不能为 null | @NotNull private String name; |
@NotEmpty | 字符串、集合不能为空 | @NotEmpty private String email; |
@NotBlank | 字符串不能为 null、空或空格 | @NotBlank private String username; |
@Size(min=, max=) | 限制长度(字符串、集合) | @Size(min=2, max=10) private String name; |
@Min/@Max | 数值范围限制 | @Min(18) @Max(60) private Integer age; |
@Email | 邮箱格式验证 | @Email private String email; |
@Pattern(regexp=) | 自定义正则表达式 | @Pattern(regexp="\\d{11}") private String phone; |
WebApplicationContext
WebApplicationContext = ApplicationContext + Web 特性
ApplicationContext :Spring 的核心容器,管理 Bean 的生命周期、依赖注入等。
WebApplicationContext :继承自 ApplicationContext ,专门为 Servlet 环境 提供支持。
它能够访问 ServletContext(是 Web 应用级别的全局上下文,可以存储全局共享数据、获取部署路径、读取资源) 和 Web 资源(如 request、session、servletContext 等)
Spring MVC 的 DispatcherServlet 会使用 WebApplicationContext 来管理 Controller、ViewResolver、HandlerMapping 等 Web 相关 Bean
管理 Spring MVC Bean:Controller、HandlerMapping、HandlerAdapter、ViewResolver 等 Web 相关组件
与 ServletContext 集成,可以访问 Web 应用的上下文(比如路径、Servlet 属性等)
Spring MVC 与 Servlet
Servlet 是 Java Web 的基础组件,用来处理请求和生成响应。它的生命周期:init → service → destroy。Spring MVC 的 DispatcherServlet 就是一个 Servlet,所有 Spring MVC 的 Controller 都运行在 Servlet 容器上,通过 Servlet 处理请求
SpringMVC 怎么样设定重定向和转发的?
重定向:告诉浏览器重新发起一个新的请求,URL 会改变
方式 :
返回 redirect:
前缀
@GetMapping("/oldUrl")
public String redirect() {
return "redirect:/newUrl"; // 重定向到 /newUrl
}
使用 RedirectView
@GetMapping("/oldUrl2")
public RedirectView redirectView() {
return new RedirectView("/newUrl");
}
转发:服务器内部把请求转发到另一个资源,浏览器 URL 不变
方式:
返回 forward:
前缀
@GetMapping("/forwardTest")
public String forward() {
return "forward:/targetUrl"; // 内部转发
}
使用 RequestDispatcher
(手动)
@GetMapping("/forwardTest2")
public void forward(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/targetUrl").forward(request, response);
}
Spring MVC如何处理静态资源
因为前端页面一定会用到 JS、CSS、图片。在默认情况下,Spring MVC 的 DispatcherServlet
会拦截所有请求,这会导致静态资源也被拦截,无法直接访问。
基于 Java 配置
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 映射 /static/** 到 classpath:/static/
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
Spring Boot 的简化方式
在 Spring Boot 中,已经默认做了静态资源映射。
只要把文件放在以下目录之一,Spring Boot 会自动映射:
classpath:/static/
classpath:/public/
classpath:/resources/
classpath:/META-INF/resources/
访问方式:
http://localhost:8080/js/app.js → 对应 src/main/resources/static/js/app.js
SpringMvc 的控制器是不是单例模式,有什么问题,怎么解决?
是的,SpringMVC 的 Controller 默认是单例(singleton)模式的。Spring 容器在启动时就会创建 Controller 实例,并放在 IoC 容器中,所有请求都会复用这个单例对象。
单例模式可能带来的问题:
同一个 Controller 实例要处理多个请求,如果写法不当,就会有线程安全问题,比如在 Controller 里定义了一个 private List<User> users;
并在方法里修改它。多个请求同时进来,就会导致数据混乱。
怎么解决:
- 在 Controller 中只使用 局部变量,不要定义可变的实例变量。因为局部变量是放在方法栈中的,每个线程都有自己的副本,天然线程安全。
- 使用 @Scope("prototype"):如果一定要在 Controller 里使用成员变量,可以把 Controller 改为 多例模式(每次请求创建一个新对象)。
使用线程安全的类如果必须在 Controller 里维护状态,可以用线程安全的数据结构(如
ConcurrentHashMap
)。
SpingMvc 常用的数据传递对象
方式 | 作用域 | 适用场景 |
---|---|---|
Model | request | 最推荐,简洁,专注于数据传递 |
ModelMap | request | 和 Model 差不多,兼容 Map 风格 |
ModelAndView | request | 需要同时返回视图和数据时 |
HttpServletRequest | request | 偏 Servlet 风格,Spring MVC 里不推荐 |
HttpSession | session | 跨请求共享数据时 |
Spring MVC 如何上传文件?
首先,需要在 Spring 配置中注册一个 MultipartResolver,用于将请求体中包含的文件数据解析成 MultipartFile 对象。Spring 提供了 CommonsMultipartResolver
(基于 Apache Commons FileUpload)或者 StandardServletMultipartResolver
(基于 Servlet 3.0 API)两种实现。
在 Spring Boot 中,如果引入了 spring-boot-starter-web
,默认会自动配置 MultipartResolver
,无需手动配置。在前端表单中,需要设置 enctype="multipart/form-data"
,同时通过 POST 方法提交文件。Spring MVC 会自动将上传的文件封装到 MultipartFile 对象中,开发者可以通过 getOriginalFilename()
获取文件名,transferTo()
保存文件到服务器,或者通过 getInputStream()
读取文件内容进行处理。
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
if (!file.isEmpty()) {
String fileName = file.getOriginalFilename();
file.transferTo(new File("/uploads/" + fileName));
}
return "success";
}
怎么样把ModelMap里面的数据放入Session里面
@Controller
@SessionAttributes("user") // 告诉Spring:名字叫 "user" 的数据要放进Session
public class UserController {@RequestMapping("/login")
public String login(Model model) {
User user = new User("Tom", 20);// 把对象放入Model,key是 "user"
model.addAttribute("user", user);// Spring看到这个key是 @SessionAttributes 标记过的,就自动放到Session里
return "success";
}@RequestMapping("/profile")
public String profile(ModelMap model) { // 从 ModelMap 取出,其实底层是从 Session 里拿的
User user = (User) model.get("user");
System.out.println("从Session取出:" + user);
return "profile";
}
}
SpingMvc 中的控制器的注解一般用什么
在 SpringMVC 中,控制器最常用的注解是
@Controller
。如果返回的是 JSON 数据,可以用
@RestController
来代替,等价于@Controller + @ResponseBody
。(@ResponseBody
告诉 Spring:不要去找视图解析器,而是直接写入响应体,SpringMVC 会找到合适的 HttpMessageConverter,把 对象转换成 JSON)理论上
@Component
也可以标记控制器,但不推荐,因为缺少明确语义。
Spring MVC如何实现异步请求处理?
在传统同步模式下:
浏览器发一个请求 → Tomcat 分配线程 → Controller 执行业务逻辑 → 返回结果 → 线程释放
如果业务逻辑耗时很长(如数据库大查询),线程就会一直被占用,容易导致 线程资源耗尽。
异步处理的思路:
浏览器请求 URL,DispatcherServlet 接收请求,发现 Controller 方法返回的是 Callable
或 DeferredResult
,然后就会挂起当前请求线程,DispatcherServlet 不再占用原来的请求线程,当前线程可以去处理其他请求,然后异步线程去执行任务,当异步逻辑在 独立线程池中执行完成后,将结果写入 Callable/DeferredResult,DispatcherServlet 接收到异步任务结果,回调给 视图解析器或 HttpMessageConverter 渲染响应,返回客户端。
实现:
使用 Callable<T>:
调用 Controller 方法,发现返回 Callable
→ 异步处理,然后当前处理线程释放,业务逻辑在另一个线程池中执行
@GetMapping("/asyncCallable")
@ResponseBody
public Callable<String> asyncCallable() {
return () -> {
Thread.sleep(3000);
return "异步返回结果";
};
}
使用 DeferredResult<T>:
更灵活,可以在 Controller 外部异步完成处理(比如异步消息、事件回调)
@GetMapping("/asyncDeferred")
@ResponseBody
public DeferredResult<String> asyncDeferred() {
DeferredResult<String> result = new DeferredResult<>();
new Thread(() -> {
try {
Thread.sleep(3000);
result.setResult("返回结果");
} catch (InterruptedException e) {
result.setErrorResult(e.getMessage());
}
}).start();
return result;
}
如何解决 POST 请求和 GET 请求中文乱码问题?
GET 请求中文乱码问题:
GET 请求的参数是拼在 URL 上的,浏览器在发送时会对 URL 进行编码(一般是 UTF-8 或 GBK),但服务器端(Tomcat)在解析时可能使用了 默认 ISO-8859-1 编码,导致乱码。
解决方案
Spring Boot 中在 application.properties
中配置:server.tomcat.uri-encoding=UTF-8
POST 请求中文乱码问题:
POST 请求的参数在 请求体(Request Body) 中传输,Tomcat 读取时默认使用 ISO-8859-1 解码。
解决方案
Spring Boot 内置了 CharacterEncodingFilter
,默认就是 UTF-8,如果要确保开启,可以在 application.properties
里加:
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8