豆角茄子子

导航

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

问题已抛出,如标题。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中迅速拿到对应信息。

posted on 2018-12-12 23:13  豆角茄子子  阅读(274)  评论(0编辑  收藏  举报