美文网首页Java 杂谈
Spring MVC为何能准确的找到一个http请求对应cont

Spring MVC为何能准确的找到一个http请求对应cont

作者: AGI大模型阿南 | 来源:发表于2018-12-17 21:11 被阅读1次

    问题已抛出,如标题。Spring版本基于5.1.3。

    宏观而言,我们需要给一个类加注解@Controller,然后定义一个加了注解@RequestMapping的方法,这样Spring容器就可以准确找到对应的方法了。

    其实要回答这个问题,可以从Spring源码去一步步分析。

    在Spring MVC里,有一专门处理请求映射的接口HandlerMapping,查看此接口的实现类:

    其中,RequestMappingHandlerMapping是我们需要关注的。我们首先看一下RequestMappingHandlerMapping的抽象父类AbstractHandlerMethodMapping,省略其他方法,先关注这两个相关核心方法:

    public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {

        ...

        // Handler method detection

    /**

    * Detects handler methods at initialization.

    */

    @Override

    public void afterPropertiesSet() {

    initHandlerMethods();

    // Total includes detected mappings + explicit registrations via registerMapping..

    int total = this.getHandlerMethods().size();

    if ((logger.isTraceEnabled() && total == 0) || (logger.isDebugEnabled() && total > 0) ) {

    logger.debug(total + " mappings in " + formatMappingName());

    }

    }

    /**

    * Scan beans in the ApplicationContext, detect and register handler methods.

    * @see #isHandler(Class)

    * @see #getMappingForMethod(Method, Class)

    * @see #handlerMethodsInitialized(Map)

    */

    protected void initHandlerMethods() {

    String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);

    for (String beanName : beanNames) {

    if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {

    Class<?> beanType = null;

    try {

    beanType = obtainApplicationContext().getType(beanName);

    }

    catch (Throwable ex) {

    // An unresolvable bean type, probably from a lazy bean - let's ignore it.

    if (logger.isTraceEnabled()) {

    logger.trace("Could not resolve type for bean '" + beanName + "'", ex);

    }

    }

    if (beanType != null && isHandler(beanType)) {

    detectHandlerMethods(beanName);

    }

    }

    }

    handlerMethodsInitialized(getHandlerMethods());

    }

        ...

    }

    可以看到AbstractHandlerMethodMapping实现了InitializingBean接口,在Spring初始化bean的时候,如果bean实现了InitializingBean接口,会自动调用afterPropertiesSet方法。initHandlerMethods方法,顾名思义是初始化HandlerMethods,查看它是被afterPropertiesSet方法调用,这个方法代表bean在容器中被初始化的时候,会去执行initHandlerMethods方法。

    那initHandlerMethods方法具体做了什么事情?大概看一下方法内部的业务,首先拿到容器里的所有bean名称放进数组beanNames中;然后遍历数组,拿到每一个bean的类型beanType,对每一个beanType做了一个判断isHandler(beanType),查看此方法的实现,即进入RequestMappingHandlerMapping中:

    public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping

    implements EmbeddedValueResolverAware {

        ...

        /**

    * {@inheritDoc}

    * Expects a handler to have a type-level @{@link Controller} annotation.

    */

    @Override

    protected boolean isHandler(Class<?> beanType) {

    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||

    AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));

    }

        ...

    }

    ·很容易看出来,是去判断该类是否加了注解@Controller或@RequestMapping。点进去查看AnnotatedElementUtils.hasAnnotation方法的实现:

    public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {

    // Shortcut: directly present on the element, with no processing needed?

    if (element.isAnnotationPresent(annotationType)) {

    return true;

    }

    return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));

    }

    所以我们判断一个类上是否加了什么注解,可以这么写:

    public class Test {

    public static void main(String[] args) {

    Test.class.isAnnotationPresent(Override.class);

    }

    }

    判断完类上加了注解@Controller或者@RequestMapping后,看到继续执行了detectHandlerMethods方法:

    /**

    * Look for handler methods in a handler.

    * @param handler the bean name of a handler or a handler instance

    */

    protected void detectHandlerMethods(final Object handler) {

    Class<?> handlerType = (handler instanceof String ?

    obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {

    final Class<?> userType = ClassUtils.getUserClass(handlerType);

    Map<Method, T> methods = MethodIntrospector.selectMethods(userType,

    (MethodIntrospector.MetadataLookup<T>) method -> getMappingForMethod(method, userType));

    if (logger.isTraceEnabled()) {

    logger.trace(formatMappings(userType, methods));

    }

    methods.forEach((key, mapping) -> {

    Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);

    registerHandlerMethod(handler, invocableMethod, mapping);

    });

    }

    }

    /**

    * Register a handler method and its unique mapping. Invoked at startup for

    * each detected handler method.

    * @param handler the bean name of the handler or the handler instance

    * @param method the method to register

    * @param mapping the mapping conditions associated with the handler method

    * @throws IllegalStateException if another method was already registered

    * under the same mapping

    */

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {

    this.mappingRegistry.register(mapping, handler, method);

    }

    这个方法筛选出了类中加了注解@RequestMapping的方法,放进Map集合methods中,紧接着去遍历每一个method,进入registerHandlerMethod方法,注册到映射注册表mappingRegistry中,其中就是一些Map。

    至此,我们就明白了在Spring初始化bean的时候,就把所有的加了@Controller/@RequestMapping的类里面的加了@RequestMapping的方法放进了map中,当http请求来临时,直接去map中迅速拿到对应信息。

    相关文章

      网友评论

        本文标题:Spring MVC为何能准确的找到一个http请求对应cont

        本文链接:https://www.haomeiwen.com/subject/znxhkqtx.html