brief introduction
In the previous article, we roughly analyzed how Spring maps the request path to the processing method, but the initialization related to the mapping is still a fog for us
This article will explore how to initialize the mapping between request path and processing method
overview
Based on the previous article: Spring source code parsing - spring web request mapping parsing
This article was originally intended to be finished earlier, but it was stuck and did not find the desired entry point. Fortunately, it located the relevant key code around Thursday and preliminarily explored the code process of relevant initialization
Next, I will show the positioning and analysis process during this period. The following is my exploration process during this period:
- I want to locate the initialization of handlerMappings, but I don't locate the initialization related to the request URL and processing method
- Looking back at handler mappings, I found that there is no custom HelloWorld in this Map
- Aware of the key RequestMappingHandlerMapping, trace sending is successfully matched only in this type
- Review the request mapping analysis in the previous article, and carefully initialize the relevant code in requestmapping handler mapping
- Successfully find the relevant path and processing method initialization key code
Next, let's take a closer look at:
Source code analysis
Initial exploration and initialization: go astray
In class: dispatcherservlet In Java, we locate the key code obtained by mappedHandler
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
It just traverses: handlerMappings, so it tracks the initialization process of handlerMappings
As a result, I failed to find what I wanted and found nothing related to the custom class: HelloWorld
There are a lot of code and many things in it, but they are not shown here. Interested brothers can explore by themselves
Review request mapping find match: wake up
Exploring handler mappings failed, so I went back to the above section of traversal processing
After debugging, handlerMappings are basically fixed, including the following classes:
- this.handlerMappings
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- RouterFunctionMapping
- SimpleUrlHandlerMapping
- WelcomePageHandlerMapping
The successful matching is: RequestMappingHandlerMapping, which returns the processing method we want HelloWorld
During debugging, I wonder why we need to initialize these at the initial stage, and set another layer of request matching. The current knowledge is not enough to crack, so we can only explore later
So we began to sort out the request matching of RequestMappingHandlerMapping. In the following key code, the matching was successful:
# AbstractHandlerMethodMapping.java @Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); // I got the matching result here List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } ....... }
In the above code, the matching is successful. The matching method is very simple:
# AbstractHandlerMethodMapping.java -- MappingRegistry @Nullable public List<T> getMappingsByDirectPath(String urlPath) { return this.pathLookup.get(urlPath); }
So the key point is this Initialize mappingregistry, find the initialization code and mark the breakpoint
During this period, it is assumed that it is the initial in the class: AbstractHandlerMethodMapping, and a breakpoint is marked in the following function:
# AbstractHandlerMethodMapping.java public void setPatternParser(PathPatternParser patternParser) { Assert.state(this.mappingRegistry.getRegistrations().isEmpty(), "PathPatternParser must be set before the initialization of " + "request mappings through InitializingBean#afterPropertiesSet."); super.setPatternParser(patternParser); } public void registerMapping(T mapping, Object handler, Method method) { if (logger.isTraceEnabled()) { logger.trace("Register \"" + mapping + "\" to " + method.toGenericString()); } this.mappingRegistry.register(mapping, handler, method); }
But I couldn't get in, so I searched directly in its defined internal class: MappingRegistry, and successfully located the key code I wanted
Request mapping key code positioning
After reading the relevant code of class: MappingRegistry, we found the following methods and can. We directly hit the breakpoint and restart the program:
Found the front: this Add operations related to pathlookup
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 code is the key 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, new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); } finally { this.readWriteLock.writeLock().unlock(); } }
After the application was restarted, we successfully came to the breakpoint we marked. By analyzing the call stack, we did find the key code of request mapping
We will analyze the call stack from the following network:
Application startup related
At the beginning, I was familiar with Spring startup. I believe you have read these codes many times when trying to read the source code
Trace found in: defaultlistablebeanfactory There is a large nested loop in the preInstantiateSingletons method of class. I wonder if this code can be optimized at present?
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args); } public static void main(String[] args) throws Exception { run(new Class[0], args); } public ConfigurableApplicationContext run(String... args) { ...... try { ...... this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); // Enter from below this.refreshContext(context); this.afterRefresh(context, applicationArguments); ...... } catch (Throwable var10) { ...... this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } ...... } public void refresh() throws BeansException, IllegalStateException { synchronized(this.startupShutdownMonitor) { ....... try { this.postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); beanPostProcess.end(); this.initMessageSource(); this.initApplicationEventMulticaster(); this.onRefresh(); this.registerListeners(); // Enter from here this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var10) { } finally { ...... } ...... } }
RequestMappingHandlerMapping related initialization
Continuing to trace the following, you see the CreateBean and afterpropertieset of the property
# AbstractAutowireCapableBeanFactory.class protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ...... try { // RequestMappingHandlerMapping is initialized here beanInstance = this.doCreateBean(beanName, mbdToUse, args); if (this.logger.isTraceEnabled()) { this.logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (ImplicitlyAppearedSingletonException | BeanCreationException var7) { throw var7; } catch (Throwable var8) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", var8); } } # AbstractAutowireCapableBeanFactory.class protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = bean instanceof InitializingBean; if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (this.logger.isTraceEnabled()) { this.logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } if (System.getSecurityManager() != null) { try { AccessController.doPrivileged(() -> { ((InitializingBean)bean).afterPropertiesSet(); return null; }, this.getAccessControlContext()); } catch (PrivilegedActionException var6) { throw var6.getException(); } } else { // Here you enter the related operations of the request mapping ((InitializingBean)bean).afterPropertiesSet(); } } ...... }
Request map initialization
Continue tracking and look at the code related to circular traversal of Controllers (there are still many details to be clarified. I'll continue later and sort out the main line first)
# AbstractHandlerMethodMapping.java @Override public void afterPropertiesSet() { // Initialize request mapping initHandlerMethods(); } protected void initHandlerMethods() { // Traverse all custom Controllers, and then define a controller yourself for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // Here we see the HelloWorld we defined processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } protected String[] getCandidateBeanNames() { return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); }
Continue to track, and see the code related to the specific request path in the following acquisition class, and the code mapped to the specific initialization request
# AbstractHandlerMethodMapping.java protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { // Entry after obtaining Controller Bean detectHandlerMethods(beanName); } } protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { // Process all controllers methods 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); } }); if (logger.isTraceEnabled()) { logger.trace(formatMappings(userType, methods)); } else if (mappingsLogger.isDebugEnabled()) { mappingsLogger.debug(formatMappings(userType, methods)); } methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); // register registerHandlerMethod(handler, invocableMethod, mapping); }); } } 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) { // Map add this.pathLookup.add(path, mapping); } } finally { this.readWriteLock.writeLock().unlock(); } }
summary
After a period of exploration and sorting, we finally got the general request path mapping initialization code
- 1. When the application starts, initialize: RequestMappingHandlerMapping
- 2. Request path initialization in requestmappinghandlermapping
After debugging, we also found that although RequestMappingHandlerMapping was initialized at the beginning, it was loaded into handlerMappings only when the first request was made
Although this article has obtained the general request path initialization code, many details are worth exploring, such as the processing of Method in Bean
I wrote some DI and Web related demos before, stopped in the Servlet and got stuck in the request mapping initialization and matching. This gave me some ideas. I'll take a detailed look at this code later to improve the previous Demo