• SpringMVC source series: HandlerMapping
  • SpringMVC source code series: AbstractHandlerMapping

AbstractHandlerMapping is an abstract base class that implements the HandlerMapping interface. Support for sorting, default handlers, and handler interceptors, including handler interceptors mapped by path patterns. All HandlerMapping is derived from AbstractHandlerMapping. In addition, this base class does not support exposure of PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, which depends on the specific subclass, usually based on request URL mapping.

As mentioned earlier, HandlerMapping is all about finding handlers and Interceptors through request. The specific acquisition is achieved by subclasses.

1.AbstractHandlerMapping Class definition

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
 implements HandlerMapping, Ordered {
Copy the code

AbstractHandlerMapping inherited WebApplicationObjectSupport, initialized automatically call template method initApplicationContext; AbstractHandlerMapping was created in this method. Both HandlerMapping and Ordered interfaces are implemented, which explains the support for ordering mentioned above.

2.AbstractHandlerMapping

  • Sorted value order

    The default value is the maximum value of Integer, which is the same as having no sort, since it is only theoretically possible to exceed integer.max_value.

    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
    Copy the code
  • The defaultHandler is defaultHandler

    private Object defaultHandler;
    Copy the code
  • The Spring utility class urlPathHelper

    The Helper class is used for URL path matching. Provides support for URL paths in RequestDispatcher, including and support for consistent URL decoding.

    private UrlPathHelper urlPathHelper = new UrlPathHelper();
    Copy the code
  • Spring Tools Class PathMatcher(AntPathMatcher)

    Policy interface for string-based path matching.

    private PathMatcher pathMatcher = new AntPathMatcher();
    Copy the code
  • List of interceptors

    Interceptors used to configure SpringMVC can be configured in one of two ways:

    • 1. Set the attributes when registering HandlerMapping
    • 2. Set by subclass extendInterceptors hook method (called from initApplicationContext)

    Interceptors are not used directly. They are allocated to mappedInterceptors and adaptedInterceptors by type through the initInterceptors method. Interceptors are only used for configuration.

    private final List<Object> interceptors = new ArrayList<Object>();
    Copy the code
  • adaptedInterceptors

    Interceptors of the type assigned to adaptedInterceptors do not need to be matched and are added to the return value HandlerExecutionChain in getHandler. He can only get it from interceptors.

    private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<HandlerInterceptor>();
    Copy the code
  • corsProcessor

    The CorsProcessor accepts requests and CorsConfiguration and updates the response policy. This component does not care how CorsConfiguration is selected, but instead takes subsequent actions, such as applying CORS validation checks and rejecting the response or adding the CORS header to the response.

    private CorsProcessor corsProcessor = new DefaultCorsProcessor();
    Copy the code
  • corsConfigSource

    Provide an instance of CorsConfiguration for each request based on the CorsConfiguration collection mapped on the path pattern. Support for precise path mapping URIs (such as “/ admin”) and Ant style path patterns (such as “/ admin / **”)

    private final UrlBasedCorsConfigurationSource corsConfigSource 
    = new UrlBasedCorsConfigurationSource();
    Copy the code
  • Cross-domain related issues

CorsConfiguration PoJO CorsConfigurationSource Request that encapsulates cross-domain configuration information CorsProcessor class that performs cross-domain operations

3.AbstractHandlerMapping get&set method

3.1 setOrder

Specify the sort value for this HandlerMapping bean.

public final void setOrder(int order) {
  this.order = order;
}
Copy the code

3.2 setDefaultHandler

Specify the sort value for this HandlerMapping bean. Sets the default handler for this handler mapping. If no specific mapping is found, the handler is returned. The default value is null, indicating that there is no default handler.

public void setDefaultHandler(Object defaultHandler) {
	this.defaultHandler = defaultHandler;
}
Copy the code

3.3 getDefaultHandler

Returns the default handler for this handler mapping, or NULL if none is present.

public Object getDefaultHandler() {
	return this.defaultHandler;
}
Copy the code

3.4 setAlwaysUseFullPath

Set this if THE URL lookup always uses the full path in the current servlet context. Otherwise, if applicable, use the path in the current servlet map (that is, in web.xml “… / * “servlet mapping case). The default value is false. The implementation in setAlwaysUseFullPath is specifically delegated to urlPathHelper and corsConfigSource.

public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
	this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
	this.corsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
}
Copy the code

3.5 setUrlDecode

Set if the context path and request URI should be decoded by the URL. Both are returned “undecoded” by the Servlet API, as opposed to the Servlet path. Use the requested or default encoding according to the Servlet specification (ISO-8859-1). The implementation in setUrlDecode is specifically delegated to urlPathHelper and corsConfigSource.

public void setUrlDecode(boolean urlDecode) {
	this.urlPathHelper.setUrlDecode(urlDecode);
	this.corsConfigSource.setUrlDecode(urlDecode);
}
Copy the code

3.6 setRemoveSemicolonContent

If the “;” If the (semicolon) content should be removed from the request URI. The default value is true. SetRemoveSemicolonContent implementation of the concrete is entrusted to urlPathHelper and corsConfigSource.

public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
	this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
	this.corsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
}
Copy the code

3.7 setUrlPathHelper

Set UrlPathHelper to resolve the lookup path. Use it to override the default UrlPathHelper with custom subclasses, or to share common UrlPathHelper Settings across multiple HandlerMappings and MethodNameResolvers.

public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
	Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
	this.urlPathHelper = urlPathHelper;
	this.corsConfigSource.setUrlPathHelper(urlPathHelper);
}
Copy the code

3.8 getUrlPathHelper

Returns the UrlPathHelper implementation used to resolve the lookup path.

public UrlPathHelper getUrlPathHelper() {
	return urlPathHelper;
}
Copy the code

3.9 setPathMatcher

Set the PathMatcher implementation to the URL path to match the registered URL pattern. The default is AntPathMatcher.

public void setPathMatcher(PathMatcher pathMatcher) {
	Assert.notNull(pathMatcher, "PathMatcher must not be null");
	this.pathMatcher = pathMatcher;
	this.corsConfigSource.setPathMatcher(pathMatcher);
}
Copy the code

3.10 setInterceptors

Set up an interceptor to apply all handlers of this handler map map. The supported interceptor types are HandlerInterceptor, WebRequestInterceptor, and MappedInterceptor. Mapping interceptors only apply to urls where the request matches its path pattern. Mapped interceptor beans are also detected by type during initialization.

ublic void setInterceptors(Object... interceptors) {
	this.interceptors.addAll(Arrays.asList(interceptors));
}
Copy the code

The other get&set methods are not listed, so you can read them for yourself.

4. Create AbstractHandlerMapping

Because AbstractHandlerMapping inherited WebApplicationObjectSupport class, therefore AbstractHandlerMapping initApplicationContext is created based on template method.

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

As you can see from the method structure, initApplicationContext contains three child processing methods.

  • ExtendInterceptors: This is also a template method that is not implemented in AbstractHandlerMapping (the method body is empty) and is used to provide an entry for subclasses to add (modify) Interceptors (it is not used in existing SpringMVC implementations).

  • DetectMappedInterceptors: Used to add all MappedInterceptor type beans from the SpringMVC container and its parent container to the MappedInterceptors property.

    Detect beans of type MappedInterceptor and add them to the list of mapped interceptors. This method is called in addition to any MappedInterceptors that may be provided through setInterceptors, and by default adds all beans of the MappedInterceptor type from the current context and their ancestors. Subclasses can override and optimize this strategy.

protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
	mappedInterceptors.addAll(
			BeanFactoryUtils.beansOfTypeIncludingAncestors(
					getApplicationContext(), MappedInterceptor.class, true.false).values());
}
Copy the code
  • InitInterceptors: Initializes the specified interceptor, checks the MappedInterceptors and adjusts the HandlerInterceptors and WebRequestInterceptors as needed. (Current Spring version 4.3.6)
protected void initInterceptors() {
    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

Here’s the 4.1.5 initInterceptors method:

protected void initInterceptors() {
    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");
    		}
    		if (interceptor instanceof MappedInterceptor) {
    			this.mappedInterceptors.add((MappedInterceptor) interceptor);
    		}
    		else{ this.adaptedInterceptors.add(adaptInterceptor(interceptor)); }}}}Copy the code

In version 4.1.5, the job of initInterceptors is to add objects contained in the interceptors attribute to adaptedInterceptors or mappedInterceptors by type. In version 4.1.5, mappedInterceptors is an AbstractHandlerMapping attribute. The main reason is because springMVC has added cross-domain support since 4.2, which is the last two of the properties above. PS: When reading the relevant source code of Spring, you need to pay attention to the changes and differences between different versions, not just one version. In addition, I think reading the source code should focus on the coding method, the use of design mode, design ideas and concepts, rather than just knowing how it is implemented.

The mappedInterceptors need to match the requested URL and are added to the getHandler’s return value, HandlerExecytionChain.

AdaptInterceptor method:

ADAPTS the given interceptor object to the HandlerInterceptor interface. By default, the supported interceptor types are HandlerInterceptor and WebRequestInterceptor. Each of the given WebRequestInterceptor will be encapsulated in the WebRequestHandlerInterceptorAdapter. You can override it in a subclass.

protected HandlerInterceptor adaptInterceptor(Object interceptor) {
	if (interceptor instanceof HandlerInterceptor) {
		return (HandlerInterceptor) interceptor;
	}
	else if (interceptor instanceof WebRequestInterceptor) {
		return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
	}
	else {
		throw new IllegalArgumentException("Interceptor type not supported: "+ interceptor.getClass().getName()); }}Copy the code

5. Get handlers and Interceptor

HandlerMapping uses the getHandler method to get the Handler and Interceptor. Therefore, the abstract base class AbstractHandlerMapping provides a concrete implementation. And in AbstractHandlerMapping, getHandler uses the final keyword, which means that subclasses can no longer override this method.

The purpose of getHandler is to find the handler for a given request and return to the default handler if no specific request is found.

@Override public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Handler = getHandlerInternal(request); // If the previous method did not get it, the default handler is usedif(Handler == null) {// The default handler is the AbstractHandlerMapping attribute that passessetThe resulting value handler = getDefaultHandler(); } // If no Hander is found, Null is returnedif (handler == null) {
		returnnull; } // Bean name or resolved handler? // If a String handler is found,ifHandlerName = (String) handler; handler = getApplicationContext().getBean(handlerName); } // Create a HandlerExecutionChain object based on handler and request.  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if(CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig ! = null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); }return executionChain;
}
Copy the code

GetHandlerInternal:

protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
Copy the code

Finds the handler for the given request, and returns NULL if the particular request cannot be found. This method is called by getHandler; If the null return value is set, the default handler will result. On a CORS pre-flight request, this method should return a match that does not match the pre-flight request, but instead, based on the URL path, HTTP methods and Headers in the “access-Control-request-method” header are obtained from the “access-Control-request-headers” header, allowing CORS configurations to be obtained through getCorsConfigurations, Note: This method can also return a pre-built HandlerExecutionChain that combines a handler object with a dynamically determined interceptor. The state-specified interceptors will be merged into the existing chain.

GetHandlerExecutionChain:

GetLookupPathForRequest: Returns the mapping lookup path for a given request, if applicable, in the current servlet map, or in the Web application. If the include request is called in the RequestDispatcher, the include request URL is detected.

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest Request) {// If the handler is of type HandlerExecutionChain, the handler is of type HandlerExecutionChain. HandlerExecutionChain = (Handler instanceof HandlerExecutionChain) ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); / / return a given request mapping lookup path String lookupPath = this. UrlPathHelper. GetLookupPathForRequest (request); // Iterate over the current adaptedInterceptors listfor(HandlerInterceptor interceptor: this. AdaptedInterceptors) {// If it is of the MappedInterceptor typeif(interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; // Whether the interceptor applies to the given request path, and returns if sotrue
			if(mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); }}else{ chain.addInterceptor(interceptor); }}return chain;
}
Copy the code

Build a HandlerExecutionChain for the given handler, including available interceptors. The default implementation builds the standard HandlerExecutionChain with the given handler, a generic interceptor for the handler map, and any MappedInterceptors that match the current request URL. Interceptors are added in the order in which they are registered. To expand/rearrange the list of interceptors, subclasses can override it.

It is important to note that the handler object passed in May be either a primitive handler or a pre-built HandlerExecutionChain. This method should explicitly handle both cases, creating a new HandlerExecutionChain or extending an existing chain. In order to simply add a custom subclass interceptors, can consider to call super. GetHandlerExecutionChain (handler, request) and call on the object returned by the chain HandlerExecutionChain# addInterceptor.

GetCorsHandlerExecutionChain:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, CorsConfiguration config) {// Check whether the request is pre-requested by using the HTTP method options in the request header.if(CorsUtils.isPreFlightRequest(request)) { HandlerInterceptor[] interceptors = chain.getInterceptors(); = new HandlerExecutionChain(new PreFlightHandler(config), interceptors); }else{// If it is a normal request, add a CorsInterceptor. chain.addInterceptor(new CorsInterceptor(config)); }return chain;
}
Copy the code

Update HandlerExecutionChain for CORS (HTTP Access Control: Cross-domain Resource sharing) related processing.

  • For pre-flight requests, the default implementation replaces the selected handler with a simple HttpRequestHandler that calls the configured setCorsProcessor. (Replace the processor with the inner PreFlightHandler class)
  • For normal requests, the default implementation inserts a HandlerInterceptor, which performs CORS related checks and adds the CORS header. (Add CorsInterceptor interceptor)

Two inner classes in AbstractHandlerMapping

These two inner classes are used to verify that the request is CORS and encapsulate the corresponding Adapter.

  • PreFlightRequest is an adapter of CorsProcessor to HttpRequestHandler. Such HandlerAdapter use HttpRequestHandlerAdapter processing directly.
  • CorsInterceptor is CorsProcessor for HandlerInterceptorAdapter adapter.

The specific class information is as follows:

PreFlightHandler

private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {

	private final CorsConfiguration config;

	public PreFlightHandler(CorsConfiguration config) {
		this.config = config;
	}

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
		corsProcessor.processRequest(this.config, request, response);
	}

	@Override
	public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
		returnthis.config; }}Copy the code

CorsInterceptor

private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource {

	private final CorsConfiguration config;

	public CorsInterceptor(CorsConfiguration config) {
		this.config = config;
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return corsProcessor.processRequest(this.config, request, response);
	}

	@Override
	public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
		returnthis.config; }}Copy the code

Abstract: AbstractHandlerMapping AbstractHandlerMapping is an AbstractHandlerMapping template that defines the HandlerMapping function. However, the detailed implementation needs to be examined in subclasses. HandlerExecutionChain, CORS cross-domain and other issues involved in this part will be covered in separate sections depending on the actual situation.

If you have any comments or suggestions, leave them in the comments section below, or send us an email ([email protected])! Welcome friends to communicate with us and grow up together.