SoFunction
Updated on 2025-03-08

Detailed explanation of SpringMVC component HandlerMapping (II)

1. Preface

in front of"Detailed explanation of SpringMVC component HandlerMapping (I)In 》, we analyze the overall logic of the HandlerMapping component and its implementation method of the AbstractUrlHandlerMapping series.

In this section, we will analyze the implementation of the AbstractHandlerMethodMapping series.

2. AbstractHandlerMethodMapping system

In the AbstractHandlerMethodMapping system, there are only three classes, namely

  1. AbstractHandlerMethodMapping
  2. RequestMappingInfoHandlerMapping
  3. RequestMappingHandlerMapping

These three classes inherit from the previous class in turn, while the first AbstractHandlerMethodMapping abstract class inherits from the AbstractHandlerMapping abstract class and implements the InitializingBean interface. After implementing the InitializingBean interface, the afterPropertiesSet() method will be called after the bean is instantiated.

In the AbstractHandlerMethodMapping system, the hierarchy structure of the class is relatively simple and clear, but Spring is still relatively complex in order to ensure the flexibility of the AbstractHandlerMethodMapping system.

Before analyzing the AbstractHandlerMethodMapping class, let’s first understand several of the core basic classes.

  • HandlerMethod
    • A method-based processor includes the corresponding methods and instance beans of the processor, and provides some methods such as accessing method parameters, method return values, method annotations, etc.
  • RequestMappingInfo
    • Indicates the request information and encapsulates the matching conditions of the mapping relationship. When using the @RequestMapping annotation, the configuration information is finally set to the RequestMappingInfo. The different properties of the @RequestMapping annotation will be mapped to the corresponding XXXRequestCondition.
  • AbstractHandlerMethodMapping - Internal class Match
    • Encapsulates HandlerMethod and generic class T. The generic class T actually represents the RequestMappingInfo.
  • Internal class MappingRegistration
    • Records the information when the mapping relationship is registered. Encapsulates HandlerMethod, generic class T, mappingName, directUrls attributes (save the corresponding relationship between url and RequestMappingInfo, multiple urls may correspond to the same mappingInfo)
  • Internal class MappingRegistry
    • Mainly maintain several maps to store mapped information. The following details the mapping relationship of this internal class and its definition

3. AbstractHandlerMethodMapping Abstract Class

There are many internal classes defined in the AbstractHandlerMethodMapping abstract class. The Match, MappingRegistration, and MappingRegistry mentioned above are all in this class. Among them, MappingRegistry is the most important. Let us first analyze this internal class because the several Map attributes involved are the core of maintaining the relationship between the request and the processor Handler.

3.1. MappingRegistry internal class

The internal MappingRegistry class mainly maintains the mapping relationship between objects such as T (RequestMappingInfo), mappingName, HandlerMethod, CorsConfiguration, etc., and also provides a read-write lock readWriteLock object.

1. Attributes

//Maintain the relationship between mapping and MappingRegistration registration informationprivate final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//Maintain the relationship between mapping and HandlerMethodprivate final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
//Save the correspondence between the URL and the matching condition (mapping). Key is those URLs that do not contain wildcards. The value corresponds to a list value.private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
//Save the correspondence between name and HandlerMethod (one name can have multiple HandlerMethods)private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
//Save the relationship between HandlerMethod and cross-domain configurationprivate final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
//Read and write lockprivate final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

2. register() method, unregister() method. The register() method and unregister() method are mainly used to register the mapping relationship between objects such as T (RequestMappingInfo), mappingName, HandlerMethod, CorsConfiguration, etc.

Among them, the register() method code is as follows:

public void register(T mapping, Object handler, Method method) {
	// Assert that the handler method is not a suspending one.
	if ((()) && (method)) {
		throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
	}
	//Add write lock	().lock();
	try {
		//Create a HandlerMethod object (constructor creates an object)		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		//Verify the correspondence between handlerMethod and mapping. If a HandlerMethod object already exists and is different from handlerMethod, an exception "Ambiguous mapping. Cannot map" will be thrown		validateMethodMapping(handlerMethod, mapping);
		//Maintain mappingLookup object and establish mapping relationship between mapping and handlerMethod		(mapping, handlerMethod);
		//Get the matching condition mapping, corresponding URL (those URLs that do not contain wildcards), and the URL is provided by the subclass implementation getMappingPathPatterns() method, but is actually provided by the PatternsRequestCondition. When is the directUrls initialized?  We are analyzing in the future.		List<String> directUrls = getDirectUrls(mapping);
		for (String url : directUrls) {
			(url, mapping);
		}
		String name = null;
		//Get mappingName and maintain the relationship with handlerMethod		if (getNamingStrategy() != null) {
			//The namingStrategy property corresponds to the RequestMappingInfoHandlerMethodMappingNamingStrategy object (set in the subclass RequestMappingInfoHandlerMapping constructor), naming rules: all size characters in the class name + "#" + method name name = getNamingStrategy().getName(handlerMethod, mapping);			addMappingName(name, handlerMethod);
		}
		//Get cross-domain configuration CorsConfiguration object to maintain the relationship with handlerMethod.  initCorsConfiguration() sets the empty method and is rewritten in the RequestMappingHandlerMapping class.		CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
		if (corsConfig != null) {
			(handlerMethod, corsConfig);
		}
		//Maintain the relationship between mapping and MappingRegistration		(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
	}
	finally {
		().unlock();
	}
}

The unregister() method mainly removes the mapping relationship between mapping matching conditions and other objects, and the code is not posted here.

3.2. Initialization

As we mentioned earlier, AbstractHandlerMethodMapping implements the InitializingBean interface, so the afterPropertiesSet() method will be called after the bean is instantiated.

In fact, the initialization work of the AbstractHandlerMethodMapping class starts from this place.

The code is as follows:

@Override
public void afterPropertiesSet() {
	initHandlerMethods();
}
protected void initHandlerMethods() {
	//Get candidate bean instance	for (String beanName : getCandidateBeanNames()) {
		if (!(SCOPED_TARGET_NAME_PREFIX)) {
			//Transfer subsequent bean instances and process them separately			processCandidateBean(beanName);
		}
	}
	//Just just printed the log, there is no actual processing logic	handlerMethodsInitialized(getHandlerMethods());
}

In the afterPropertiesSet() method, the initHandlerMethods() method is called, and the actual initialization logic is implemented in this method. First, get the names of all candidate Bean instances through getCandidateBeanNames() method, and the code is as follows:

protected String[] getCandidateBeanNames() {
	//detectHandlerMethodsInAncestorContexts Indicates whether to look for bean instances from the ancestor container	return ( ?
			(obtainApplicationContext(), ) :
			obtainApplicationContext().getBeanNamesForType());
}

Then, iterate over the Bean instance obtained through the getCandidateBeanNames() method, and call the processCandidateBean() method for processing. The code is as follows:

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		//Get the corresponding Class type		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (()) {
			("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	//Judge whether beanType is the processor type, and judge through the isHandler() method. This method is implemented in the RequestMappingHandlerMapping class and judges based on whether there are Controller or RequestMapping annotations.	if (beanType != null && isHandler(beanType)) {
		//Get the corresponding processing method in the specified bean instance and register it into the corresponding Map relation map through mappingRegistry		detectHandlerMethods(beanName);
	}
}

In the processCandidateBean() method, determine whether the bean instance corresponding to beanName is the processor type (judgmented by isHandler method). If so, call the detectHandlerMethods() method to obtain the corresponding processing method in the instance, and register it into the corresponding Map relation map through mappingRegistry. The code is as follows:

protected void detectHandlerMethods(Object handler) {
	//Get the corresponding Class of the processor	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : ());
	if (handlerType != null) {
		//Get the original type defined by the user. In most cases, it is the type itself. It mainly makes additional judgments on cglib and obtains the parent class of the cglib proxy;		Class<?> userType = (handlerType);
		//Find the specified method in the given type userType. The specific judgment conditions are determined by the getMappingForMethod() method.		Map<Method, T> methods = (userType,
				(<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								() + "]: " + method, ex);
					}
				});
		if (()) {
			(formatMappings(userType, methods));
		}
		//Register the filtered method into the mapping relationship through registerHandlerMethod()		((method, mapping) -> {
			Method invocableMethod = (method, userType);
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}

Since then, the initialization work has been completed, that is, the mapping relationship between the mapping matching conditions and the HandlerMethod is registered.

3.3. getHandlerInternal() method

I analyzed the initialization method of the AbstractHandlerMethodMapping class before. Now we start to analyze how this class implements the getHandlerInternal() method of the parent class, that is, how to obtain the corresponding processor HandlerMethod object through request.

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
	//Get lookupPath	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	//Set "." Parameter	(LOOKUP_PATH, lookupPath);
	//Acquiring the read lock	();
	try {
		//Get the processor corresponding to the request		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		//If the processor corresponds to bean name, call createWithResolvedBean() method to create the corresponding bean instance		return (handlerMethod != null ? () : null);
	}
	finally {
		();
	}
}

In the getHandlerInternal() method, we know that the lookupHandlerMethod() method is a method that truly implements the search processor, and the code is as follows:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	//From the attribute, get the mapping collection corresponding to lookupPath	List<T> directPathMatches = (lookupPath);
	if (directPathMatches != null) {
		//Get the matching mapping and add to the matches variant		addMatchingMappings(directPathMatches, matches, request);
	}
	if (()) {
		// If there is no instance matching lookupPath, iterate through all mappings and find matching mappings		addMatchingMappings(().keySet(), matches, request);
	}
	if (!()) {//Indicate that there are mappings that meet the conditions, there may be multiple mappings		//Get the sorter for matching conditions, get it from the abstract method getMappingComparator() method, which is implemented by the subclass		Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
		//Sort		(comparator);
		Match bestMatch = (0);
		if (() > 1) {// More than one match			if (()) {
				(() + " matching mappings: " + matches);
			}
			if ((request)) {// When OPTIONS requests, directly handle				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = (1);
			//If there are multiple processors, the exception will be directly thrown			if ((bestMatch, secondBestMatch) == 0) {
				Method m1 = ();
				Method m2 = ();
				String uri = ();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		//Define the ".bestMatchingHandler" property		(BEST_MATCHING_HANDLER_ATTRIBUTE, );
		//Train the matching processor, here is just adding a ".pathWithinHandlerMapping" property, and the specific implementation is carried out in the subclass.		handleMatch(, lookupPath, request);
		return ;
	}
	else {//When there is no matching bean, call handleNoMatch() method, empty method, and have subclasses to implement it.		return handleNoMatch(().keySet(), lookupPath, request);
	}
}

4. RequestMappingInfoHandlerMapping Abstract Class

The RequestMappingInfoHandlerMapping abstract class inherits from the AbstractHandlerMethodMapping class, and it is clear that the generic class is RequestMappingInfo.

The following things are done in the RequestMappingInfoHandlerMapping abstract class:

1. Define the default handling method of Options, which involves the HTTP_OPTIONS_HANDLE_METHOD constant, the internal class HttpOptionsHandler and the static code block initialization constant.

2. Constructor, the naming strategy for mapping is set in the constructor, that is, the implementation method of RequestMappingInfoHandlerMethodMappingNamingStrategy.

3. Several methods in the parent class are implemented, as follows:

//Get the URL collection corresponding to RequestMappingInfo@Override
protected Set<String> getMappingPathPatterns(RequestMappingInfo info) {
	return ().getPatterns();
}
//Judge whether the current RequestMappingInfo matches the request. In fact, it is determined by the getMatchingCondition() method of RequestMappingInfo and returns a newly created RequestMappingInfo instance.@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
	return (request);
}
//Get the comparator@Override
protected Comparator<RequestMappingInfo> getMappingComparator(final HttpServletRequest request) {
	return (info1, info2) -> (info2, request);
}

4. Re-replace the handleMatch() method of the parent class, and the logic of processing variables (template variables and matrix variables (MatrixVariable)) and media types is mainly added to this method. The parameter processing process will be analyzed in detail later.

5. Rewrite the handleNoMatch() method of the parent class. This method uses the internal class PartialMatchHelper to determine the cause of the mismatch, and then throws the specified exception. Not posting code.

5. RequestMappingHandlerMapping class

In addition to inheriting RequestMappingInfoHandlerMapping, the RequestMappingHandlerMapping class also implements two interfaces: MatchableHandlerMapping and EmbeddedValueResolverAware.

Among them, the method to implement the MatchableHandlerMapping interface is not used yet; but the EmbeddedValueResolverAware interface is implemented, indicating that it supports parsing String strings.

Defined properties:

// Whether to enable suffix matchingprivate boolean useSuffixPatternMatch = true;
//Whether the suffix pattern matching should only be valid for path extensions explicitly registered in ContentNegotiationManager.private boolean useRegisteredSuffixPatternMatch = false;
//The tail slash matchesprivate boolean useTrailingSlashMatch = true;
//Set the prefix path according to the conditionsprivate Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
//Multimedia type judgmentprivate ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
//String parser, processing spring expression@Nullable
private StringValueResolver embeddedValueResolver;
//RequestMappingInfo build configurationprivate  config = new ();

AfterPropertiesSet() method

In the previous example, we know that afterPropertiesSet() method is a method that implements initialization. In the AbstractHandlerMethodMapping abstract class, the detection and registration of handler classes and methods are implemented. In the RequestMappingHandlerMapping class, the initialization of the RequestMappingInfo build configuration is added, and the code is as follows:

@Override
public void afterPropertiesSet() {
	 = new ();
	(getUrlPathHelper());
	(getPathMatcher());
	();
	();
	();
	(getContentNegotiationManager());
	();
}

isHandler() method

In the AbstractHandlerMethodMapping class, when initializing, isHandler is used in the processCandidateBean() method to determine whether the current bean instance is a processor. The actual judgment logic is implemented here.

@Override
protected boolean isHandler(Class<?> beanType) {
	return ((beanType, ) ||
			(beanType, ));
}

getMappingForMethod method

In the AbstractHandlerMethodMapping class, when initializing, in the detectHandlerMethods() method, this method is called to achieve the judgment of the processor method.

@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	RequestMappingInfo info = createRequestMappingInfo(method);
	if (info != null) {
		RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
		if (typeInfo != null) {
			info = (info);
		}
		String prefix = getPathPrefix(handlerType);
		if (prefix != null) {
			info = (prefix).build().combine(info);
		}
	}
	return info;
}
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
	RequestMapping requestMapping = (element, );
	RequestCondition<?> condition = (element instanceof Class ?
			getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
	return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
protected RequestMappingInfo createRequestMappingInfo(
		RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
	 builder = RequestMappingInfo
			.paths(resolveEmbeddedValuesInPatterns(()))
			.methods(())
			.params(())
			.headers(())
			.consumes(())
			.produces(())
			.mappingName(());
	if (customCondition != null) {
		(customCondition);
	}
	return ().build();
}

Other methods: In addition to calling the parent class method, the two methods of registerMapping() and registerHandlerMethod() are mainly set to determine the ConsumersRequestCondition.

There are also cross-domain related parameters to configure, so I will not analyze them in detail here.

6. Summary

In this article, we only analyzed the basic implementation of these three classes in HandlerMapping implemented by the AbstractHandlerMethodMapping system, including the specific introduction of RequestCondition and RequestMappingInfo (also a subclass of RequestCondition) and HandlerMethod and other classes. We are gradually learning and recording in the subsequent process.

This is the end of this article about the HandlerMapping (II) of SpringMVC components. For more related SpringMVC HandlerMapping content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!