Spring MVC为何能准确的找到一个http请求对应controller的某个方法进行处理

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

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

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

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

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

public abstract class AbstractHandlerMethodMapping 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 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 methods = MethodIntrospector.selectMethods(userType,

(MethodIntrospector.MetadataLookup) 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请求对应controller的某个方法进行处理)