Introduction to the

In the previous article, I outlined how Spring maps request paths to processing methods, but map-related initialization is still a mystery to us

This article will explore how the mapping of request paths and processing methods is initialized

An overview of

Based on the previous article: Spring source Parsing – SpringWeb request mapping parsing

This article was intended to be finished earlier, but it has been stuck and failed to find the desired entry point. Fortunately, I located the relevant key code around Thursday and initially explored the relevant initialization code process

Next, I will show the positioning and parsing process during this period. The following is my exploration process during this period:

  • Trying to locate the initialization of handlerMappings, but not locating the request URL and handler related initializations
  • Go back and look at handlerMappings and see what it has. There is no custom HelloWorld in this Map
  • Aware of key RequestMappingHandlerMapping, tracking send only in the type matching success
  • Review last request mapping parsing, the RequestMappingHandlerMapping meticulous initialization code
  • Key code for successfully finding the relevant path and handling method initialization

Let’s take a closer look:

The source code parsing

Initial exploration initialization: misguided

In class: dispatcherServlet.java, we locate the key code that mappedHandler gets

@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) {
			returnhandler; }}}return null;
}
Copy the code

It is traversal: handlerMappings, thus tracing the handlerMappings initialization process

I failed to find what I was looking for and found nothing related to the custom class HelloWorld

This piece has a lot of code, there are a lot of things in it, but I won’t show it here, interested elder brother can explore

Review request map lookup matches: a rude awakening

Exploring handlerMappings is fruitless, so go back to the above iterating process

HandlerMappings is basically fixed after debugging and contains the following classes:

  • this.handlerMappings
    • RequestMappingHandlerMapping
    • BeanNameUrlHandlerMapping
    • RouterFunctionMapping
    • SimpleUrlHandlerMapping
    • WelcomePageHandlerMapping

And matching of success is: RequestMappingHandlerMapping, its return to the HelloWorld we want processing method

Debugging is very confused why the initial initialization of these several, and then set a layer of request matching, the current knowledge is not enough to crack, can only be explored later

Then began to comb the RequestMappingHandlerMapping request match, a key code match below:

    # AbstractHandlerMethodMapping.java
    @Nullable
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
        // Get the match 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); }... }Copy the code

This is done in the code above, where the method is simple and crude:

    # AbstractHandlerMethodMapping.java -- MappingRegistry
    @Nullable
	public List<T> getMappingsByDirectPath(String urlPath) {
		return this.pathLookup.get(urlPath);
	}
Copy the code

The key point then is to initialize this.mappingRegistry, find the initialized code, and set the breakpoint

Thought it was in the class: during AbstractHandlerMethodMapping in initial, put a breakpoint in the following functions:

    # 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);
	}
Copy the code

But they couldn’t get in, so they looked directly in the inner class they defined: MappingRegistry, and successfully located the key code they wanted

Request mapping key code location: a silver lining

MappingRegistry: MappingRegistry: MappingRegistry: MappingRegistry

Found the previous: this.pathLookup related add operation

    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,
				newMappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig ! =null));
			}
		finally {
			this.readWriteLock.writeLock().unlock(); }}Copy the code

After the application was restarted, we did get to the breakpoint we had set, and by analyzing the call stack, we did find the key code for the request mapping

We’ll look at the call stack from the bottom line:

Application startup correlation

Start by familiarizing yourself with Spring startup related code, which I believe you’ve read many times when trying to read the source code

Tracking found in: DefaultListableBeanFactory preInstantiateSingletons method in a class, a large section of the nested loop, and wish this code can now optimization?

    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 the bottom
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments); . }catch (Throwable var10) {
            ......
            this.handleRunFailure(context, var10, listeners);
            throw newIllegalStateException(var10); }... }public void refresh(a) 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{... }... }}Copy the code

RequestMappingHandlerMapping related initialization

Continuing below, you see the CreateBean and afterPropertiesSet properties

    # AbstractAutowireCapableBeanFactory.class
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {...try {
            / / initialize the RequestMappingHandlerMapping 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) {
                    throwvar6.getException(); }}else {
                // Enter the operations related to the request mapping((InitializingBean)bean).afterPropertiesSet(); }}... }Copy the code

Request mapping initialization

Follow along and see the Controllers looped through the code.

    # AbstractHandlerMethodMapping.java
    @Override
	public void afterPropertiesSet(a) {
        // Initializes the request mapping
		initHandlerMethods();
	}

    protected void initHandlerMethods(a) {
        // Go through all the custom Controllers and define a new one
		for (String beanName : getCandidateBeanNames()) {
			if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// Here you see the HelloWorld we defined
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

    protected String[] getCandidateBeanNames() {
		return (this.detectHandlerMethodsInAncestorContexts ?
				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
				obtainApplicationContext().getBeanNamesForType(Object.class));
	}
Copy the code

As you follow along, you see the code for the specific request path in the fetch class below, and the code for initializing the specific request map

    # 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)) {
            // Get the Controller Bean entrydetectHandlerMethods(beanName); }}protected void detectHandlerMethods(Object handler) { Class<? > handlerType = (handlerinstanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if(handlerType ! =null) {
            // Handle all Controllers methodClass<? > 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);
                / / registerregisterHandlerMethod(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) {
                // Add mapping
				this.pathLookup.add(path, mapping); }}finally {
			this.readWriteLock.writeLock().unlock(); }}Copy the code

conclusion

After a period of exploration, we finally get the rough request path map initialization code

  • 1. The application starts up, the initialization: RequestMappingHandlerMapping
  • 2. Request path in the RequestMappingHandlerMapping initialization

After debugging, we also found that although the RequestMappingHandlerMapping is initialized from the start, but loaded into handlerMappings is the first time I request to load in

This article gives you some rough code for requesting path initialization, but there are many details worth exploring, such as the handling of methods in beans

I have written some DI and Web related demos before, which stopped at servlets and got stuck in request mapping initialization and matching. This gave me some ideas, and I will look at this code in detail later to improve the previous Demo

Refer to the link

  • Spring source code parsing series