HandlerInterceptor
是Spring MVC框架中的一个接口,在org.springframework.web.servlet包下,它是Spring MVC框架中的一部分,它参与到Spring MVC的请求处理生命周期中,所以不是Spring IoC容器的Bean生命周期中的东西,它用于实现拦截器功能,也就是说用它的时候容器都已经启动了。拦截器是AOP(面向切面编程)的一个应用,允许在请求处理过程中的不同阶段执行一些通用操作,如日志记录、权限校验、事务管理等,而无需修改每个控制器方法的代码。
HandlerInterceptor
接口定义了三个方法,分别对应请求处理的不同阶段:
-
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):
- 在控制器方法执行前被调用。可以在这个方法中进行预处理操作,比如检查用户是否登录、权限验证等。返回
true
表示继续执行后续操作(调用控制器方法),返回false
则表示中断请求,不继续执行。
- 在控制器方法执行前被调用。可以在这个方法中进行预处理操作,比如检查用户是否登录、权限验证等。返回
-
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView):
- 在控制器方法处理完请求后,且解析视图之前被调用。可以在这里对模型数据进行修改,或者添加额外的视图属性。注意,此方法的执行不影响控制器方法的执行结果,即不论控制器方法是否成功执行,此方法都会被调用。
-
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex):
- 在整个请求处理完毕后被调用,即在视图渲染完成并且响应已发送给客户端后执行。可以在此方法中进行资源清理工作,如关闭数据库连接等。此方法还会接收一个异常参数,如果在之前的处理过程中抛出了异常,这个异常信息会传到这里。
使用HandlerInterceptor
需要创建一个实现该接口的类,并在Spring MVC的配置中注册这个拦截器,之后所有匹配的请求都将经过这个拦截器的处理流程。
既然它是spring mvc请求处理生命周期里的东西,那么我们看一下spring mvc请求的步骤:
- 请求到达DispatcherServlet:所有的请求首先到达前端控制器DispatcherServlet。
- 查找HandlerMapping:DispatcherServlet根据请求信息查找HandlerMapping,确定哪个Controller(或Handler)将处理这个请求。
- 实例化Handler(Controller):如果Controller还未被实例化,Spring会创建一个实例。
- 执行HandlerInterceptor的preHandle方法:在调用Controller方法之前,Spring会按照配置顺序调用所有注册的拦截器的
preHandle
方法。 - 调用Controller方法:如果所有拦截器的
preHandle
方法都返回true
,则执行Controller中的处理方法。 - 执行HandlerInterceptor的postHandle方法:Controller方法执行完毕后,但在视图渲染之前,Spring会调用所有拦截器的
postHandle
方法。 - 视图渲染:接下来,Spring会根据Controller方法返回的逻辑视图名查找对应的视图,并进行渲染。
- 执行HandlerInterceptor的afterCompletion方法:最后,当请求处理全部完成,包括视图渲染完成且响应已发送给客户端后,Spring会调用所有拦截器的
afterCompletion
方法。
我们如果想自定义一个HandlerInterceptor怎么做呢?
一般情况会用HandlerInterceptor接口的实现类HandlerInterceptorAdapter类。比如:
java">public class AuthInterceptor extends HandlerInterceptorAdapter {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {String requestUrl = request.getRequestURI();if (checkAuth(requestUrl)) {return true;}return false;}private boolean checkAuth(String requestUrl) {System.out.println("===权限校验===");return true;}
}
然后注册到spring容器中:
java">@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {@Beanpublic AuthInterceptor getAuthInterceptor() {return new AuthInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new AuthInterceptor());}
}
那么WebMvcConfigurerAdapter又是什么呢?
WebMvcConfigurerAdapter
是Spring MVC框架中的一个类,它是一个抽象类,实现了WebMvcConfigurer
接口。这个类的存在主要是为了提供一个方便的扩展点,使得开发者可以在不重写整个配置类的情况下,自定义Spring MVC的配置。
常用方法包括但不限于:
addCorsMappings(CorsRegistry registry)
:用于配置跨域资源共享(CORS)策略。addInterceptors(InterceptorRegistry registry)
:用于注册自定义拦截器,可以全局或针对特定路径应用。configureViewResolvers(ViewResolverRegistry registry)
:配置视图解析器,决定如何将逻辑视图名解析为实际的视图技术。configureContentNegotiation(ContentNegotiationConfigurer configurer)
:配置内容协商策略,以支持不同的响应格式(如JSON、XML)。addViewControllers(ViewControllerRegistry registry)
:配置简单的视图控制器,直接将URL映射到视图名称。addResourceHandlers(ResourceHandlerRegistry registry)
:配置静态资源的处理,如图片、CSS、JavaScript文件的路径映射。configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
:配置默认的Servlet处理,通常用于处理静态资源。
不过随着Spring Framework 5.x及后续版本的发展,官方推荐直接实现WebMvcConfigurer
接口而不是继承WebMvcConfigurerAdapter
,因为从Spring Framework 5.0开始,WebMvcConfigurerAdapter
类已被弃用,鼓励使用默认方法(default methods)直接在接口WebMvcConfigurer
中实现配置。尽管如此,很多遗留代码和文档中仍然可以看到关于WebMvcConfigurerAdapter
的使用。
我们经常听说Handler或者Controller,那么他俩啥关系呢?
Handler(处理器): Handler是一个更广泛的概念,指的是能够处理HTTP请求并生成响应的组件。它可以是一个类或者一个方法,负责接收来自客户端的请求,执行相应的业务逻辑处理,并准备响应数据。在Spring MVC中,Handler不仅仅局限于我们通常理解的Controller,它还可以是实现了特定接口(如HttpRequestHandler
)的类,或者是标记了特定注解(如@RequestMapping
)的方法。因此,Handler实际上涵盖了所有能够响应HTTP请求的处理单元。
Controller(控制器): Controller是Spring MVC中最常见的Handler类型,它通常是一个类,通过@Controller
注解标记,负责处理HTTP请求并返回视图或者数据。Controller内部可以包含多个处理请求的方法,这些方法上通常会使用@RequestMapping
及其衍生注解(如@GetMapping
、@PostMapping
等)来明确指定处理何种类型的请求。从这个角度看,Controller是Handler的一种具体实现形式,专门用于Web请求的处理。
也就是说所有Controller都是Handler,因为它们都能处理请求并作出响应,但不是所有的Handler都是Controller。Handler是一个更为通用的概念,它包括了Controller以及其他形式的请求处理器。在Spring MVC的架构中,通过HandlerMapping将特定的请求映射到相应的Handler(包括但不限于Controller)上,然后通过HandlerAdapter适配器来调用Handler处理请求。
HandlerMapping又是什么呢?
HandlerMapping
在Spring MVC框架中是一个核心组件,它负责将接收到的HTTP请求映射到合适的处理器(Handler
)上,这个处理器通常是控制器(Controller
)中的一个方法。这个过程是Spring MVC处理请求的首要步骤之一,确保每个进入系统的请求能够被正确地导向至处理该请求的业务逻辑。
它的作用一般为:
-
请求到处理器的映射:基于请求的URL、HTTP方法、请求头等信息,查找并决定哪个处理器(Controller方法)应当处理该请求。这类似于早期Java Web开发中在
web.xml
文件中通过servlet-mapping
将URL映射到Servlet的功能,但在Spring MVC中更为灵活和强大。 -
存储映射关系:在系统启动时,
HandlerMapping
会初始化并存储这些映射关系,通常是将URL路径或者特定的请求条件与对应的处理器关联起来,保存在内存中的数据结构中,便于快速查找。 -
支持多种映射策略:Spring MVC提供了多种
HandlerMapping
的实现,如BeanNameUrlHandlerMapping
(根据Bean名称进行映射)、RequestMappingHandlerMapping
(基于@RequestMapping
注解进行映射)等,允许开发者根据应用的需求选择最合适的映射策略。 -
处理拦截器集成:
HandlerMapping
还会处理与处理器相关的拦截器(HandlerInterceptor
)配置,将拦截器链(HandlerInterceptor
集合)与处理器关联,使得在请求处理前后可以执行额外的逻辑,如安全检查、日志记录等。
HandlerMapping在什么时候被初始化的呢?
HandlerMapping
在Spring MVC应用的初始化阶段被加载和初始化。
-
Spring容器启动:当Spring Boot应用或传统的基于XML配置的Spring MVC应用启动时,Spring IoC容器开始初始化。
-
DispatcherServlet初始化:作为Spring MVC的核心前端控制器,
DispatcherServlet
的初始化是自动配置的一部分,或者通过web.xml手动配置。当DispatcherServlet
实例化时,它会创建并配置自己所需的各个组件,包括HandlerMapping
。 -
寻找HandlerMapping Bean:
DispatcherServlet
会查询Spring容器,寻找所有类型的HandlerMapping
Bean。这些Bean可能是直接在配置中定义的,或是通过组件扫描自动发现的。 -
初始化HandlerMapping:对于找到的每一个
HandlerMapping
Bean,Spring会调用其生命周期方法,包括afterPropertiesSet
方法,完成必要的初始化工作。在这个过程中,HandlerMapping
会读取应用上下文中所有的Bean,找出标记为控制器(通常是带有@Controller
注解的类)的Bean,并分析这些控制器中带有请求映射注解(如@RequestMapping
)的方法,建立请求URL到具体处理方法的映射关系。 -
构建处理器链:同时,
HandlerMapping
也会处理任何配置的拦截器(HandlerInterceptor
),并将它们组织成拦截器链,与处理器方法关联,以便在请求处理前后执行拦截逻辑。 -
准备就绪:完成上述步骤后,
HandlerMapping
就绪,等待处理接收到的HTTP请求。
HandlerMapping有几种呢?
-
BeanNameUrlHandlerMapping
:- 用途: 这是最基础的映射方式,它将请求URL与Bean名称进行匹配。例如,一个名为
myController
的Bean可以处理所有以/myController
开头的请求。 - 使用: 通常不需要显式配置,除非有特殊需求。控制器的Bean名称需与URL路径相匹配。
- 用途: 这是最基础的映射方式,它将请求URL与Bean名称进行匹配。例如,一个名为
-
SimpleUrlHandlerMapping
:- 用途: 提供更灵活的URL到处理器的映射,可以将一个URL映射到多个处理器上,也可以设置默认处理器。
- 使用: 通过配置文件或Java配置类定义映射关系,例如可以为每个URL指定一个或多个处理类和方法。
-
RequestMappingHandlerMapping(这是我们最常用的)
:- 用途: 基于注解的映射,是最常用的一种。支持
@RequestMapping
、@GetMapping
、@PostMapping
等注解,用于映射URL到方法。 - 使用: 在Controller类或方法上使用上述注解声明请求映射规则。Spring自动检测这些注解并创建映射关系。
- 用途: 基于注解的映射,是最常用的一种。支持
-
虽然不是直接的PathMatchConfigurer
:HandlerMapping
实现,但通过它可以配置路径匹配的行为,如启用Ant风格路径、路径前缀等,影响所有基于路径映射的HandlerMapping
,尤其是RequestMappingHandlerMapping
。 -
自定义HandlerMapping:
如果标准实现不能满足需求,可以实现HandlerMapping
接口来自定义映射逻辑。
那么HandlerAdapter又是什么呢?
HandlerAdapter
在Spring MVC框架中扮演着核心角色,它是一个接口(org.springframework.web.servlet.HandlerAdapter
),负责将接收到的HTTP请求委派给正确的处理器(Handler
,通常是实现了特定接口或标注了特定注解的控制器类Controller
中的方法)来处理,并且处理该处理器的执行结果,以便进一步的处理,比如视图渲染。
HandlerAdapter
的工作流程主要包括:
-
识别处理器:通过实现
supports(Object handler)
方法,判断当前HandlerAdapter
是否支持处理某个特定的Handler
。不同的HandlerAdapter
可能针对不同类型或结构的处理器,比如有的专为标注了@RequestMapping
的方法设计,有的可能处理实现了特定接口的类。 -
执行处理器:通过
handle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法,实际调用处理器来处理请求。这里涉及到参数解析、数据绑定、执行业务逻辑、异常处理等多个环节,并最终返回一个ModelAndView
对象(或其它结果类型,依据具体实现)。 -
资源最后修改时间:某些场景下,通过
getLastModified(HttpServletRequest request, Object handler)
方法获取处理器所处理资源的最后修改时间,用于HTTP缓存控制。
Spring MVC框架内预定义了几种HandlerAdapter
的实现,比如:
AnnotationHandlerMethodAdapter
:用于处理带有注解(如@RequestMapping
)的处理器方法。HttpRequestHandlerAdapter
:用于处理实现了HttpRequestHandler
接口的处理器。SimpleControllerHandlerAdapter
:用于处理实现了Controller
接口的简单控制器。
我们又听到了一个ModelAndView的概念,那么它是什么呢?
在现在前后端分离但是人不分离的年代,有些人似乎都已经忘了
ModelAndView了,但大致也得有个了解。ModelAndView
是Spring MVC框架中的一个核心类,主要用于封装处理完请求之后的逻辑视图名和模型数据。它是模型(Model)和视图(View)设计模式的一个具体实现,旨在简化Web应用的开发,帮助开发者组织和分离业务逻辑与展示逻辑。
ModelAndView的主要特点和用途包括:
-
封装数据(Model):
ModelAndView
对象的模型部分可以用来存储要传送给视图的数据。这些数据通常是在控制器方法中根据业务逻辑计算出来的结果。开发者可以直接向ModelAndView
对象添加属性(键值对),这些属性随后可用于填充视图。 -
指定视图(View):除了数据,
ModelAndView
还允许你指定处理请求后要呈现给用户的视图名称。这个视图名称可以是一个逻辑名称,Spring MVC框架会通过视图解析器(如InternalResourceViewResolver
)将其转换为实际的视图实现,比如一个JSP页面、Thymeleaf模板或其他视图技术的实现。 -
整合数据与视图:通过
ModelAndView
,你可以一次性完成模型数据的设置和视图的指定,使得控制器方法的逻辑更加集中和清晰。
java">@RequestMapping("/example")
public ModelAndView exampleMethod() {ModelAndView modelAndView = new ModelAndView();modelAndView.addObject("message", "Hello from Spring MVC!");modelAndView.setViewName("exampleView"); // 设定逻辑视图名return modelAndView;
}
handleRequest
方法返回一个新的ModelAndView
实例。它指定了视图名为"exampleView",并向模型中添加了一个名为"message"的属性,值为"Hello, Spring MVC!"。通过setViewName
方法设定了一个逻辑视图名,如"exampleView"。这个逻辑视图名并不会直接对应到一个具体的文件,而是需要视图解析器(View Resolver)来解析。当Spring处理这个请求时,它会根据视图名称找到对应的视图,并将模型数据传递给视图进行渲染。
Spring MVC框架配置了视图解析器,它的工作是将逻辑视图名转换为实际的视图实现。例如,如果配置了JSP视图解析器(或者是模板),它会查找对应于逻辑视图名的JSP文件。默认情况下,对于逻辑视图名"exampleView",它会查找"WEB-INF/views/exampleView.jsp"这样的路径。
当视图解析器找到对应的JSP文件后,Spring MVC会将之前在ModelAndView
中设置的所有模型数据传递给JSP。在JSP中,你可以通过EL表达式(${expression})或JSTL标签来访问这些数据。
java"><%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Example Page</title>
</head>
<body><h1>${message}</h1> <!-- 显示模型中的数据 -->
</body>
</html>
ModelAndView
作为一个载体,将控制器处理的数据(模型)和要展示的视图逻辑名结合起来,然后通过Spring MVC的视图解析机制,找到对应的模板文件(如JSP),最后模板引擎根据传入的数据动态生成最终的HTML页面返回给客户端。
在前后端分离的项目中,后端服务端点(Controller方法 通常用RESTful API)通常会返回如JSON或XML等数据格式,而不是像ModelAndView
那样直接绑定到视图。例如,你可能会看到后端Controller方法返回ResponseEntity
、自定义的DTO(数据传输对象)或者直接使用@ResponseBody
注解来直接输出JSON字符串给前端。这样,前端可以使用Ajax、Fetch API或者其他的HTTP客户端库来消费这些API,获取数据并自行渲染到页面上。