How does @requestMapping work?

Blog index

Recently I was writing a permission framework, thinking how to get all the URL maps in the project? Let’s talk about how the conclusion was obtained, and more on that later.

Map<RequestMappingInfo, HandlerMethod> map = RequestMappingHandlerMapping.getHandlerMethods(); <RequestMappingInfo> requestMappingInfos = handlermethods.keyset (); // Get all mapping Set<String> mappings =new LinkedHashSet<>();for (RequestMappingInfo info : requestMappingInfos) {
        PatternsRequestCondition patternsCondition = info.getPatternsCondition();
        for(String mapping : patternsCondition.getPatterns()){ mappings.add(mapping); }}Copy the code

Then it comes to mind that MVC runs a process, why can it execute to the corresponding method through a URL? RequestMapping (@requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping, @requestMapping)

Initialization of @requestMapping

RequestMappingHandlerMapping: first of all from the afterPropertiesSet of of this class:

public void afterPropertiesSet(a) {
	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());

    // afterPropertiesSet of the parent class is called
	super.afterPropertiesSet();
}

// Check whether the bean has an @controller or @requestMapping annotation
protected boolean isHandler(Class
        beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

Copy the code

Org. Springframework. Web. Servlet. Handler. AbstractHandlerMethodMapping# afterPropertiesSet initialization method of the parent:

@Override
public void afterPropertiesSet(a) {
	initHandlerMethods();
}

protected void initHandlerMethods(a) {
        //getCandidateBeanNames() gets all registered beans
	for (String beanName : getCandidateBeanNames()) {
		if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// Process candidate beansprocessCandidateBean(beanName); }}// Print the number of handlerMethods
	handlerMethodsInitialized(getHandlerMethods());
}

protected void processCandidateBean(String beanName) { Class<? > beanType =null;
	try {
	    // Get the type of the bean
		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); }}// Select the bean that satisfies isHandler(beanType) and check whether the bean has the @Controller or @requestMapping annotation
	// isHandler(Class
       beanType) unrealized in the class, the specific implementation is in RequestMappingHandlerMapping the posted code above
	if(beanType ! =null&& isHandler(beanType)) { detectHandlerMethods(beanName); }}// Look for methods flagged by @requestMapping in the specified handler
    // This handler is a Controller
	protected void detectHandlerMethods(Object handler) { Class<? > handlerType = (handlerinstanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if(handlerType ! =null) {
		    // Return its own Class, or its parent if the Class is a CGLB proxyClass<? > userType = ClassUtils.getUserClass(handlerType);// Select a method similar to the given target based on the lookup of the associated metadata
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
						    // Select the method marked by @requestMapping. This returns the RequestMappingInfo object in which the mapping is also stored
							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) -> {
			    // Returns a callable method on a given Class
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				// Register the request mappingregisterHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code

RequestMappingInfo – this is also the mapping passed in the registration method below, meaning that the required mapping value is stored in this object.

protected RequestMappingInfo getMappingForMethod(Method method, Class<? RequestMappingInfo info = createRequestMappingInfo(method);if(info ! RequestMappingInfo = null) {// Get method for @requestMapping annotation method on class path value RequestMappingInfotypeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo ! = null) {// concatenate info =typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
			}
		}
		returninfo; } protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition<? > customCondition) {RequestMappingInfo. Builder Builder = RequestMappingInfo / / you can see our path assigned to the paths .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) .methods(requestMapping.method()) .params(requestMapping.params()) .headers(requestMapping.headers()) .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name());if(customCondition ! = null) { builder.customCondition(customCondition); } // Take a look at the builder, where to transfer the path property to?return builder.options(this.config).build();
	}
    public RequestMappingInfo build() { ContentNegotiationManager manager = this.options.getContentNegotiationManager(); // You can see that the path value becomes an attribute pattern for PatternsRequestCondition; , the code here is relatively simple, PatternsRequestCondition patternsCondition = new PatternsRequestCondition(this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(), this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(), this.options.getFileExtensions()); // While PatternsRequestCondition is an attribute of RequestMappingInfo, so the method that gets the mapping finds all the mapping corresponding to the method at the beginning of this articlereturn new RequestMappingInfo(this.mappingName, patternsCondition,
    				new RequestMethodsRequestCondition(this.methods),
    				new ParamsRequestCondition(this.params),
    				new HeadersRequestCondition(this.headers),
    				new ConsumesRequestCondition(this.consumes, this.headers),
    				new ProducesRequestCondition(this.produces, this.headers, manager),
    				this.customCondition);
    	}
Copy the code

Before introducing the registerHandlerMethod(Object,Method,Mapping) Method, We will first introduce several classes of HandlerMethods. From their names, we can know that they are the combination of Handle and Method. This structure preserves handle and corresponding Method.

public class HandlerMethod { // handle private final Object bean; @Nullable private final BeanFactory beanFactory; // handle Class private final Class<? > beanType; // private final Method Method; private final Method bridgedMethod; private final MethodParameter[] parameters; . A few other minor omissionsCopy the code

MappingRegistry is an inner class with several key member variables to hold all the mapping and methods

Class MappingRegistry {// Hold the mapping and MappingRegistration relationship. MappingRegistration private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); Private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); // Save the mapping between url and mapping. This is very important, but there is no external method, so I want to obtain the mapping. Private Final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); Private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); // // saves the relationship between HandlerMethod and CorsConfiguration. Private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();Copy the code

Let’s move on to the core Method registerHandlerMethod(Object,Method,Mapping)

/ / mappingRegistry AbstractHandlerMethodMapping is an abstract class, Protected void registerHandlerMethod(Object handler, Method Method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { // Assert that the handler method is not a suspending one.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); }} / / read/write lock on this. ReadWriteLock. WriteLock (). The lock (); // Create HandlerMethod based on handle and Method. HandlerMethod HandlerMethod = createHandlerMethod(handler, method); // validateMethodMapping(handlerMethod, Mapping); // Store mapping and handlerMethod into map this.mappingLookup. Put (mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping);for(String URL: directUrls) {store url and mapping in urlLookup 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.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
			}
			finally {
				this.readWriteLock.writeLock().unlock();
			}
		}
Copy the code