何为HandlerMapping、HandlerAdapter?

何为HandlerMapping、HandlerAdapter?

1.前言

现在回头来看,SpringMVC整体流程就已经很简单了。但是我们一直没有解释HandlerMapping、HandlerAdapter。

我们在前面写过这么一段代码:

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class HelloController{

//真实访问地址 : 项目名/hello
@RequestMapping("/hello")
public String hello(Model model){
//封装数据
model.addAttribute("msg","HelloAnnotationMVC!");
return "hello";//会被视图解析器处理
}
}

并且在XML里配置:

1
2
3
4
5
<!--映射注解的handlerMapping-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean>

<!--映射注解的handlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean>

2.HandlerMapping

查找-返回处理器HandlerMapping 负责将传入的 HTTP 请求映射到相应的处理器(即控制器)上。它的主要职责是DispatcherServlet根据Http请求,通过配置的 HandlerMapping 实例来查找处理该请求的处理器(控制器)。

上文中,RequestMappingHandlerMapping 会根据 /hello URL 将请求映射到 hello 方法。

2.1 主要的 HandlerMapping 实现类

  1. RequestMappingHandlerMapping

    • 描述:这是最常用的 HandlerMapping 实现,用于处理基于注解的请求映射,如 @RequestMapping@GetMapping@PostMapping 等。
    • 应用场景:处理所有使用 @RequestMapping 及其派生注解的控制器方法。
    • 典型配置:默认启用,无需额外配置。
  2. 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;
      }
  3. BeanNameUrlHandlerMapping

    • 描述:将请求 URL 映射到与 URL 名称匹配的处理器 Bean。处理器 Bean 的名称需要与请求路径匹配。
    • 应用场景:主要用于开发阶段或简单应用,处理器 Bean 名称直接与 URL 对应
    • 典型配置:默认启用,无需额外配置。例如,Bean 名为 /myHandler 的处理器会处理 /myHandler URL 请求。
  4. ControllerClassNameHandlerMapping

    • 描述:将请求 URL 映射到控制器类名。控制器类名决定了 URL 映射规则,例如 HomeController 会映射到 /home

    • 应用场景:适用于基于类名的约定来处理请求的场景。

    • 典型配置:需要手动启用。

      1
      2
      3
      4
      @Bean
      public ControllerClassNameHandlerMapping controllerClassNameHandlerMapping() {
      return new ControllerClassNameHandlerMapping();
      }
  5. 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"));
      }
  6. SimpleServletHandlerAdapter

    • 描述:用于处理标准的 Servlet。
    • 应用场景:在 Spring MVC 中需要直接使用标准 Servlet 时。
    • 典型配置:不常用,主要用于特定场景。

2.2 处理优先级

Spring MVC 在处理请求时,会根据一定的优先级顺序调用 HandlerMapping。默认情况下,Spring 会自动注册并配置以下 HandlerMapping 实现,按顺序尝试查找处理器:

  1. RequestMappingHandlerMapping
  2. SimpleUrlHandlerMapping
  3. BeanNameUrlHandlerMapping
  4. ControllerClassNameHandlerMapping

这个顺序可以通过设置 Order 属性来调整,确保请求能够按照预期的方式被正确映射到处理器。

3.HandlerAdapter

调用处理器:一旦 HandlerMapping 找到处理器,DispatcherServlet 会委托 HandlerAdapter 来实际调用处理器来处理请求。它的主要职责是根据处理器的类型调用适当的方法来处理请求,并返回一个 ModelAndView 对象。然后DispatcherServlet 使用返回的 ModelAndView 对象,通过 ViewResolver 来解析视图并渲染响应。

对于上面的控制器方法,RequestMappingHandlerAdapter 会调用 hello 方法,并返回视图名称 hello

3.1 主要的 HandlerAdapter 实现类

  1. RequestMappingHandlerAdapter

    • 描述:处理使用 @RequestMapping 注解的控制器方法。
    • 应用场景:这是最常用的 HandlerAdapter,用于处理基于注解的控制器方法,如 @RequestMapping@GetMapping@PostMapping 等。
    • 典型配置:默认启用,无需额外配置。
  2. HttpRequestHandlerAdapter

    • 描述:处理实现了 HttpRequestHandler 接口的处理器。
    • 应用场景:用于直接处理 HTTP 请求,适用于需要直接处理请求和响应的场景。
    • 典型配置:无需特殊配置,默认启用。
    1
    2
    3
    4
    5
    6
    public class MyHttpRequestHandler implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 处理请求逻辑
    }
    }
  3. 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 请求
    }
    }
  4. 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.完整的请求处理流程

以下是一个完整的请求处理流程:

  1. 接收请求DispatcherServlet 接收 HTTP 请求。
  2. 查找处理器:使用配置的 HandlerMapping 查找处理器。假设使用 RequestMappingHandlerMapping,它会根据请求 URL 查找带有 @RequestMapping 注解的方法。
  3. 返回处理器HandlerMapping 返回一个包含处理器和拦截器链的 HandlerExecutionChain 对象。
  4. 调用处理器DispatcherServlet 使用适当的 HandlerAdapter(如 RequestMappingHandlerAdapter)调用处理器方法。
  5. 处理请求:控制器方法处理请求,返回视图名称和模型数据。
  6. 解析视图DispatcherServlet 使用 ViewResolver 将视图名称解析为实际的视图对象。
  7. 渲染视图:视图对象渲染响应,将结果返回给客户端。


5.HandlerExecutionChain

HandlerExecutionChain 是 Spring MVC 中用于封装处理器(Handler)及其相关的拦截器链(Interceptor Chain)的类。当 HandlerMapping 确定了哪个处理器将处理请求时,它返回一个 HandlerExecutionChain 实例,这个实例不仅包含处理器,还包含一系列在处理器执行前后运行的拦截器。

5.1 主要职责

  • 封装处理器:包含用于处理当前请求的处理器对象。
  • 封装拦截器链:包含在处理请求之前或之后运行的一系列拦截器。

5.2 组件和结构

HandlerExecutionChain 主要由以下组件组成:

  1. Handler:实际处理请求的控制器对象。
  2. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取Handler
Object handler = getHandlerInternal(request);
... ...

//获取HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

//是否是CORS请求,是则对其处理
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}

//AbstractHandlerMapping--->getHandler(HttpServletRequest request)

前面DispatcherServlet请求流程我们讲过。HandlerMapping返回的是HandlerExecutionChain-执行链

我们看:Object handler = getHandlerInternal(request);返回处理器

5.3.1 getHandlerInternal(HttpServletRequest request)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {

//获取请求的查找路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
... ...

//查找匹配的处理器方法
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);

... ...
//返回处理器方法
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
}

//AbstractHandlerMethodMapping类---->getHandlerInternal(HttpServletRequest request)

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
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class HelloController{

//真实访问地址 : 项目名/hello
@RequestMapping("/hello")
public String hello(Model model){

//封装数据
model.addAttribute("msg","HelloAnnotationMVC!");

return "hello";//会被视图解析器处理
}
}

5.3.3 lookupHandlerMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//获取当前路径下所有的处理器方法
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

//获取match的path
if (directPathMatches != null)
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {

//获取一个处理器
Match bestMatch = matches.get(0);

... ...

handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}

}

//AbstractHandlerMethodMapping类---->lookupHandlerMethod

6.为什么通过lookupPath返回的是一个List集合处理器?

我们发现:

1
2
//获取当前路径下所有的处理器方法
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

前面不是说根据因为给定的URL路径返回一个处理器吗,为什么代码里会返回一个list集合?其实是因为:

  1. 多HTTP方法支持:同一个路径可以映射到不同的HTTP方法(如GET、POST、PUT、DELETE等),每个HTTP方法都有自己的处理器方法。
  2. 多条件匹配:除了HTTP方法外,还可以根据请求头、请求参数、内容类型等进一步细化匹配。
  3. 可扩展性:返回List使得映射更加灵活,可以方便地添加或移除处理器方法。

因为给定的URL路径可能对应多个处理器方法。这种设计允许处理不同的HTTP方法(如GET、POST、PUT、DELETE等)或其他请求条件(如请求参数、头信息等)在同一URL路径上的映射

6.1 原理解析

Spring MVC的RequestMappingHandlerMapping类负责管理URL路径到处理器方法的映射关系。每个URL路径可能有多个处理器方法映射,这些处理器方法可以处理相同路径但不同条件的请求。例如,同一个路径/api/users可以有GET和POST方法处理器。

6.2 查找过程

当一个请求到达时,Spring MVC会执行以下步骤:

  1. 获取路径:从请求中提取URL路径。
  2. 查找映射:使用getMappingsByUrl方法从MappingRegistry中获取对应路径的所有处理器方法。
  3. 匹配方法:遍历返回的处理器方法列表,根据请求的具体条件(如HTTP方法、请求参数等)找到合适的处理器方法

7.List集合存储的是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
this.mappingRegistry-->


class MappingRegistry {

private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

/**
* 返回所有映射和处理程序方法。线程不安全
*/
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}

//返回给定URL路径的匹配项。线程不安全
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
}
... ...
}

在Spring MVC中,MappingRegistry是一个内部类,用于维护请求映射与处理器方法的关系。urlLookupMappingRegistry中的一个重要成员变量,它用于快速查找特定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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/api")
public class ApiController {

@GetMapping("/users")
public ResponseEntity<List<User>> getUsers() {
// 处理请求
return ResponseEntity.ok(/* some user list */);
}

@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
// 创建用户
return ResponseEntity.status(HttpStatus.CREATED).body(/* created user */);
}
}

在注册处理器方法时,urlLookup会存储如下信息:

  • Key:/api/users
  • Value:
    • RequestMappingInfo实例(对应getUsers方法,HTTP方法为GET)
    • RequestMappingInfo实例(对应createUser方法,HTTP方法为POST)

7.2.2 MultiValueMap

我们一看:

1
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

明明是key- value形式,为什么是key- T(List)呢?其实是因为:

1
public interface MultiValueMap<K, V> extends Map<K, List<V>> {}

urlLookup使用MultiValueMap的原因是,同一个URL路径可能会对应多个RequestMappingInfo实例。例如,同一个路径可以支持不同的HTTP方法(GET、POST等),或者根据不同的请求参数、头信息等来区分处理器方法。MultiValueMap允许一个Key对应多个Value,非常适合这种情况。


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


何为HandlerMapping、HandlerAdapter?
https://nanchengjiumeng123.top/2024/02/19/framework/spring/Spring MVC/6.何为HandlerMapping、HandlerAdapter?/
作者
Yang Xin
发布于
2024年2月19日
许可协议