likes
comments
collection
share

揭秘@Controller内部方法与URL绑定的全流程

作者站长头像
站长
· 阅读数 27

MVC的开发模式中,在控制层中,通常会用@RequestMappring注解来定义一个资源链接的请求url,然后编写该url所对应的特定处理逻辑。

进一步,为了@RequestMappring标注的类能被Spring容器管理,通常还会在相关类上使用@Controller注解,但用了这么长时间的@Controller,你是否了解过Spring框架内部是如何维护url方法之间绑定关系的? 不了解也没关系,今天我们便来扒一扒Spring框架对@Controller解析的全流程!

前言

对于一个Springboot而言,当其收到一个Http请求到后,在其内部会通过DispatcherServlet # doDispatch 来实现对请求相对分发、执行`。 其处理逻辑如下:

揭秘@Controller内部方法与URL绑定的全流程

总结来看,这一过程完成大致完成如下逻辑:

  1. 分发,即根据请求寻找对应的执行方法。 这个过程主要涉及到url处理器之间的匹配。这一过程中会涉及到的组件有:HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)等。
  2. 执行,反射执行寻找到的执行方法。 这一过程的本质就是通过反射机制来调用被@ReqeustMapping标注的方法

而在执行能顺利执行的前提则在于@Controller所标注的类能顺利注入容器,并实现url处理方法的绑定。接下来,我们便来看看Spring内部对于@Controller所标注的类究竟是通过何种方式来注入到Spring容器内部。

@Controller内部方法绑定全流

开始分析之前,我们先对SpringMVC中的HandlerMapping组件进行一个简单的回顾。SpringMVC中,HandlerMapping(处理器映射)的主要用途在于将传入的HTTP请求映射到相应的处理器方法上

具体而言,在SpringMVC中,DispatcherServlet需要处理分发很多请求,而每个请求通常对应一个特定的Handler来进行处理。而接收到一个请求后,具体使用哪个Handler来处理则需要通过HandlerMaping进行处理。换言之,对于一个SpringBoot应用而言,其主要在HandlerMapping组件中完成了url控制器的匹配工作。

因此,如果我们想探究@Controller的解析过程,HandlerMapping不失为一个很好的切入点。所以我们不妨深入到HandlerMapping的公共父类AbstractHandlerMethodMapping 来看看其内部是否有对@Controller注解的解析。其中,AbstractHandlerMethodMapping的类结构如下图所示

揭秘@Controller内部方法与URL绑定的全流程

可以看到对于AbstractHandlerMethodMapping而言,其实现了一个名为InitializingBean的接口。InitializingBean接口,通常用于在bean实例化之后,进行一些初始化工作。 如果一个类实现该接口,那么当其实例化完成后,Spring容器自动调用afterPropertiesSet()方法。从而为bean提供一种在bean被完全初始化后、但在其实际使用之前执行定制初始化逻辑的机会。

进一步,AbstractHandlerMethodMapping内部 afterPropertiesSet()的调用逻辑如下所示:

揭秘@Controller内部方法与URL绑定的全流程

不难看出,afterPropertiesSet的处理逻辑全部委托于initHandlerMethods,而initHandlerMethods方法的核心在于 getCandidateBeanNamesprocessCandidateBean

protected void initHandlerMethods() {
    // <1> 遍历 Bean ,逐个处理
    for (String beanName : getCandidateBeanNames()) {
        // 排除目标代理类,AOP 相关,可查看注释
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // <2> 处理 Bean
            processCandidateBean(beanName);
        }
    }

    //<3> 空方法,暂无具体的实现,可用于后续扩展()
    handlerMethodsInitialized(getHandlerMethods());

}

我们首先来看getCandidateBeanNames内部的逻辑:

protected String[] getCandidateBeanNames() {
    // 获取上下文中所有的 Bean 的名称
    return (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class)
            : obtainApplicationContext().getBeanNamesForType(Object.class));
}

可以看到,对于getCandidateBeanNames方法而言, 其会根据detectHandlerMethodsInAncestorContexts的值来选择不同的方式获取bean信息。具体来看,如果detectHandlerMethodsInAncestorContextstrue则通过BeanFactoryUtils.beanNamesForTypeIncludingAncestors来获取包括祖先上下文中的所有bean的名称;而当其为false时,则直接通过obtainApplicationContext().getBeanNamesForType(Object.class)方法获取当前上下文中所有bean的名称。返回值是一个字符串数组,包含了所有候选bean的名称。

(Ps:在AbstractHandlerMethodMapping内部,detectHandlerMethodsInAncestorContexts变量默认置为false,因此其默认只会获取当前容器的bean信息,而不会主动加载父容器的bean信息。)

总结来看,getCandidateBeanNames 会获取当前容器中所有的bean 的名称集合。然后,在逐个遍历交由processCandidateBean处理。* 而processCandidateBean逻辑如下:

protected void processCandidateBean(String beanName) {
    // <1> 获得 Bean 对应的 Class 对象
    Class<?> beanType = null;
    // ... 省略相关异常捕获代码
    beanType = obtainApplicationContext().getType(beanName);
     // ... 省略相关异常捕获代码
    // <2> 判断 Bean 是否有 @Controller 或者 @RequestMapping 注解
    if (beanType != null && isHandler(beanType)) {
        // <3> 扫描处理器方法
        detectHandlerMethods(beanName);
    }
}

对于processCandidateBean而言,其大致逻辑又可以分为如下两点:

  1. 尝试获取指定beanNamebean信息
  2. 调用 isHandler方法对对获取到的bean判断,如果可以处理则调用 detectHandlerMethods方法.

(Ps: isHandler在子类 RequestMappingHandlerMapping中进行了实现,其主要用于判断类内部是否有 @Controller 或者 @RequestMapping 注解.)

detectHandlerMethods相关逻辑

protected void detectHandlerMethods(Object handler) {
    // <1> 获得 Bean 对应的 Class 对象
    Class<?> handlerType = (handler instanceof String 
    ? obtainApplicationContext().getType((String) handler)
    : handler.getClass());
    
    if (handlerType != null) {
        // <2> 获得真实的 Class信息
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // <3> 获得匹配的方法和对应的 Mapping 对象
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                     // 创建该方法对应的 Mapping 对象,
                     return getMappingForMethod(method, userType);
                });
      
      
        // <4> 遍历方法,逐个注册 HandlerMethod
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

其中<3> 处的 getMappingForMethod为一个抽象方法,其在RequestMappingHandlerMapping中有具体实现。其在RequestMappingHandlerMapping中会为标有@RequestMapping注解的方法创建 RequestMappingInfo对象,以而实现控制器类和方法上的 @RequestMapping 注解所标注url与方法的绑定。

进一步<4> 遍历 methods方法与@RequestMapping映射集合,然后调用registerHandlerMethod ,逐个注册对应的 HandlerMethod 对象,其逻辑如下:

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

MappingRegistrySpring MVC内部用来管理和存储请求映射的一个组件。它负责将HTTP请求映射到对应的处理方法上。具体来看,MappingRegistry主要用于管理HandlerMethod和其对应的URL路径映射。并提供了注册、查找和移除映射的方法,其主要组成部分如下所示:

MappingRegistry内部成员变量

class MappingRegistry {
    /**
     * 注册表
     *
     * Key: Mapping
     * Value:{@link MappingRegistration}(Mapping + HandlerMethod)
     */
    private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
   
}
  • registry为注册表。其存存储了所有的URL路径和HandlerMethod的映射关系。其Value所使用的MappingRegistration 对象其实是(Mapping + HandlerMethod)的组合。具体存储内容如下所示:

揭秘@Controller内部方法与URL绑定的全流程

(Ps:由于篇幅原因,有关MappingRegistry中的注册逻辑此处暂时省略)

总结

总结里看,在SpringMVC内部,当加载RequestMappingHandlerMapping初始化完成后,会执行其父类的afterPropertiesSet方法,其会扫描有@Controller或者@RequestMapping注解的类下面的方法。

进一步,如果方法上面有@RequestMapping注解,则会为该方法创建对应的RequestMappingInfo对象。并将所有的 RequestMappingInfo对象和 Method 以及方法所在类,往 MappingRegistry 进行注册,会生成 HandlerMethod 处理器(Method 所有信息)对象。 并最终通过MappingRegistry 负责管理和存储请求映射,将 HTTP 请求映射到对应的处理方法上。其主要通过注册表 registry 存储所有的 URL 路径和 HandlerMethod 的映射关系。

这样一来当 Spring MVCDispatcherServlet处理请求的时候,获取到对应的 HandlerMethod 处理器,就可以通过反射的方式来执行对应的方法。

转载自:https://juejin.cn/post/7390686791777173531
评论
请登录