Previously, I shared with my friends a general initialization process and request processing process of SpringMVC. In the request processing process, nine components are involved, which are as follows:

  1. HandlerMapping
  2. HandlerAdapter
  3. HandlerExceptionResolver
  4. ViewResolver
  5. RequestToViewNameTranslator
  6. LocaleResolver
  7. ThemeResolver
  8. MultipartResolver
  9. FlashMapManager

These components believe that partners are more or less involved in daily development, if you are unfamiliar with these components, you can reply SSM in the public account background, free access to songko’s introductory video tutorial.

In the next few articles, Songo would like to make an in-depth analysis of the nine components, from usage to source code, one by one. Today, we will take a look at the first HandlerMapping among the nine components.

1. An overview

HandlerMapping is called a processor mapper. It finds the corresponding Handler and Interceptor based on the current request, encapsulates it into a HandlerExecutionChain object, and returns it. Let’s look at the HandlerMapping interface:

public interface HandlerMapping {
	String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
	@Deprecated
	String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
	String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
	String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
	String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
	String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
	String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
	String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
	default boolean usesPathPatterns(a) {
		return false;
	}
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
Copy the code

As you can see, in addition to a bunch of declared constants, there is really only one method that needs to be implemented, getHandler, whose return value is what we know as HandlerExecutionChain.

The inheritance relationship of HandlerMapping is as follows:

Although this inheritance relationship looks a little round, in fact, there are two categories under careful observation:

  • AbstractHandlerMethodMapping
  • AbstractUrlHandlerMapping

Everything else is just a bunch of secondary interfaces.

AbstractHandlerMethodMapping system is carried out according to the method name match, and AbstractUrlHandlerMapping system is conducted according to the URL path matching, These two classes have a common subclass AbstractHandlerMapping. Next, we will analyze these three key classes in detail.

2.AbstractHandlerMapping

Abstract AbstractHandlerMapping implements the HandlerMapping interface. Matching by URL or method name is implemented by inheriting AbstractHandlerMapping. So what AbstractHandlerMapping does is something common, and subclasses are supposed to do something specific. This is a typical template method.

AbstractHandlerMapping indirectly inherits ApplicationObjectSupport and rewrites the initApplicationContext method (which is also a template method). This is an AbstractHandlerMapping entry method for initializing AbstractHandlerMapping.

@Override
protected void initApplicationContext(a) throws BeansException {
	extendInterceptors(this.interceptors);
	detectMappedInterceptors(this.adaptedInterceptors);
	initInterceptors();
}
Copy the code

All three methods are associated with interceptors.

extendInterceptors

protected void extendInterceptors(List<Object> interceptors) {}Copy the code

ExtendInterceptors is a template method that can be implemented in a subclass. After a subclass implements the method, interceptors can be added, removed, or modified, but in the SpringMVC implementation, this method is not implemented in a subclass.

detectMappedInterceptors

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
			obtainApplicationContext(), MappedInterceptor.class, true.false).values());
}
Copy the code

The detectMappedInterceptors method looks for all MappedInterceptor beans from the SpringMVC container as well as the Spring container. Find it and add it to the mappedInterceptors property (which is the global adaptedInterceptors property). Typically, once you define an interceptor, you configure the interceptor in an XML file, and the interceptor, along with its various configuration information, is packaged as a MappedInterceptor object.

initInterceptors

protected void initInterceptors(a) {
	if (!this.interceptors.isEmpty()) {
		for (int i = 0; i < this.interceptors.size(); i++) {
			Object interceptor = this.interceptors.get(i);
			if (interceptor == null) {
				throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
			}
			this.adaptedInterceptors.add(adaptInterceptor(interceptor)); }}}Copy the code

The initInterceptors method initializes interceptors by adding interceptors from the Interceptors collection to the adaptedInterceptors collection.

At this point, we see that all interceptors will eventually be stored in the adaptedInterceptors variable.

The initialization of AbstractHandlerMapping is essentially the initialization of an interceptor.

Why is interceptor so important in AbstractHandlerMapping? Is not seriously, we think, AbstractUrlHandlerMapping and AbstractHandlerMethodMapping the biggest difference is to find the difference between the processor, once found the processor, again to find the interceptor, but interceptors are unified, There was no obvious difference, so the interceptor is unified in the AbstractHandlerMapping processing, rather than AbstractUrlHandlerMapping or AbstractHandlerMethodMapping processing.

Let’s take a look at the AbstractHandlerMapping#getHandler method to see how the processor gets it:

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	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);
	}
	// Ensure presence of cached lookupPath for interceptors and others
	if(! ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
		CorsConfiguration config = getCorsConfiguration(handler, request);
		if(getCorsConfigurationSource() ! =null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig ! =null ? globalConfig.combine(config) : config);
		}
		if(config ! =null) {
			config.validateAllowCredentials();
		}
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}
Copy the code

The execution flow of this method looks like this:

  1. The getHandlerInternal method is first called to try to get the processor, which is also a template method that will be implemented in a subclass.
  2. If no handler is found, the getDefaultHandler method is called to get the default handler, which we can configure when configuring HandlerMapping.
  3. If the handler found is a string, then look up the corresponding Bean in the SpringMVC container based on that string.
  4. Make sure lookupPath exists, which will be used later when searching for the corresponding interceptor.
  5. Once the handler is found, the getHandlerExecutionChain method is next called to get the HandlerExecutionChain object.
  6. Next, if inside is cross-domain processing, obtain cross-domain related configuration, and then verify & configure, check whether cross-domain is allowed. The configuration and verification of the cross-domain is quite interesting, and Songko will write articles to discuss it with friends in detail.

Let’s look at the execution logic of the getHandlerExecutionChain method in step 5, where the handler becomes HandlerExecutionChain:

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
	HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
			(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
	for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
		if (interceptor instanceof MappedInterceptor) {
			MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
			if(mappedInterceptor.matches(request)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); }}else{ chain.addInterceptor(interceptor); }}return chain;
}
Copy the code

A new HandlerExecutionChain object is created from the existing handler and iterated through the adaptedInterceptors collection. If the interceptor type is MappedInterceptor, Call matches to check if it is the interceptor intercepting the request. If it is, add the chain.addInterceptor to the HandlerExecutionChain object. If it is a normal interceptor, it is added directly to the HandlerExecutionChain object.

This is the general logic of the AbstractHandlerMapping#getHandler method. As you can see, there’s a template method that getHandlerInternal is implemented in a subclass, so let’s look at its subclass.

3.AbstractUrlHandlerMapping

AbstractUrlHandlerMapping, see how the name, is carried out in accordance with the URL to match, its principle is to save the URL address and the corresponding Handler in the same Map, when call getHandlerInternal method, We’ll go to the Map and find the Handler that returns the requested URL.

Let’s start with his getHandlerInternal method:

@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	String lookupPath = initLookupPath(request);
	Object handler;
	if (usesPathPatterns()) {
		RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request);
		handler = lookupHandler(path, lookupPath, request);
	}
	else {
		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;
}
Copy the code
  1. First find lookupPath, which is the requested path. This method itself songko will not say more, previously in Spring5 in the new gameplay! This KIND of URL request really opens my mind! It was introduced in the article.
  2. LookupHandler has an overloaded method, depending on the URL matching pattern used. If the latest PathPattern is used (after Spring5), the lookupHandler method will be used to retrieve the Handler object. The lookupHandler with three parameters is used; If you still use the old AntPathMatcher, you use a lookupHandler with two arguments.
  3. If the handler is a String, the handler is a String. If the handler is a String, the handler is a String. Call validateHandler () to verify that the found handler matches the Request Bean. This method is empty and has no subclass implementation, so it can be ignored. Finally, we add some parameters to the found handler using the buildPathExposingHandler method.

This is the logic of the whole getHandlerInternal method, which is actually not difficult. It mainly involves lookupHandler and buildPathExposingHandler methods, which need to be introduced with you in detail. Let’s look at them separately.

lookupHandler

LookupHandler has two. Let’s look at them separately.

@Nullable
protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
	Object handler = getDirectMatch(lookupPath, request);
	if(handler ! =null) {
		return handler;
	}
	// Pattern match?
	List<String> matchingPatterns = new ArrayList<>();
	for (String registeredPattern : this.handlerMap.keySet()) {
		if (getPathMatcher().match(registeredPattern, lookupPath)) {
			matchingPatterns.add(registeredPattern);
		}
		else if (useTrailingSlashMatch()) {
			if(! registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) {
				matchingPatterns.add(registeredPattern + "/");
			}
		}
	}
	String bestMatch = null;
	Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
	if(! matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); 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, lookupPath);
		// 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, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); }}return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
	}
	// No handler found...
	return null;
}
@Nullable
private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
	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);
	}
	return null;
}
Copy the code
  1. In this case, the getDirectMatch method is called to find the corresponding processor directly in the handlerMap. The mapping between the request URL and the processor is stored in the handlerMap. The specific search process is to find the corresponding processor in the handlerMap. If it’s a String, go to the Spring container and find the corresponding Bean, then call validateHandler to verify (there’s no validation, as we’ve already said), and finally call buildPathExposingHandler to add the interceptor.
  2. If the getDirectMatch method does not return null, the found handler is returned and the method ends there. When does the getDirectMatch method not return null? The getDirectMatch method does not return null if there is no wildcard in the request address, and each request address corresponds to one processor. This is the only case where the getDirectMatch method does not return null, because the handlerMap holds the code definition. The access path of a processor may have wildcards, but when we actually make the request, there will be no wildcards in the request path, and then we will not be able to find the corresponding handler in the handlerMap. If wildcards are used to define interfaces, they need to be handled in the following code.
  3. Next we deal with the wildcard case. Start by defining the matchingPatterns collection and comparing the current request path with the request path rules stored in the handlerMap collection. Any matching rules are stored directly into the matchingPatterns collection. There is also the possibility of useTrailingSlashMatch. If you are not familiar with SpringMVC, you may be confused by this. Here’s what happens/For a simple example, if you define an interface that is/user, then the request path can be/userCan also be/user/Both defaults are supported, so the useTrailingSlashMatch branch handles the latter case simply by adding registeredPattern/It then proceeds to match the request path.
  4. Since a request URL may match multiple interfaces defined, the matchingPatterns variable is an array, and then you sort the matchingPatterns. Once that’s done, Select the first sorted item as the best option to assign to the bestMatch variable. The default collation is AntPatternComparator, but developers can also customize it. The priorities defined in AntPatternComparator are as follows:
The routing configuration priority
Path without any special characters, for example, configuration route/a/b/c First priority
with{}Path, such as:/a/{b}/c Second priority
Paths with regulars, such as:/a/{regex:\d{3}}/c Third priority
with*Path, such as:/a/b/* Fourth priority
with支那Path, such as:/a/b/** Fifth priority
The most obscure match:/ * * Lowest priority
  1. After finding bestMatch, then go to handlerMap to find the corresponding processor according to bestMatch. If not, go to check whether bestMatch matches/End, if it is/End, remove the end/Go to the handlerMap, and if it doesn’t find it, it’s time to throw an exception. If we find a String handler, we go to the Spring container to find the Bean, and then call validateHandler to verify.
  2. Then call the extractPathWithinPattern method to extract the mapping path, for example, define the interface rule ismyroot/*.html, the request path ismyroot/myfile.htmlSo what you end up with ismyfile.html.
  3. The next for loop deals with cases where there are multiple best matching rules. In step 4, we sort the matchingPatterns, and when that’s done, we select the first as the best option and assign it to bestMatch, but there may be multiple best options, This is the case where there are multiple best choices.
  4. Finally, register two internal interceptors by calling the buildPathExposingHandler method, which I’ll describe in more detail below.

LookupHandler also has an overloaded method, but it’s actually pretty easy to understand once you understand how it’s executed. The only thing songo will say is that the overloaded method uses a PathPattern to match URL paths. This method uses AntPathMatcher to match URL paths.

buildPathExposingHandler

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
		String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
	HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
	chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
	if(! CollectionUtils.isEmpty(uriTemplateVariables)) { chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
	}
	return chain;
}
Copy the code

BuildPathExposingHandler method to HandlerExecutionChain added two PathExposingHandlerInterceptor and interceptor UriTemplateVariablesHandlerInterceptor, these two interceptors in their respective preHandle respectively add some properties to the request object, properties of concrete adding friends can see, this is simple, I am not much said.

In the previous method, there is an important variable called handlerMap. The relationship between the interface and the handler is stored in this variable. How is this variable initialized? It’s another way of involving AbstractUrlHandlerMapping registerHandler:

protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
	for(String urlPath : urlPaths) { registerHandler(urlPath, beanName); }}protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
	Object resolvedHandler = handler;
	if (!this.lazyInitHandlers && handler instanceof String) {
		String handlerName = (String) handler;
		ApplicationContext applicationContext = obtainApplicationContext();
		if (applicationContext.isSingleton(handlerName)) {
			resolvedHandler = applicationContext.getBean(handlerName);
		}
	}
	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("/")) {
			setRootHandler(resolvedHandler);
		}
		else if (urlPath.equals("/ *")) {
			setDefaultHandler(resolvedHandler);
		}
		else {
			this.handlerMap.put(urlPath, resolvedHandler);
			if(getPatternParser() ! =null) {
				this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); }}}}Copy the code

The registerHandler(String[],String) method takes two parameters. The first parameter is the defined request path, the second parameter is the name of the handler Bean, and the first parameter is an array. This is because the same handler can have multiple request paths.

In the overloaded registerHandler(String,String) method, the handlerMap is initialized as follows:

  1. If lazyInitHandlers is not set and handler is of type String, then go to the Spring container to find the corresponding Bean and assign it to resolvedHandler.
  2. If there is a handler in the handlerMap, it will throw an exception. One URL can only correspond to one handler.
  3. The handler is then configured based on the URL path and eventually added to the handlerMap variable.

This is the main work, AbstractUrlHandlerMapping registerHandler will call in its subclasses.

Next we see AbstractUrlHandlerMapping subclasses.

3.1 SimpleUrlHandlerMapping

For ease of handling, SimpleUrlHandlerMapping defines its own urlMap variable to do some pre-processing before registration, such as making sure all urls start with a /. SimpleUrlHandlerMapping overrides the parent’s initApplicationContext method and calls the registerHandlers in that method. At the same time, the parent registerHandler method is called to initialize handlerMap:

@Override
public void initApplicationContext(a) throws BeansException {
	super.initApplicationContext();
	registerHandlers(this.urlMap);
}
protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
	if (urlMap.isEmpty()) {
		logger.trace("No patterns in " + formatMappingName());
	}
	else {
		urlMap.forEach((url, handler) -> {
			// Prepend with slash if not already present.
			if(! url.startsWith("/")) {
				url = "/" + url;
			}
			// Remove whitespace from handler bean name.
			if (handler instanceofString) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); }}Copy the code

This code is very simple, there’s really nothing to say, if the URL doesn’t start with a /, just add a/to it manually. Some of you might be wondering, where does urlMap come from? From our config file, of course, as follows:

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="urlMap">
        <map>
            <entry key="/aaa" value-ref="/hello"/>
        </map>
    </property>
</bean>
Copy the code

3.2 AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping AbstractUrlHandlerMapping subclasses, but there are some different places and SimpleUrlHandlerMapping.

What’s the difference?

AbstractDetectingUrlHandlerMapping will automatically search for SpringMVC containers as well as to all of the Spring container beanName, then according to the beanName parsing out the corresponding URL address, The resolved URL and the corresponding beanName are registered in the handlerMap variable of the parent class. In other words, if you use the AbstractDetectingUrlHandlerMapping, don’t like SimpleUrlHandlerMapping to each configuration URL address mapping relation and the processor. We’ll look at AbstractDetectingUrlHandlerMapping# initApplicationContext method:

@Override
public void initApplicationContext(a) throws ApplicationContextException {
	super.initApplicationContext();
	detectHandlers();
}
protected void detectHandlers(a) throws BeansException {
	ApplicationContext applicationContext = obtainApplicationContext();
	String[] beanNames = (this.detectHandlersInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
			applicationContext.getBeanNamesForType(Object.class));
	for (String beanName : beanNames) {
		String[] urls = determineUrlsForHandler(beanName);
		if(! ObjectUtils.isEmpty(urls)) { registerHandler(urls, beanName); }}}Copy the code

AbstractDetectingUrlHandlerMapping override the parent class initApplicationContext method, and this method invokes the detectHandlers method, in detectHandlers, The URL corresponding to beanName is determineUrlsForHandler. However, this method is null. Specific implementation in its subclasses, only a subclass AbstractDetectingUrlHandlerMapping BeanNameUrlHandlerMapping, together we look at:

public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) { urls.add(alias); }}returnStringUtils.toStringArray(urls); }}Copy the code

This is a simple class, so there’s a determineUrlsForHandler method, and the logic is simple enough to determine if a beanName starts with a slash, and if so, use that as a URL.

If we want to use in project BeanNameUrlHandlerMapping, configuration method is as follows:

<bean class="org.javaboy.init.HelloController" name="/hello"/>
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping">
</bean>
Copy the code

Note that the Controller’s name must start with a /, or the bean will not be automatically treated as a handler.

At this point, AbstractUrlHandlerMapping system finished things will share with you.

4.AbstractHandlerMethodMapping

AbstractHandlerMethodMapping system only three classes, AbstractHandlerMethodMapping, RequestMappingInfoHandlerMapping and RequestMappingHandlerMapping the diagram below:

In front of the third section AbstractUrlHandlerMapping system, a general is a class Handler, but under the AbstractHandlerMethodMapping system, a Handler is a Mehtod, This is the most common usage we currently use with SpringMVC, which is to tag a method directly with @requestMapping, which is a Handler.

Then we’ll take a look at AbstractHandlerMethodMapping.

4.1 Initialization Process

AbstractHandlerMethodMapping class implements the InitializingBean interface, so the Spring container will automatically call the afterPropertiesSet method, here will finish initialization:

@Override
public void afterPropertiesSet(a) {
	initHandlerMethods();
}
protected void initHandlerMethods(a) {
	for (String beanName : getCandidateBeanNames()) {
		if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); }protected String[] getCandidateBeanNames() {
	return (this.detectHandlerMethodsInAncestorContexts ?
			BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
			obtainApplicationContext().getBeanNamesForType(Object.class));
}
protected void processCandidateBean(String beanName) { Class<? > beanType =null;
	try {
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
	}
	if(beanType ! =null&& isHandler(beanType)) { detectHandlerMethods(beanName); }}Copy the code

As you can see, the specific initialization is again done in the initHandlerMethods method, where the getCandidateBeanNames method is first called to get all the Beannames in the container, The processCandidateBean method is then called to process the candidate beanName by finding the beanType based on the beanName. Then call isHandler method to judge the beanType is a Handler, isHandler is an empty method, are realized in its subclasses RequestMappingHandlerMapping, This method checks to see if there is an @controller or @requestMapping annotation on the beanType. If there is, this is the handler we want. DetectHandlerMethods ();

protected void detectHandlerMethods(Object handler) { Class<? > handlerType = (handlerinstanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());
	if(handlerType ! =null) { Class<? > userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> {try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]."+ method, ex); }}); methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code
  1. First, find the type of handler, handlerType.
  2. Call the classutils.getUserClass method to check if it is a child object type of the Cglib proxy. If so, return the parent type, otherwise return the argument directly.
  3. The next call MethodIntrospector. SelectMethods method for current all conform to the requirements in the bean method.
  4. Iterate over methods and call the registerHandlerMethod method to complete the registration.

There are two more methods involved in this code:

  • getMappingForMethod
  • registerHandlerMethod

Let’s look at them separately:

getMappingForMethod

GetMappingForMethod is a template method and concrete implementation in the subclass RequestMappingHandlerMapping inside:

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class
        handlerType) {
	RequestMappingInfo info = createRequestMappingInfo(method);
	if(info ! =null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if(typeInfo ! =null) {
			info = typeInfo.combine(info);
		}
		String prefix = getPathPrefix(handlerType);
		if(prefix ! =null) {
			info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); }}return info;
}
Copy the code

RequestMappingInfo (RequestMappingInfo) ¶ RequestMappingInfo (RequestMappingInfo) contains the details of the interface definition. It contains information about parameters, header, Produces, Consumes, request methods, and so on. We then get a RequestMappingInfo based on the handlerType as well and call the Combine method to combine the two RequestMappingInfo. Then call getPathPrefix to check if handlerType has a URL prefix, add it to info, and return info.

We can use the @requestMapping annotation on the Controller to configure a path prefix so that all methods in the Controller have that path prefix. However, this method requires configuration one by one. What if you want to configure all controllers at once? We can configure this using the new addPathPrefix method introduced in Spring5.1, as follows:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class)); }}Copy the code

The above configuration means that all classes tagged with @RestController are automatically prefixed with itboyHub. With this configuration, the getPathPrefix method above gets/itboyHub.

registerHandlerMethod

Once the URL and handlerMethod are found, the next step is to save the information as follows:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
	this.readWriteLock.writeLock().lock();
	try {
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		validateMethodMapping(handlerMethod, mapping);
		Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
		for (String path : directPaths) {
			this.pathLookup.add(path, mapping);
		}
		String name = null;
		if(getNamingStrategy() ! =null) {
			name = getNamingStrategy().getName(handlerMethod, mapping);
			addMappingName(name, handlerMethod);
		}
		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if(corsConfig ! =null) {
			corsConfig.validateAllowCredentials();
			this.corsLookup.put(handlerMethod, corsConfig);
		}
		this.registry.put(mapping,
				newMappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig ! =null));
	}
	finally {
		this.readWriteLock.writeLock().unlock(); }}Copy the code
  1. The HandlerMethod object is first created by calling the createHandlerMethod method.
  2. The validateMethodMapping method is called to verify whether handlerMethod already exists.
  3. DirectPaths are extracted from mappings, which are request paths without wildcard characters, and the mapping between request paths and mapping is saved in pathLookup.
  4. Find all the abbreviations for handlers and add them to nameLookup by calling the addMappingName method. For example, if we define a request interface named Hello in HelloController, this is what we get hereHC#helloHC is the uppercase letter in HelloController.
  5. Initialize the cross-domain configuration and add it to corsLookup.
  6. Add the constructed relationship to Registry.

By the way, what’s the point of step four? This is actually Spring4 began to increase the function, it is a small egg, although the daily development is rarely used, but I still say it with you.

Suppose you have the following interface:

@RestController
@RequestMapping("/javaboy")
public class HelloController {
    @GetMapping("/aaa")
    public String hello99(a) {
        return "aaa"; }}Copy the code

When you request this interface, you don’t want to go through the path, you want to go directly through the method name, ok? Of course you can!

In the JSP file, add the following hyperlink:

<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<a href="${s:mvcUrl('HC#hello99').build()}">Go! </a> </body> </html>Copy the code

When the JSP page is rendered, the href attribute automatically becomes the request path for the Hello99 method. The implementation of this function depends on the content of the previous step 4.

At this point, we’ve read AbstractHandlerMethodMapping initialization process.

4.2 Request Processing

Next we’ll look at when the request arrives, AbstractHandlerMethodMapping will be how to deal with.

As in the previous section 3, the entry method to handle the request is getHandlerInternal, as follows:

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	String lookupPath = initLookupPath(request);
	this.mappingRegistry.acquireReadLock();
	try {
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return(handlerMethod ! =null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
		this.mappingRegistry.releaseReadLock(); }}protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	if(directPathMatches ! =null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
	}
	if(! matches.isEmpty()) { Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			bestMatch = matches.get(0);
			if (CorsUtils.isPreFlightRequest(request)) {
				for (Match match : matches) {
					if (match.hasCorsConfig()) {
						returnPREFLIGHT_AMBIGUOUS_MATCH; }}}else {
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.getHandlerMethod().getMethod();
					Method m2 = secondBestMatch.getHandlerMethod().getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
							"Ambiguous handler methods mapped for '" + uri + "' : {" + m1 + "," + m2 + "}");
				}
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.getHandlerMethod();
	}
	else {
		return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); }}Copy the code

If lookupHandlerMethod does not return null, Create a HandlerMethod using createWithResolvedBean. Songo will explain how to create a HandlerMethod in a later article. The lookupHandlerMethod method is also easier:

  1. First find a directPathMatches match based on lookupPath, and then add that match to matches (requests without wildcards go here).
  2. If matches is null, no matches were found according to lookupPath, so add all matches directly to matches (requests containing wildcards go here).
  3. Matches sorts matches and selects the best match for the first sorted item, or throws an exception if the first two are the same.

This is the general flow, and there is no subclass of the request.

5. Summary

SpringMVC nine components, today and partners have been through HandlerMapping, in fact, as long as you look carefully, there is no difficulty. If you find it difficult to read, you can also reply to SSM in the background of the official account and check out the free introductory course recorded by Songgo

The remaining eight components of the source code analysis, partners please look forward to!