HandlerMapping浅析

521 阅读4分钟

这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战

介绍

HandlerMapping处理器映射器,是SpringMvc的核心组件之一,用来根据请求的request信息查询对应的Handler,在web环境中,每个请求都需要一个对应的Handler来处理,所以当接收到一个请求,需要哪一个Handler来处理,HandlerMapping的作用就是找到处理的那个Handler

分析

1. HandlerMapping顶层接口

HandlerMapping类图.png

以上为HandlerMapping的类图,在HandlerMapping接口中有一个公共的抽象类AbstractHandlerMapping 所有的子孙都会继承它。该抽象类有两个子类AbstractHandlerMethodMapping表示基于方法的映射方法,这个方式就是我们日常使用的Controller的那种方式,AbstractUrlHandlerMapping表示根据url获取到对应的Handler

这里分析一下公共抽象父类AbstractHandlerMapping。这个抽象类采用了模板方法的设计模式,编写了HandlerMapping的核心逻辑getHandler()方法,获取具体的handler由子类继承并实现getHandlerInternal方法,在获取到具体的handler之后,添加该请求匹配的拦截器列表,再返回HandlerExecutionChain结构,里面包含了具体的handler和拦截器列表。

模板方法接口, 不同子类不同实现。

Object handler = getHandlerInternal(request);
if (handler == null) {
    handler = getDefaultHandler();
}

这里点一下初始化拦截器的方法。由于AbstractHandlerMapping间接继承于ApplicationContextAware接口,在bean初始化时会调用该接口进行applicationContext的赋值。而改方法中设置了一个模板方法接口,由具体子类实现。

进入到ApplicationObjectSupport方法中,bean初始化时会调用这个接口(BeanPostProcessor),主要作用是在bean初始化时赋值applicationContext上下文信息,这是个扩展点, 由子类自行实现。

protected void initApplicationContext(ApplicationContext context){
    initApplicationContext();
}

具体的实现子类AbstractHandlerMapping:主要作用是初始化拦截器。

  1. 空实现。子类可重写此方法以注册额外的拦截器
  2. 从上下文中查询拦截器并添加到拦截器列表中
  3. 初始化拦截器
protected void initApplicationContext() throws BeansException {
    // 1.
    extendInterceptors(this.interceptors);
    // 2. 
    detectMappedInterceptors(this.adaptedInterceptors);
    // 3. 
    initInterceptors();
}

根据以下代码可以得出一个简单的结论,拦截器的作用范围实际上是在Handler执行的前后,而过滤器Filter作用的范围应该是在请求进入到servlet的前和执行完servlet逻辑之后,套在springmvc中,Filter的作用范围是在请求进入到DispatcherServlet之前和执行完DispatcherServlet之后。

将自定义bean设置到适配拦截器中,bean需实现HandlerInterceptorWebRequestInterceptor

protected void initInterceptors() {
    if (!this.interceptors.isEmpty()) {
        for (int i = 0; i < this.interceptors.size(); i++) {
            Object interceptor = this.interceptors.get(i);
            this.adaptedInterceptors.add(adaptInterceptor(interceptor));
        }
    }
}

2. AbstractHandlerMethodMapping

AbstractHandlerMethodMapping这个就是RequestMappingHandlerMapping的抽象顶层父类,这种映射方式就是我们日常开发的那种,将标记有@Controller的类中的每个标记有@RequestMapping的方法都抽象为一个对应的HandlerMethod

简单罗列一下使用到的类:

  1. HandlerMethod:在初始化RequestMappingHandlerMapping会将spring容器中标记有@Controller的类中的@RequestMapping的方法都封装为HandlerMethod实体,包含了Handler对应的方法以及ControllerBean,并提供一些访问参数、方法返回值、获取注解等方法。
  2. RequestMappingInfo:在@Controller类上标记的@RequestMaping或者是在方法上标记的@RequestMapping最终都会加载到RequestMappingInfo实体中。
  3. MappingRegistration:主要记录RequestMappingInfoHandlerMethod关系
  4. MappingRegistry:一个注册表,它维护到处理程序方法的所有映射,公开执行查找的方法并提供并发访问。

AbstractHandlerMethodMapping分支的类图:

RequestMappingHandlerMapping类图.png

图中红框中的三个类,分别依次继承,我们日常开发所使用的就是RequestMappingHandlerMapping

2.1 初始化

​ 在springbean容器启动后,当初始化类RequestMappingHandlerMapping完成时,由于它间接实现了初始化的后置方法InitializingBean,所以会进入afterPropertiesSet方法,

这里主要先初始化RequestMappingInfo构建配置,再通过该方法调用到父类的afterPropertiesSet, 从而调用到父类的initHandlerMethods方法(这个方法是完成映射的解析工作) 后置初始化方法, 当这个Bean初始化完成之后调用, 获取容器中所有BeanDefinition中含有@RequestMapping或者是@ControllerBean信息

public void afterPropertiesSet() {
    initHandlerMethods();
}

从容器中获取所有Bean的名称,默认只查找SpringMVCIOC容器,不查找它的父容器,获取容器中所有Object.class类型的bean, 逐个遍历进行处理。然后 利用反射得到@ControllerBean中的Method并包装成HandlerMethod,最后放入注册表中。

protected void initHandlerMethods() {
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

这里的isHandler方法由子类(RequestMappingHandlerMapping)实现,判断是否拥有@Controller注解或@RequestMapping注解这个isHandler是个抽象接口, 由子类实现来控制是否进行接下来的注册。

protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    if (beanType != null && isHandler(beanType)) {
        detectHandlerMethods(beanName);
    }
}

利用反射得到Bean中的Method并包装成HandlerMethod对象,然后放入Map

  1. 根据字符串获取到对应的类型
  2. 若是代理对象获取目标类型
  3. 根据Method和它的@RequestMapping注解,创建RequestMappingInfo对象。这里的T就是 RequestMappingInfo,它封装了@RequestMapping信息。
protected void detectHandlerMethods(Object handler) {
    // 1.
    Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        // 2.
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        // 3.
                        return getMappingForMethod(method, userType);
                    }
                });
        methods.forEach((method, mapping) -> {
            // ..
        });
    }
}

收集要封装的接口信息, 键为method的引用, 值为RequestMethodInfo的引用

  1. specificHandlerType若为空表示其为代理类, 则取currentHandlerType
  2. 函数式接口: 遍历currentHandlerType的所有methods, 并执行第二个函数引用
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
    for (Class<?> currentHandlerType : handlerTypes) {
        // 1. 
        final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);

        // 2. 
        ReflectionUtils.doWithMethods(currentHandlerType, method -> {
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
            // ...
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }
    return methodMap;
}

这里使用了两次的函数式接口,不同函数式接口可能看到这回有点吃力。第一个函数式接口的作用其实就是将某个method上标记的@RequestMapping信息和这个method所在类上标记的@RequestMapping信息拼接起来后包装为RequestMappingInfo后返回。第二个函数式接口的作用是将第一个函数式接口处理之后返回的RequestMappingInfo信息暂存到某个变量中。

最后将解析出来的所有RequestMappingInfo信息逐个遍历,通过调用以下方法,添加到注册表中。

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

注册的核心逻辑:实例化一个HandlerMethod对象,然后校验是否已经存在这个handlerMethod,最后添加到mappingLookup查找器中.

2.2 查找

getHandlerInternal方法是由AbstractHandlerMapping抽象类定义的模板方法,具体细节由子类实现,用于获取对应的HandlerMethod先根据当前请求获取“查找路径”,再获取当前请求最佳匹配的处理方法(即Controller类的方法中)

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    this.mappingRegistry.acquireReadLock();
    try {
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
}

具体是通过该方法获取HandlerMethod,从MappingRegistry.urlLookup属性中,获取lookupPath对应的mapping集合,获取匹配的mapping后,添加到matches列表。如果没有匹配lookupPath的实例,则遍历所有的mapping,查找符合条件的mapping。 获取匹配条件的排序器,由抽象方法getMappingComparator方法获取,该方法由子类实现这个排序规则也是个模板方法, 由子类实现, 由子类控制当出现多个匹配的mapping通过比较器排序后, 选择第一个为最匹配的mapping

Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));