Spring source code parsing -- spring web request Map initialization

Posted by jds580s on Thu, 20 Jan 2022 11:44:49 +0100

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

Reference link

Topics: Java Spring source code analysis