何为HandlerMapping、HandlerAdapter?
何为HandlerMapping、HandlerAdapter?
1.前言
现在回头来看,SpringMVC整体流程就已经很简单了。但是我们一直没有解释HandlerMapping、HandlerAdapter。
我们在前面写过这么一段代码:
1 |
|
并且在XML里配置:
1 |
|
2.HandlerMapping
查找-返回处理器:HandlerMapping
负责将传入的 HTTP
请求映射到相应的处理器(即控制器)上。它的主要职责是DispatcherServlet根据Http请求,通过配置的
HandlerMapping
实例来查找处理该请求的处理器(控制器)。
上文中,
RequestMappingHandlerMapping
会根据/hello
URL 将请求映射到hello
方法。
2.1 主要的 HandlerMapping 实现类
RequestMappingHandlerMapping
- 描述:这是最常用的
HandlerMapping
实现,用于处理基于注解的请求映射
,如@RequestMapping
、@GetMapping
、@PostMapping
等。 - 应用场景:处理所有使用
@RequestMapping
及其派生注解的控制器方法。 - 典型配置:默认启用,无需额外配置。
- 描述:这是最常用的
SimpleUrlHandlerMapping
描述:基于简单的 URL 路径映射,
将请求 URL 映射到特定的处理器 Bean
。应用场景:配置简单的 URL 到 Bean 的映射,适合于
静态资源或简单的请求处理
。典型配置
1
2
3
4
5
6
7
8@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
Properties mappings = new Properties();
mappings.put("/static/**", "staticResourceHandler");
mapping.setMappings(mappings);
return mapping;
}
BeanNameUrlHandlerMapping
- 描述:将请求 URL 映射到与 URL 名称匹配的处理器 Bean。处理器 Bean 的名称需要与请求路径匹配。
- 应用场景:主要用于开发阶段或简单应用,
处理器 Bean 名称直接与 URL 对应
- 典型配置:默认启用,无需额外配置。例如,Bean 名为
/myHandler
的处理器会处理/myHandler
URL 请求。
ControllerClassNameHandlerMapping
描述:将请求 URL 映射到控制器类名。控制器类名决定了 URL 映射规则,例如
HomeController
会映射到/home
。应用场景:适用于基于类名的约定来处理请求的场景。
典型配置:需要手动启用。
1
2
3
4@Bean
public ControllerClassNameHandlerMapping controllerClassNameHandlerMapping() {
return new ControllerClassNameHandlerMapping();
}
RouterFunctionMapping
描述:用于处理基于函数式编程风格的路由定义,通常与 Spring WebFlux 一起使用。
应用场景:在使用 Spring WebFlux 时,通过函数式编程定义路由。
典型配置:
1
2
3
4@Bean
public RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions.route(GET("/hello"), request -> ServerResponse.ok().bodyValue("Hello, World"));
}
SimpleServletHandlerAdapter
- 描述:用于处理标准的 Servlet。
- 应用场景:在 Spring MVC 中需要直接使用标准 Servlet 时。
- 典型配置:不常用,主要用于特定场景。
2.2 处理优先级
Spring MVC 在处理请求时,会根据一定的优先级顺序调用
HandlerMapping
。默认情况下,Spring 会自动注册并配置以下
HandlerMapping
实现,按顺序尝试查找处理器:
RequestMappingHandlerMapping
SimpleUrlHandlerMapping
BeanNameUrlHandlerMapping
ControllerClassNameHandlerMapping
这个顺序可以通过设置 Order
属性来调整,确保请求能够按照预期的方式被正确映射到处理器。
3.HandlerAdapter
调用处理器:一旦 HandlerMapping
找到处理器,DispatcherServlet
会委托
HandlerAdapter
来实际调用处理器来处理请求。它的主要职责是根据处理器的类型调用适当的方法来处理请求,并返回一个
ModelAndView
对象。然后DispatcherServlet
使用返回的 ModelAndView
对象,通过
ViewResolver
来解析视图并渲染响应。
对于上面的控制器方法,
RequestMappingHandlerAdapter
会调用hello
方法,并返回视图名称hello
。
3.1 主要的 HandlerAdapter 实现类
RequestMappingHandlerAdapter
- 描述:处理使用
@RequestMapping
注解的控制器方法。 - 应用场景:这是最常用的
HandlerAdapter
,用于处理基于注解的控制器方法,如@RequestMapping
、@GetMapping
、@PostMapping
等。 - 典型配置:默认启用,无需额外配置。
- 描述:处理使用
HttpRequestHandlerAdapter
- 描述:处理实现了
HttpRequestHandler
接口的处理器。 - 应用场景:用于直接处理 HTTP 请求,适用于需要直接处理请求和响应的场景。
- 典型配置:无需特殊配置,默认启用。
1
2
3
4
5
6public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 处理请求逻辑
}
}- 描述:处理实现了
SimpleServletHandlerAdapter
- 描述:处理标准的 Servlet。
- 应用场景:在 Spring MVC 中需要直接使用标准 Servlet 时。
- 典型配置:无需特殊配置,默认启用。
1
2
3
4
5
6
7@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理 GET 请求
}
}HandlerFunctionAdapter
- 描述:处理基于函数式编程风格的路由和处理器函数,通常与 Spring WebFlux 一起使用。
- 应用场景:在使用 Spring WebFlux 时,通过函数式编程定义路由和处理器。
- 典型配置:
1
2
3
4
5
6
7@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions.route(RequestPredicates.GET("/hello"), request -> ServerResponse.ok().bodyValue("Hello, World"));
}
}
3.2 总结
Spring 提供了多个 HandlerAdapter
实现,以支持不同类型的处理器:他们可以同时使用。DispatcherServlet会根据HandlerMapping
返回的HandlerExecutionChain
,获取对应的Handler,不同的Handler会有不同的控制器进行处理。
RequestMappingHandlerAdapter
:处理基于注解的控制器方法。HttpRequestHandlerAdapter
:处理实现HttpRequestHandler
接口的处理器。SimpleServletHandlerAdapter
:处理标准的 Servlet。HandlerFunctionAdapter
:处理函数式编程风格的处理器。
4.完整的请求处理流程
以下是一个完整的请求处理流程:
- 接收请求:
DispatcherServlet
接收 HTTP 请求。 - 查找处理器:使用配置的
HandlerMapping
查找处理器。假设使用RequestMappingHandlerMapping
,它会根据请求 URL 查找带有@RequestMapping
注解的方法。 - 返回处理器:
HandlerMapping
返回一个包含处理器和拦截器链的HandlerExecutionChain
对象。 - 调用处理器:
DispatcherServlet
使用适当的HandlerAdapter
(如RequestMappingHandlerAdapter
)调用处理器方法。 - 处理请求:控制器方法处理请求,返回视图名称和模型数据。
- 解析视图:
DispatcherServlet
使用ViewResolver
将视图名称解析为实际的视图对象。 - 渲染视图:视图对象渲染响应,将结果返回给客户端。
5.HandlerExecutionChain
HandlerExecutionChain
是 Spring MVC
中用于封装处理器(Handler)及其相关的拦截器链(Interceptor
Chain)的类。当 HandlerMapping
确定了哪个处理器将处理请求时,它返回一个
HandlerExecutionChain
实例,这个实例不仅包含处理器,还包含一系列在处理器执行前后运行的拦截器。
5.1 主要职责
- 封装处理器:包含用于处理当前请求的处理器对象。
- 封装拦截器链:包含在处理请求之前或之后运行的一系列拦截器。
5.2 组件和结构
HandlerExecutionChain
主要由以下组件组成:
- Handler:实际处理请求的控制器对象。
- HandlerInterceptor[]:一个或多个拦截器数组。这些拦截器会在处理器处理请求之前和之后执行相应的逻辑。
我们在前面说过,它的主要职责是DispatcherServlet根据Http请求,通过配置的
HandlerMapping
实例来查找处理该请求的处理器(控制器)。下面来看它是如何查找对应的Handler的。
在Spring框架中,HandlerMapping
并不是一个Map
,尽管它在某些方面具有类似Map
的功能。HandlerMapping
是Spring
MVC中的一个接口,用于将HTTP请求映射到处理器(Handler)。它的主要作用是根据请求的URL、HTTP方法等信息来确定应该调用哪个处理器来处理该请求。
我们用的最多的是RequestMappingHandlerMapping
。我们以它为例子:
HandlerMapping是最顶层的接口。它通过getHandler
方法返回HandlerExecutionChain
5.3 getHandler
HandlerMapping接口就一个方法:
该方法由AbstractHandlerMapping
子类实现。
1 |
|
前面DispatcherServlet请求流程我们讲过。HandlerMapping返回的是HandlerExecutionChain-执行链
我们看:Object handler = getHandlerInternal(request);
返回处理器
5.3.1 getHandlerInternal(HttpServletRequest request)
1 |
|
5.3.2 UrlPathHelper
UrlPathHelper
是Spring
MVC中用于处理和解析URL路径的一个实用类。它提供了各种方法来处理URL路径,例如获取请求的实际路径、移除上下文路径、移除Servlet路径等。
返回的路径并不是完整的URL路径,而是相对于应用上下文(context path)和Servlet映射路径的相对路径。这个相对路径通常是用于确定请求应该由哪个控制器或处理器处理的路径。
示例:假设应用部署在/myapp
,Servlet映射路径为/api/*
,客户端发送了一个请求到http://example.com/myapp/api/products/123
。则
- Context Path:
/myapp
- Servlet Path:
/api
- Request
URI:
/myapp/api/products/123
getLookupPathForRequest(request)
返回的查找路径会是:/products/123
。这个路径是相对于应用和Servlet映射路径的。
上文就是通过Request获取到路径。然后根据路径去匹配到controller中的Handler。其实并没有那么高大上,就是controller中的一个方法。
1 |
|
5.3.3 lookupHandlerMethod
1 |
|
6.为什么通过lookupPath返回的是一个List集合处理器?
我们发现:
1 |
|
前面不是说根据因为给定的URL路径返回一个处理器吗,为什么代码里会返回一个list集合?其实是因为:
- 多HTTP方法支持:同一个路径可以映射到不同的HTTP方法(如GET、POST、PUT、DELETE等),每个HTTP方法都有自己的处理器方法。
- 多条件匹配:除了HTTP方法外,还可以根据请求头、请求参数、内容类型等进一步细化匹配。
- 可扩展性:返回
List
使得映射更加灵活,可以方便地添加或移除处理器方法。
因为给定的URL路径可能对应多个处理器方法。这种设计允许处理不同的HTTP方法(如GET、POST、PUT、DELETE等)或其他请求条件(如请求参数、头信息等)在同一URL路径上的映射
。
6.1 原理解析
Spring
MVC的RequestMappingHandlerMapping
类负责管理URL路径到处理器方法的映射关系。每个URL路径可能有多个处理器方法映射,这些处理器方法可以处理相同路径但不同条件的请求。例如,同一个路径/api/users
可以有GET和POST方法处理器。
6.2 查找过程
当一个请求到达时,Spring MVC会执行以下步骤:
- 获取路径:从请求中提取URL路径。
- 查找映射:使用
getMappingsByUrl
方法从MappingRegistry
中获取对应路径的所有处理器方法。 - 匹配方法:遍历返回的处理器方法列表,根据请求的具体条件(如HTTP方法、请求参数等)找到合适的处理器方法
7.List集合存储的是什么?
1 |
|
在Spring
MVC中,MappingRegistry
是一个内部类,用于维护请求映射与处理器方法的关系。urlLookup
是MappingRegistry
中的一个重要成员变量,它用于快速查找特定URL路径对应的所有映射信息。
7.1urlLookup变量
urlLookup
是一个MultiValueMap
,用于将URL路径映射到对应的RequestMappingInfo
列表。存储的是一个URL路径到RequestMappingInfo
的映射关系,其中:
- Key:一个字符串,表示URL路径(通常是控制器方法上的
@RequestMapping
路径)。 - Value:一个列表,包含与该URL路径关联的所有
RequestMappingInfo
实例。
7.2 RequestMappingInfo
RequestMappingInfo
是Spring
MVC中用于封装请求映射信息的类。它包含了请求路径、HTTP方法、参数、头信息等条件,用于确定哪个处理器方法应该处理特定的HTTP请求。
7.2.1 示例
假设我们有如下控制器方法:
1 |
|
在注册处理器方法时,urlLookup
会存储如下信息:
- Key:
/api/users
- Value:
RequestMappingInfo
实例(对应getUsers
方法,HTTP方法为GET)RequestMappingInfo
实例(对应createUser
方法,HTTP方法为POST)
7.2.2 MultiValueMap
我们一看:
1 |
|
明明是key- value形式,为什么是key- T(List)呢?其实是因为:
1 |
|
urlLookup
使用MultiValueMap
的原因是,同一个URL路径可能会对应多个RequestMappingInfo
实例。例如,同一个路径可以支持不同的HTTP方法(GET、POST等),或者根据不同的请求参数、头信息等来区分处理器方法。MultiValueMap
允许一个Key对应多个Value,非常适合这种情况。
博客说明
文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!