AbstractUrlHandlerMapping is matched by a url, that is to say, via the url with the corresponding Handler package to a Map, and then in getHandlerInternal method used in the url as the key from the Map for our Handler.

AbstractUrlHandlerMapping realized from the url for handler process, specific mapping relationship, namely handlerMap is specific to a subclass to complete. Defined AbstractUrlHandlerMapping handlerMap used to maintain the mapping relations, are as follows:

private final Map<String, Object> handlerMap = 
new LinkedHashMap<String, Object>();
Copy the code

In addition, there is a rootHandler, which is used to handle “/” requests.

As mentioned in the previous three articles, the handler is obtained through the getHandlerInternal method. Below look at the specific source code, analyze the handler and handlerMap construction.

// Find the Handler for the URL path of the given request.
protected Object getHandlerInternal(HttpServletRequest request) throws
Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    // Use lookupPath to find handlers from the Map
    Object handler = lookupHandler(lookupPath, request);
    if (handler == null) {
    	// Temporary variable to hold the original handler
    	Object rawHandler = null;
    	// Is the root path '/'
    	if ("/".equals(lookupPath)) {
    	    / / get rootHandler
    		rawHandler = getRootHandler();
    	}
    	// If rawHandler is null
    	if (rawHandler == null) {
    	    // Get the default handler
    		rawHandler = getDefaultHandler();
    	}
    	// If rawHandler is not null
    	if(rawHandler ! =null) {
    		// If it is a string, look for the specific bean in the container
    		if (rawHandler instanceof String) {
    			String handlerName = (String) rawHandler;
    			// get from the container
    			rawHandler = getApplicationContext().getBean(handlerName);
    		}
    		// Verify that the handler and request match
    		validateHandler(rawHandler, request);
    		// Register interceptors
    		handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); }}/ / log debug
    if(handler ! =null && logger.isDebugEnabled()) {
    	logger.debug("Mapping [" + lookupPath + "] to " + handler);
    }
    else if (handler == null && logger.isTraceEnabled()) {
    	logger.trace("No handler mapping found for [" + lookupPath + "]");
    }
    / / returns the handler
    return handler;
}
Copy the code

There are several method calls in the getHandlerInternal method, GetLookupPathForRequest, getRootHandler, getDefaultHandler, lookupHandler, buildPathExposingHandler, etc. GetLookupPathForRequest, getRootHandler, getDefaultHandler LookupHandler and buildPathExposingHandler are the two core methods.

  • lookupHandler

    The lookupHandler uses getUrlPathHelper().getlookuppathForRequest (request) to retrieve the lookupPath from the Map. Why is that? The reason is that many handlers use the Pattern matching Pattern, such as “/user/*”, where the asterisk matches anything, not the character in the url string. If the Pattern contains PathVariable, it cannot be obtained directly from the Map.

    In addition, a URL may match multiple patterns, so we must choose the best one at this time, so the search process is not so simple as to obtain from the map directly. So let’s take a look at what’s going on in lookupHandler:

protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
    // Match directly from Map
    Object handler = this.handlerMap.get(urlPath);
    / / get
    if(handler ! =null) {
    	// If it is a string, the Bean is fetched from the container
    	if (handler instanceof String) {
    		String handlerName = (String) handler;
    		handler = getApplicationContext().getBean(handlerName);
    	}
    	// Verify a match
    	validateHandler(handler, request);
    	// Register interceptors
    	return buildPathExposingHandler(handler, urlPath, urlPath, null);
    }
    // Pattern matches. The Pattern marked with * is matched with the URL
    List<String> matchingPatterns = new ArrayList<String>();
    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 +"/"); }}}// Get the best match
    String bestPatternMatch = null;
    Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
    if(! matchingPatterns.isEmpty()) { Collections.sort(matchingPatterns, patternComparator);if (logger.isDebugEnabled()) {
    		logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
    	}
    	bestPatternMatch = matchingPatterns.get(0);
    }
    // The best match is not null
    if(bestPatternMatch ! =null) {
        // See if there is a corresponding Handler in the Map
    	handler = this.handlerMap.get(bestPatternMatch);
    	// If not in Map
    	if (handler == null) {
    	// Whether to end with /
    		Assert.isTrue(bestPatternMatch.endsWith("/"));
    		// Remove/and fetch again
    		handler = this.handlerMap.get(bestPatternMatch.substring(0, bestPatternMatch.length() - 1));
    	}
    	// If it is a String, get the Bean from the container?
    	if (handler instanceof String) {
    		String handlerName = (String) handler;
    		handler = getApplicationContext().getBean(handlerName);
    	}
    	// Verify a match
    	validateHandler(handler, request);
    	String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
    
    	// There may be several optimal patterns, let's make sure we have the correct URI template variables.
    	Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
    	for (String matchingPattern : matchingPatterns) {
    		if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); }}if (logger.isDebugEnabled()) {
    		logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
    	}
    	return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
    }
    // No handler found...
    return null;
}
Copy the code

In the above code, I need to say something about the annotation; The code is as follows:

Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
    if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); }}Copy the code

The sorting was done using the sort method, and then the first as bestPatternMatch, but if multiple patterns are in the same order, meaning that sort returns 0, and there are multiple best matches, then we need to make sure we have the correct URI template variable. The code above handles this situation.

  • buildPathExposingHandler

    This method appears frequently in both pieces of code, so what does it do? The code I’m commenting on is registered interceptor, so what interceptor is registered? With those two questions in mind, let’s look at the code.

/ / buildPathExposingHandler for a given rawHandler build a Handler object, and in the hold
// Expose the actual handler PATH_WITHIN_HANDLER_MAPPING_ATTRIBUT before the line handler
//E and URI_TEMPLATE_VARIABLES_ATTRIBUTE.

// The default implementation builds a HandlerExecutionChain with a special interceptor that is exposed
// Path attribute and URI template variable.
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, Map
       
         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

Four parameters:

  • RawHandler rawHandler
  • BestMatchingPattern bestMatchingPattern
  • PathWithinMapping The path exposed before Handler is executed
  • UriTemplateVariables If no variable is found, URI template variable can be {null}

BuildPathExposingHandler registers two interceptors for the handler it has already found

  • ExposingHandlerInterceptor
  • UriTemplateVariablesHandlerInterceptor

These two classes are AbstractUrlHandlerMapping inner classes, namely two internal interceptors. The main function of these interceptors is to set the pattern, matching conditions, and URL template parameters that actually match the current URL into the request property, so that they can be retrieved directly from the request property later in the process. Let’s look at two inner class definitions:

private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {

    private final String bestMatchingPattern;
    private final String pathWithinMapping;
    public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
    	this.bestMatchingPattern = bestMatchingPattern;
    	this.pathWithinMapping = pathWithinMapping;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    	exposePathWithinMapping(this.bestMatchingPattern,
    	this.pathWithinMapping, request);
    	// Set the request attribute
    	request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
    	return true; }}private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {

    private final Map<String, String> uriTemplateVariables;
    public UriTemplateVariablesHandlerInterceptor(Map<String, String> uriTemplateVariables) {
    	this.uriTemplateVariables = uriTemplateVariables;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    / / this exposeUriTemplateVariables kind of set the request properties
    	exposeUriTemplateVariables(this.uriTemplateVariables, request);
    	return true; }}Copy the code

As can be seen from the inner class code, the two inner classes are through preHandle method invokes the exposePathWithinMapping and exposeUriTemplateVariables complete attribute set into the request.

The key to finding handlers is to maintain the mapping between urls and handlers, which is to build a handlerMap. In the AbstractUrlHandlerMapping is through registerHandler this method to construct handlerMap. AbstractUrlHandlerMapping provides two registerHandler method, is through the code below to see the specific implementation.

protected void registerHandler(String[] urlPaths, String beanName)
    throws BeansException, IllegalStateException {
    Assert.notNull(urlPaths, "URL path array must not be null");
    for(String urlPath : urlPaths) { registerHandler(urlPath, beanName); }}Copy the code

The first registerHandler registers multiple urls to a single handler. BeanName is actually the name of our processor. You can find the real processor Bean by beanName into the container. This is done by iterating through all urls and then registering the Handler to the handlerMap by calling a second registerHandler. Look at the second one:

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;
    
    // If the handler is of type string and is not lazyInitHandlers, then from SpringMV
    // get handler from C container
    if (!this.lazyInitHandlers && handler instanceof String) {
    	String handlerName = (String) handler;
    	if (getApplicationContext().isSingleton(handlerName)) {
    		resolvedHandler = getApplicationContext().getBean(handlerName);
    	}
    }
    
    Object mappedHandler = this.handlerMap.get(urlPath);
    if(mappedHandler ! =null) {
    	if(mappedHandler ! = resolvedHandler) {// Exception handling}}else {
        // Whether to follow the path
    	if (urlPath.equals("/")) {
    		if (logger.isInfoEnabled()) {
    			logger.info("Root mapping to " +
    			getHandlerDescription(handler));
    		}
    		setRootHandler(resolvedHandler);
    	}
    	// Is the * mode
    	else if (urlPath.equals("/ *")) {
    		if (logger.isInfoEnabled()) {
    			logger.info("Default mapping to " +
    			getHandlerDescription(handler));
    		}
    		setDefaultHandler(resolvedHandler);
    	}
    	// Add to handlerMap
    	else {
    		this.handlerMap.put(urlPath, resolvedHandler);
    		if (logger.isInfoEnabled()) {
        		logger.info("Mapped URL path [" + urlPath + "] onto "+ getHandlerDescription(handler)); }}}}Copy the code

If there is no url in the Map, add it to the Map. If there is no url in the Map, add it to the Map. If there is no url in the Map, check whether the original and the current registered handler are the same. You cannot have two different handlers for the same URL.

Before putting, some “/” and “/*” validation is also done, so these two paths are not saved to handlerMap.

  • “/” : setRootHandler (resolvedHandler);
  • “/ *” : setDefaultHandler (resolvedHandler);

OK, to this AbstractUrlHandlerMapping this class analysis completed, AbstractUrlHandlerMapping do is actually defines a frame, a subclass just complete the initialization of the Map. We can talk about AbstractUrlHandlerMapping subclasses of follow-up.