HandlerMapping表示的是一个URL与一个Handler(可以简单的理解为Controller中有@RequestMapping注解的方法)之间的映射关系。

HandlerMapping的继承关系图如下:

【springmvc】九大组件之HandlerMapping_spring

我们主要关注这3个HandlerMapping:

  1. RequestMappingHandlerMapping
  2. BeanNameUrlHandlerMapping
  3. SimpleUrlHandlerMapping

RequestMappingHandlerMapping

RequestMappingHandlerMapping表示的是一个URL与一个HandlerMethod(由Controller中有@RequestMapping注解的方法封装而成)之间的映射关系。

何时注入的RequestMappingHandlerMapping?

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#requestMappingHandlerMapping

@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

	RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
	mapping.setOrder(0);
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setContentNegotiationManager(contentNegotiationManager);
	mapping.setCorsConfigurations(getCorsConfigurations());

	PathMatchConfigurer configurer = getPathMatchConfigurer();

	Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
	if (useSuffixPatternMatch != null) {
		mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
	}
	Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
	if (useRegisteredSuffixPatternMatch != null) {
		mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
	}
	Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
	if (useTrailingSlashMatch != null) {
		mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
	}

	UrlPathHelper pathHelper = configurer.getUrlPathHelper();
	if (pathHelper != null) {
		mapping.setUrlPathHelper(pathHelper);
	}
	PathMatcher pathMatcher = configurer.getPathMatcher();
	if (pathMatcher != null) {
		mapping.setPathMatcher(pathMatcher);
	}
	Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
	if (pathPrefixes != null) {
		mapping.setPathPrefixes(pathPrefixes);
	}

	return mapping;
}

何时建立URL与HandlerMethod之间的映射关系

RequestMappingHandlerMapping实现了InitializingBean接口,这样当RequestMappingHandlerMapping实例化并初始化后会调用InitializingBean.afterPropertiesSet()方法来完成URL与HandlerMethod之间映射关系的建立。

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet

public void afterPropertiesSet() {
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(useSuffixPatternMatch());
	this.config.setTrailingSlashMatch(useTrailingSlashMatch());
	this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
	this.config.setContentNegotiationManager(getContentNegotiationManager());

	// 调用父类的afterPropertiesSet()
	super.afterPropertiesSet();
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet

public void afterPropertiesSet() {
	initHandlerMethods();
}

protected void initHandlerMethods() {
	// getCandidateBeanNames()获取Spring MVC容器中所有的Bean
	for (String beanName : getCandidateBeanNames()) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			processCandidateBean(beanName);
 // 处理候选的Bean
		}
	}
	handlerMethodsInitialized(getHandlerMethods()); // 打印日志而已,记录建立了那些映射关系
}

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		// 获取bean的类型
		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);
		}
	}
	/**
	 * @see RequestMappingHandlerMapping#isHandler(java.lang.Class)
	 */
	// 判断类上面是否有@Controller或者@RequestMapping注解
	if (beanType != null && isHandler(beanType)) {
		detectHandlerMethods(beanName);
	}
}

protected void detectHandlerMethods(Object handler) {
	// 又获取Bean的类型
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		// 遍历所有的方法,找到带有@RequestMapping注解的方法
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						/**
						 * RequestMappingHandlerMapping返回的是RequestMappingInfo
						 * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)
						 */
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		
		// 遍历所有的带有@RequestMapping注解的方法,建立url与HandlerMethod之间的映射关系
		methods.forEach((method, mapping) -> {
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			// 建立映射
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	// 将方法上面@RequestMapping注解的信息封装为RequestMappingInfo
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		// 将类上面@RequestMapping注解的信息封装为RequestMappingInfo
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			// 将类和方法上面的@RequestMapping注解信息进行组合
			// 例如类上面url为/hello,方法上面url为/index,组合后url变为/hello/index
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
		}
	}
	return info;
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod

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

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register

public void register(T mapping, Object handler, Method method) {
	// Assert that the handler method is not a suspending one.
	if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
		Class<?>[] parameterTypes = method.getParameterTypes();
		if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
			throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
		}
	}
	this.readWriteLock.writeLock().lock();
	try {
		// handler只是beanName,创建HandlerMethod
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		// 判断这个mapping是不是已经存在了,存在就会抛出异常
		validateMethodMapping(handlerMethod, mapping);
		// RequestMappingInfo->HandlerMethod
		this.mappingLookup.put(mapping, handlerMethod);

		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			// url->RequestMappingInfo
			this.urlLookup.add(url, mapping);
		}

		String name = null;
		if (getNamingStrategy() != null) {
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}

		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			this.corsLookup.put(handlerMethod, corsConfig);
		}

		this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		this.readWriteLock.writeLock().unlock();
	}
}

到这里url与handlermethod之间的映射关系就建立完成了,实际上用两个map来建立映射关系:

  1. mappingLookup:key为RequestMappingInfo,value为HandlerMethod(封装了Controller和其中的目标方法)。
  2. urlLookup:key为url,value为RequestMappingInfo。

根据URL获得HandlerMethod

org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	/**
		 * @see RequestMappingInfoHandlerMapping#getHandlerInternal(javax.servlet.http.HttpServletRequest)
		 */
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}

	if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
		// 处理跨域
		CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		config = (config != null ? config.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}

	return executionChain;
}

org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	try {
		// 调用父类的getHandlerInternal
		return super.getHandlerInternal(request);
	}
	finally {
		ProducesRequestCondition.clearMediaTypesAttribute(request);
	}
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	// 丛请求中获得url
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	request.setAttribute(LOOKUP_PATH, lookupPath);
	this.mappingRegistry.acquireReadLock();
	try {
		// 根据url获取handlerMethod(handler)
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock();
	}
}

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	// 从urlLookup这个map中根据url获取RequestMappingInfo
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}

	if (!matches.isEmpty()) {
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			// 一个url可能对应多个RequestMappingInfo,如rest风格的请求中,get/post/delete url都是一个,只不过method不一样
			// 所以这里还需要根据method、param、header等条件对RequestMappingInfo进行匹配
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			bestMatch = matches.get(0);
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException(
					"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
	else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping会根据Bean的名字映射到具体的Controller中。

简单使用

需要实现Controller接口,并注入到spring mvc中,注意bean的名字一定要以/开头。:

package com.morris.spring.mvc.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@org.springframework.stereotype.Controller("/student")
public class StudentController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("com.morris.spring.mvc.controller.StudentController.handleRequest");
        ModelAndView modelAndView = new ModelAndView("success");
        return modelAndView;
    }
}

浏览器访问http://localhost:8080/student。

何时注入的BeanNameUrlHandlerMapping?

BeanNameUrlHandlerMapping也是在WebMvcConfigurationSupport中被注入:

@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
		@Qualifier("mvcConversionService") FormattingConversionService conversionService,
		@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {

	BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
	mapping.setOrder(2);
	mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
	mapping.setCorsConfigurations(getCorsConfigurations());
	return mapping;
}

何时建立URL与Controller之间的映射关系

BeanNameUrlHandlerMapping的父类ApplicationObjectSupport实现了ApplicationContextAware接口的setApplicationContext()方法,BeanNameUrlHandlerMapping实例化过程中就会调用setApplicationContext()方法,而setApplicationContext()方法中调用了initApplicationContext()方法。

org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping#initApplicationContext

public void initApplicationContext() throws ApplicationContextException {
	super.initApplicationContext();
	detectHandlers();
}

protected void detectHandlers() throws BeansException {
	ApplicationContext applicationContext = obtainApplicationContext();
	// 获取spring mvc容器中所有的beanName
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
						  BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
						  applicationContext.getBeanNamesForType(Object.class));

	// Take any bean name that we can determine URLs for.
	// 循环所有的bean
	for (String beanName : beanNames) {
		/**
			 * @see BeanNameUrlHandlerMapping#determineUrlsForHandler(java.lang.String)
			 */
		// 拿到handler对应的url
		String[] urls = determineUrlsForHandler(beanName);
		if (!ObjectUtils.isEmpty(urls)) {
			// URL paths found: Let's consider it a handler.
			// 建立url与handler的映射关系
			registerHandler(urls, beanName);
		}
	}

	if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
		logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
	}
}

org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#registerHandler

protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
	Assert.notNull(urlPaths, "URL path array must not be null");
	for (String urlPath : urlPaths) {
		// 建立url与handler的映射关系
		registerHandler(urlPath, beanName);
	}
}

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
	Assert.notNull(urlPath, "URL path must not be null");
	Assert.notNull(handler, "Handler object must not be null");
	Object resolvedHandler = handler;

	// Eagerly resolve handler if referencing singleton via name.
	if (!this.lazyInitHandlers && handler instanceof String) {
		String handlerName = (String) handler;
		ApplicationContext applicationContext = obtainApplicationContext();
		if (applicationContext.isSingleton(handlerName)) {
			// 根据beanName从容器中获得Controller的实现类
			resolvedHandler = applicationContext.getBean(handlerName);
		}
	}

	// 判断handlerMap是否已存在
	Object mappedHandler = this.handlerMap.get(urlPath);
	if (mappedHandler != null) {
		if (mappedHandler != resolvedHandler) {
			throw new IllegalStateException(
				"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
				"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
		}
	}
	else {
		if (urlPath.equals("/")) {
			if (logger.isTraceEnabled()) {
				logger.trace("Root mapping to " + getHandlerDescription(handler));
			}
			setRootHandler(resolvedHandler);
		}
		else if (urlPath.equals("/*")) {
			if (logger.isTraceEnabled()) {
				logger.trace("Default mapping to " + getHandlerDescription(handler));
			}
			setDefaultHandler(resolvedHandler);
		}
		else {
			// 建立url与handler的映射关系
			this.handlerMap.put(urlPath, resolvedHandler);
			if (logger.isTraceEnabled()) {
				logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
			}
		}
	}
}

最终映射关系会存到handlerMap中,key为url,value为controller对象。

根据URL获得Controller

org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	// 获得url
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	request.setAttribute(LOOKUP_PATH, lookupPath);
	// 根据url从handlerMap中获得handler
	Object handler = lookupHandler(lookupPath, request);
	if (handler == null) {
		// We need to care for the default handler directly, since we need to
		// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
		Object rawHandler = null;
		if (StringUtils.matchesCharacter(lookupPath, '/')) {
			rawHandler = getRootHandler();
		}
		if (rawHandler == null) {
			rawHandler = getDefaultHandler();
		}
		if (rawHandler != null) {
			// Bean name or resolved handler?
			if (rawHandler instanceof String) {
				String handlerName = (String) rawHandler;
				rawHandler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(rawHandler, request);
			handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
		}
	}
	return handler;
}

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
	// Direct match?
	// 直接匹配
	Object handler = this.handlerMap.get(urlPath);
	if (handler != null) {
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		return buildPathExposingHandler(handler, urlPath, urlPath, null);
	}

	// Pattern match?
	// key可能是正则,正则匹配
	List<String> matchingPatterns = new ArrayList<>();
	for (String registeredPattern : this.handlerMap.keySet()) {
		if (getPathMatcher().match(registeredPattern, urlPath)) {
			matchingPatterns.add(registeredPattern);
		}
		else if (useTrailingSlashMatch()) {
			if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
				matchingPatterns.add(registeredPattern + "/");
			}
		}
	}

	String bestMatch = null;
	// 正则可能匹配到多个
	Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
	if (!matchingPatterns.isEmpty()) {
		matchingPatterns.sort(patternComparator);
		if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
			logger.trace("Matching patterns " + matchingPatterns);
		}
		bestMatch = matchingPatterns.get(0);
	}
	if (bestMatch != null) {
		handler = this.handlerMap.get(bestMatch);
		if (handler == null) {
			if (bestMatch.endsWith("/")) {
				handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
			}
			if (handler == null) {
				throw new IllegalStateException(
					"Could not find handler for best pattern match [" + bestMatch + "]");
			}
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		validateHandler(handler, request);
		String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);

		// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
		// for all of them
		Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
		for (String matchingPattern : matchingPatterns) {
			if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
				Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
				Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
				uriTemplateVariables.putAll(decodedVars);
			}
		}
		if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
			logger.trace("URI variables " + uriTemplateVariables);
		}
		return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
	}

	// No handler found...
	return null;
}

这里只需要根据url从handlerMap拿到对应的Controller对象即可。

SimpleUrlHandlerMapping

简单使用

Controller需要实现HttpRequestHandler接口,并以普通Bean的方式注入到Spring MVC容器中:

package com.morris.spring.mvc.controller;

import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class AreaController implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("======AreaController");
        PrintWriter writer = response.getWriter();
        writer.println("<h1>Simple URl</h1>");
        writer.flush();
        writer.close();
    }
}

然后再注入SimpleUrlHandlerMapping:

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
	SimpleUrlHandlerMapping suhm = new SimpleUrlHandlerMapping();
	Properties properties = new Properties();
	properties.put("area","areaController");
	suhm.setOrder(Ordered.LOWEST_PRECEDENCE - 1); // 注意优先级要比DefaultServletHandling高一点
	suhm.setMappings(properties);
	return suhm;
}

浏览器输入http://localhost:8080/area。

何时建立URL与HttpRequestHandler之间的映射关系

其实在手动注入SimpleUrlHandlerMapping时就已经指定了url与HttpRequestHandler之间的映射关系,例如上面是area和areaController,spring mvc在启动过程中会根据areaController这个beanName获取对应的实例,建立url与实例之间的映射关系。

参考 org.springframework.web.reactive.handler.SimpleUrlHandlerMapping#initApplicationContext

根据URL获得HttpRequestHandler

与BeanNameUrlHandlerMapping的逻辑一致。

参考 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#getHandlerInternal

注入默认的DefaultServletHttpRequestHandler

可以通过下面的代码开启默认Servlet:

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
	configurer.enable();
}

那么Spring MVC会注入了一个默认的SimpleUrlHandlerMapping来处理所有最后无法处理的URL:

org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer#enable()

public void enable() {
	enable(null);
}

public void enable(@Nullable String defaultServletName) {
	this.handler = new DefaultServletHttpRequestHandler();
	if (defaultServletName != null) {
		this.handler.setDefaultServletName(defaultServletName);
	}
	this.handler.setServletContext(this.servletContext);
}

@Nullable
protected SimpleUrlHandlerMapping buildHandlerMapping() {
	if (this.handler == null) {
		return null;
	}
	 // 最低的优先级
	return new SimpleUrlHandlerMapping(Collections.singletonMap("/**", this.handler),
			Ordered.LOWEST_PRECEDENCE);

}

DefaultServletHttpRequestHandler会将请求转发给Tomcat容器中的org.apache.catalina.servlets.DefaultServlet来处理:

org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#handleRequest

public void handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	Assert.state(this.servletContext != null, "No ServletContext set");
	// 获取default Servlet,这个Servlet是容器提供的,并不是Spring的
	RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
	if (rd == null) {
		throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
				this.defaultServletName + "'");
	}
	rd.forward(request, response);
}

类似于以前在web.xml中如下配置:

<!--  静态文件处理-->
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
	<servlet-name>default</servlet-name>
	<url-pattern>*.js</url-pattern>
</servlet-mapping>

这里只配置了servlet-mapping标签,并没有配置servlet标签,这个servlet也是使用的Tomcat内置的DefaultServlet。