Spring MVC
基本概念
简单来说,Spring MVC 是 Spring 对 MVC 的实现,对传统的 MVC 做了扩展。
- 将 Model 层分为了业务模型(Service)和数据模型(Repository)
- Controller 层分为了前端控制器(DispatcherServlet)和后端控制器(Controller)
以前的 Servlet + JSP 模式,开发要写很多的 Servlet,现在和 Servlet 解耦,简化了 Web 开发。
工作流程
Spring MVC 的工作流程可以分为以下几个关键步骤:
- 客户端请求:用户发送 HTTP 请求,
DispatcherServlet
接收请求。 - 执行拦截器的
preHandle()
:如果配置了拦截器,Spring MVC 会首先执行preHandle()
方法。如果返回false
,请求处理终止;否则,继续处理。 - 请求映射:
DispatcherServlet
通过HandlerMapping
找到对应的HandlerExecutionChain
(这里面包含了很多定义的HandlerInterceptor
,拦截器),然后通过HandlerAdapter
适配器的适配(适配器模式)后,执行 handler,即通过 Controller 的调用。 - 执行控制器方法:如果返回的是视图(如 JSP、Thymeleaf),则执行视图解析器进行视图解析和渲染;如果返回的是对象(如 JSON),则通过消息转换器(如
MappingJackson2HttpMessageConverter
)将对象转换为 JSON 数据。 - 执行拦截器的
postHandle()
:如果返回视图,拦截器的postHandle()
方法会在视图渲染之前执行。对于 JSON 响应,该方法仍然会执行。 - 视图渲染或 JSON 响应:对于视图,视图解析器会解析视图名称,并将其渲染为 HTML 页面。对于 JSON 响应,Spring MVC 会将对象序列化为 JSON,并返回给客户端。
- 执行拦截器的
afterCompletion()
:视图渲染或 JSON 响应完成后,Spring MVC 调用拦截器的afterCompletion()
方法,进行资源清理或日志记录。 - 响应客户端:最终将渲染的视图或 JSON 数据返回给客户端。
拦截器(Interceptor)
拦截器就是对请求起到一个拦截、过滤的作用,允许用户在方法执行前后进行自定义逻辑。
常见应用场景
- 身份认证
- 权限校验
- 日志记录等
拦截器三个核心方法
preHandle()
:在控制器方法执行之前调用。如果该方法返回false
,请求将被拦截,不会进入控制器方法。postHandle()
:在控制器方法执行之后,视图渲染之前调用。可以用于修改模型数据或视图。afterCompletion()
:在视图渲染完成后调用,用于清理资源或记录执行时间等。
拦截器的配置
在 Spring MVC 中,可以通过实现 HandlerInterceptor
接口来定义拦截器,并在配置文件或 Java 配置类中注册拦截器。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在处理器方法调用前进行拦截
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 处理器方法执行后,但视图渲染前执行
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 视图渲染后执行
}
}
实现 WebMvcConfigurer
接口来注册拦截器:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") // 拦截所有路径
.excludePathPatterns("/login", "/error"); // 排除某些路径
}
}