Controller registration in SpringBoot
This article will start with servlets and see how controllers in web containers are registered in HandlerMapping through source code. Once the request comes in, how the Web container finds the corresponding Controller method based on the request path and executes it.
First, let’s talk about the general idea and flow chart of this paper:
- We use it all the time
RequestMapping
This annotation corresponds to a method that will eventually beRequestMappingHandlerMapping
Process, and encapsulate into oneHandlerMethod
Injected into itselfmappingRegistry
In the container. This step is the registration of the Controller, and the trigger is performed becauseRequestMappingHandlerMapping
This class implementsInitializingBean
Interface, triggered by the Spring container. - When the Tomcat container is started, the Servlet init method is finally called, where all the
HandlerMapping
Registered to their own internalhandlerMappings
Attribute. This creates an indirect relationship between the Servlet and the RequestMapping-annotated Controller. - When a request arrives, Tomcat calls the Servlet’s Service method after it gets the request body and wraps it. This method will eventually go to
DispatcherServlet
thedoDispatch
Method, this method will find the best fitHandlerMapping
And pull out the correspondingHadlerMethod
And then give the correspondingHandlerAdapter
The execution. - Controller Registration Flowchart
5. Flow chart of controller discovery and use The text start
DispatcherServlet to handle requests
Servlet interface source code
public interface Servlet {
/ / initialization
public void init(ServletConfig config) throws ServletException;
// Respond to the request
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// Get servlet information
public String getServletInfo(a);
// Callback when the service stops
public void destroy(a);
}
Copy the code
Springboot has a Built-in Tomcat container, which follows the Servlet specification. The servlet specification defines callback hooks for initialization, response, getting configuration information, and destruction. As can be seen from the servlet specification, the Init method of the servlet is called when Tomcat is started, the Service method is called when the request is processed, and the destroy method is called when the container is destroyed. Servlet in the most core implementation is we know DispatchServlet, take a look at the DispatchServlet inheritance system
Take a look at what the initialization of the Servlet does from the DispatchServlet inheritance architecture.
Initialization init for the Servlet
HttpServletBean init method source
@Override
public final void init(a) throws ServletException {
// Set the properties of the servlet
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if(! pvs.isEmpty()) {try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throwex; }}// The specific initialization method is left to the subclass implementation
initServletBean();
}
// Empty implementation, specific by the subclass implementation
protected void initServletBean(a) throws ServletException {}Copy the code
As you can see from the init method in HttpServletBean, the core here is to set some bean properties for the Servlet. Continue to subclass FrameworkServlet to see the initServletBean method
@Override
protected final void initServletBean(a) throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + "'" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// This initializes the Web context, which initializes the nine policies of the Servlet
this.webApplicationContext = initWebApplicationContext();
// This method is also an empty implementation
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); }}// Initialize the context
protected WebApplicationContext initWebApplicationContext(a) {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext ! =null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if(! cwac.isActive()) {if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); }}}if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
// SpringBoot will only enter this method, which is an empty implementation in the subclass DispatchServletonRefresh(wac); }}if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
Copy the code
Then follow the onRefresh method of DispatchServlet, which initializes the nine DispatchServlet policies. Here we are only concerned with the initHandlerMappings method
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// Initialize the policy
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
// This is the core of our concern, the Controller registration is implemented here
initHandlerMappings(context);
// This will handle Controller method calls. The logic is similar to the initHandlerMappings described above
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
Copy the code
Take a look at the initHandlerMappings method
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// Defaults to true
if (this.detectAllHandlerMappings) {
/ / the default HandlerMapping eight, here we only care about RequestMappingHandlerMapping the class
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true.false);
if(! matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList<>(matchingBeans.values());
/ / sorting
AnnotationAwareOrderComparator.sort(this.handlerMappings); }}else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
// Let Serlvet establish an indirect relationship with the Controller. The main purpose of this method is to assign a value to the handlerMappings attribute
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
// If there is no HanlderMapping, a default is given
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties"); }}}Copy the code
Take a look at the default HandlerMapping
All we care about here isRequestMappingHandlerMapping
This class, this class is the one that handles RequestMapping annotations on our Controller.
Note the “handlerMappings” statement, which will select the most appropriate handlerMappings from the “handlerMappings” list to process the request
The Servlet’s request handles service
HttpServlet service method source
/** * This method simply converts a ServletRequest to HttpServletRequest * ServletResponse to HttpServletResponse */
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// Now look at this
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Get the method type
String method = req.getMethod();
// Call different methods according to different method types, from doGet, see subclass FrameworkServlet doGet methods
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else{ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); }}}else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); }}// doGet method for FrameworkServlet
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/ / to continue with
processRequest(request, response);
}
rotected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// This is where the request is handled. Continue with the doService method of subclass DispatchServlet
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if(requestAttributes ! =null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); }}Copy the code
DoService method of DispatchServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Prints logs
logRequest(request);
// Save the snapshot
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = newHashMap<>(); Enumeration<? > attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); }}}// Set the properties
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager ! =null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if(inputFlashMap ! =null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// Process the request core method
doDispatch(request, response);
}
finally {
if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if(attributesSnapshot ! =null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}}Copy the code
DoDispatch method of DispatchServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try{ processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest ! = request);// Get the handler for the current request
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
/ / 404
noHandlerFound(processedRequest, response);
return;
}
// Get the handlerAdapter that handles the current request
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return; }}// Call the pre-interceptor
if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// The method corresponding to the request is executed here
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// Call the post-interceptor
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if(mappedHandler ! =null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }}else {
// Clean up any resources used by a multipart request.
if(multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings ! =null) {
for (HandlerMapping mapping : this.handlerMappings) {
/ / the mapping is the RequestMappingHandlerMapping next to talk about
HandlerExecutionChain handler = mapping.getHandler(request);
if(handler ! =null) {
returnhandler; }}}return null;
}
Copy the code
Here you can record the chain of calls for the Servlet to process the request service -> doGet -> processRequest -> doService -> doDispatch
RequestMappingHandlerMapping did what
Can be seen from the above inheritance figure RequestMappingHandlerMapping the InitializingBean interface, so the initialization will call the afterPropertiesSet method.
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet(a) {
/ / configuration
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
// Here is the core, which registers all controllers
super.afterPropertiesSet();
}
Copy the code
Then the parent class AbstractHandlerMethodMapping afterPropertiesSet method
@Override
public void afterPropertiesSet(a) {
initHandlerMethods();
}
protected void initHandlerMethods(a) {
for (String beanName : getCandidateBeanNames()) {
if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// The controller is registered here
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) { Class<? > beanType =null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex); }}// The isHandler here determines whether there is a Controller or RequestMapping annotation
if(beanType ! =null && isHandler(beanType)) {
// The controller will be registered heredetectHandlerMethods(beanName); }}protected void detectHandlerMethods(Object handler) {
// Get the typeClass<? > 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 {
// This converts RequestMapping to RequestMappingInfo
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]."+ method, ex); }});if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// Register with mappingRegistry, then get the corresponding HandlerMethod according to requestregisterHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code
RequestMapping is converted to RequestMappingInfo
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class
handlerType) {
// Get the RequestMapping on the method and convert it to RequestMappingInfo
RequestMappingInfo info = createRequestMappingInfo(method);
if(info ! =null) {
// Get the class RequestMapping and convert it to RequestMappingInfo
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if(typeInfo ! =null) {
// Merge RequestMapping on the method and RequestMapping on the class, where the URL is merged
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
This method converts the RequestMapping on the method to RequestMappingInfo and the class to RequestMappingInfo. Then combine the two RequestMappingInfo into one (merge of urls).
Registration of HandlerMethod
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
// This is registered directly to mappingRegistry, and later obtained directly from mappingRegistry
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
if(KotlinDetector.isKotlinType(method.getDeclaringClass())) { Class<? >[] parameterTypes = method.getParameterTypes();if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: "+ method); }}this.readWriteLock.writeLock().lock();
try {
/ / create HandlerMethod
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if(getNamingStrategy() ! =null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if(corsConfig ! =null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
// This will be added to MappingRegistry
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock(); }}Copy the code
The important thing to note here is that the Controller registered is the HandlerMethod registered directly. The HandlerMethod is the method that corresponds to the specific request in the Controller class. This object encapsulates all the information. After we get the HandlerMethod, we call the specific method through reflection
Look into the RequestMappingHandlerMapping getHandler method, this method is realized in the superclass AbstractHandlerMapping, here with the help of the template method design pattern.
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
/ / here for the real processor, subclass AbstractHandlerMethodMapping implementation
Object handler = getHandlerInternal(request);
if (handler == null) {
// If not, use the default
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Get the real instance from the IOC container if it is beanName
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Get the chain of interceptors for the request
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if(logger.isDebugEnabled() && ! request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource ! =null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); config = (config ! =null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
Copy the code
The core focus here is on two methods: the getHandlerInternal method to get the processor, and the getHandlerExecutionChain method to get the corresponding interceptor chain
AbstractHandlerMethodMapping getHandlerInternal method
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return(handlerMethod ! =null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock(); }}@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if(directPathMatches ! =null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if(! matches.isEmpty()) { Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "' : {" + m1 + "," + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}Copy the code
Can be seen from the above methods, finally from mappingRegistry attributes to retrieve HandlerMethod, mappingRegistry in the RequestMappingHandlerMapping above has a detailed explanation
AbstractHandlerMapping getHandlerExecutionChain method
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// All interceptors will be taken and the appropriate interceptors will be matched through the path
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); }}else{ chain.addInterceptor(interceptor); }}return chain;
}
Copy the code
So here we have the chain of interceptors and the method that responds to the request. Next is the method is called, here the HandlerAdapter turn appeared, how to acquire the method getHandlerAdapter RequestMappingHandlerAdapter is skipped here
Back to the doDispatch method of DispatchServlet
/ / ha is RequestMappingHandlerAdapter class here
/ / core is RequestMappingHandlerAdapter handleInternal method of a class
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
Copy the code
RequestMappingHandlerAdapter handleInternal method of a class
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if(session ! =null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// This is where HandlerMethod is actually calledmav = invokeHandlerMethod(request, response, handlerMethod); }}else {
// This is where HandlerMethod is actually calledmav = invokeHandlerMethod(request, response, handlerMethod); }}else {
// This is where HandlerMethod is actually called
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if(! response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else{ prepareResponse(response); }}return mav;
}
Copy the code
At this point, the whole call process is over. In which the HandlerAdapter registration, acquisition, processing request reflection call HandlerMethod and so on will be analyzed in the following chapter.