Do you know what data structure is used to store API related information when SpringBoot starts? (Part I)


Cover: clouds on the school basketball court

I feel shallow on paper. I never know that I have to practice it

Note: the version of SpringBoot in this article is 2.5.2; JDK version is jdk 11

Follow up articles 👉 When sending a request from the browser to the SpringBoot backend, how exactly do you find which interface? (Part II)

preface:

When writing an article, I will habitually record what factors prompted me to write this article. And even for the things you are interested in, writing is also more convenient, and the quality of the article is correspondingly higher. Of course, I want to share my views with more people and communicate with more people. "Three people, there must be my teacher", welcome to leave a message, comment and exchange.

The reason for writing this article is that yesterday, a back-end partner learning Go language asked me a question.

The questions are as follows:

Why does the browser know which interface to look for when it makes a request to the back end? What matching rules are used?

How does the SpringBoot backend store API interface information? What data structure is used to store it?

@ResponseBody
@GetMapping("/test")
public String test(){
    return "test";
}

To tell the truth, after listening to him, I feel that I am not enough. I can't answer one of my soul torture. Let's go and have a look.

My reading experience on the framework source code of SpringBoot may be one 👉 SpringBoot automatic assembly principle Yes, so to some extent, my personal understanding of the SpringBoot framework is very simple.

If there are deficiencies in the article, please correct it in time! Thank you very much.

1, Annotation derived concepts

In the java system, classes can be inherited and interfaces can be implemented. However, annotations do not have these concepts, but have A derived concept. For example, note A. If it is marked on the header of annotation B, we can say that annotation B is the derivation of annotation A.

For example:

Just like the annotation @ GetMapping has a @ RequestMapping(method = RequestMethod.GET), so we essentially use the @ RequestMapping annotation.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {

}

The same is true for @ Controller and @ RestController.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
}

No more nonsense, just the liver.

2, Start process

Let's go straight to the entrance instead of exploring.

I made a general analysis flow chart for your reference, which is also my personal exploration route.

2.1,AbstractHandlerMethodMapping

/** HandlerMapping The abstract base class of the implementation defines the mapping between the request and the HandlerMethod.
For each registered handler method, a unique mapping is maintained by subclasses that define mapping type < T > details 
*/
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
    
    // ...
    
    /**Detect handler methods on initialization. It can be said to be the entrance*/
    @Override
    public void afterPropertiesSet() {
        initHandlerMethods();
    }
    /**Scan bean s in ApplicationContext, detect and register handler methods. */
    protected void initHandlerMethods() {
        //getCandidateBeanNames(): determines the name of the candidate bean in the application context.
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                //Determine the type of the specified candidate bean. If it is identified as the handler type, call detectHandlerMethods 
                // The handler here is the interface methods we write in the controller
                processCandidateBean(beanName);
            }
        }
        // The logic here will not be discussed
        handlerMethodsInitialized(getHandlerMethods());
    }
    
    // ...
}

Only when it is scanned that it is modified by @ RestController or @ RequestMapping annotation, enter the processcandidabean method, which is what we are looking for. Other beans are not the point of our discussion and will not be discussed.

Let's take a look at the processing logic of processcandidate bean and what it does.

/** Determines the type of the specified candidate bean. If it is identified as a handler type, detectHandlerMethods is called.	 */
protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        // Determine the type of bean to inject
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {
        // Unresolved bean
        if (logger.isTraceEnabled()) {
            logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
        }
    }
	//The isHandler method determines whether it is a web resource class.
    if (beanType != null && isHandler(beanType)) {
        // It's the focus of this line
        detectHandlerMethods(beanName);
    }
}

The isHandler method determines whether it is a web resource class. When a class is marked with @ Controller or @ RequestMapping. Note that @ RestController is a derived class of @ Controller. So here, just judge @ Controller or @ RequestMapping.

In addition, isHandler is defined in abstracthandlermethodmapping < T >, and implemented in RequestMappingHandlerMapping

/**
Whether the given type is a handler with handler methods. The handler is the interface method in the Controller class we wrote
 The handler is expected to have either a type level Controller comment or a type level RequestMapping comment.
*/
@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

Continue:

2.2. detectHandlerMethods() method

This method detectHandlerMethods(beanName); What does it do?

Its method annotation is to find the handler method in the specified handler bean.

In fact, the detectHandlerMethods Method is the logic that really starts to parse the Method. By parsing @ RequestMapping or other derived annotations on the Method. Generate request information.

/** Finds the handler method in the specified handler bean.*/
	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
            //Return the user-defined class of the given class: usually only the given class, but if it is a subclass generated by CGLIB, the original class is returned.
			Class<?> userType = ClassUtils.getUserClass(handlerType);
            
            //selectMethods:
            //Select the method of a given target type according to the search of relevant metadata.
		  // The caller defines the method of interest through the MethodIntrospector.MetadataLookup parameter, allowing the associated metadata to be collected into the result map
            // Simple understanding: parsing RequestMapping information
			Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
					(MethodIntrospector.MetadataLookup<T>) method -> {
						try {
                            	//Provides mappings for handler methods. The method for which mapping cannot be provided is not a handler method
							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));
			}
            // Here, the parsed information is registered circularly
			methods.forEach((method, mapping) -> {
				Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
				registerHandlerMethod(handler, invocableMethod, mapping);
			});
		}
	}

2.3,getMappingForMethod

getMappingForMethod is defined in abstracthandlermethodmapping < T >, and implemented under the RequestMappingHandlerMapping class

This is simply to combine the class level RequestMapping with the method level RequestMapping (createRequestMappingInfo)

/** Create a RequestMappingInfo using the RequestMapping annotation at the method and type levels. */
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
        //Get on class 
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}

createRequestMappingInfo:

/**
Delegate createRequestMappingInfo(RequestMapping, RequestCondition) to provide an appropriate custom RequestCondition according to whether the annotatedElement provided is a class or method.
*/
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    //It mainly analyzes the @ RequestMapping information on the Method
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class ?
                                     getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

2.4. MethodIntrospector.selectMethods() method

Select the method of a given target type according to the search of relevant metadata.

There are many miscellaneous things in it. It's difficult to explain clearly. I'll just say it briefly here.

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
    final Map<Method, T> methodMap = new LinkedHashMap<>();
    Set<Class<?>> handlerTypes = new LinkedHashSet<>();
    Class<?> specificHandlerType = null;

    if (!Proxy.isProxyClass(targetType)) {
        specificHandlerType = ClassUtils.getUserClass(targetType);
        handlerTypes.add(specificHandlerType);
    }
    handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));

    for (Class<?> currentHandlerType : handlerTypes) {
        final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
        //Performs a given callback operation on all matching methods of a given class and superclass (or a given interface and superinterface).
        ReflectionUtils.doWithMethods(currentHandlerType, method -> {
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
            T result = metadataLookup.inspect(specificMethod);
            if (result != null) {
                // BridgeMethodResolver: returns the bridged Method given a composite bridge Method. 
                //When extending a parameterized type whose Method has parameterized parameters, the compiler may create a bridge Method. During runtime calls, you can invoke and / or use bridging methods through reflection 
                //findBridgedMethod: find the original method of the bridge Method provided.
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
                if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
                    methodMap.put(specificMethod, result);
                }
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }
    return methodMap;
}

doc comment on method:

Select the method of a given target type according to the search of relevant metadata.
The caller defines the method of interest through the MethodIntrospector.MetadataLookup parameter, allowing the associated metadata to be collected into the result map

I can't speak clearly at a glance. I'll directly post a debug picture for you to see.

2.5. registerHandlerMethod

The essence of this code is to register the parsed information circularly

methods.forEach((method, mapping) -> {
    //Select the callable method on the target type: if it is actually exposed on the target type, give the method itself, or one of the interfaces of the target type or the corresponding method on the target type itself.
   // Simply understand how to return a method
    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    registerHandlerMethod(handler, invocableMethod, mapping);
});
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

this.mappingRegistry here is an internal class of abstracthandlermethodmapping < T >.

MappingRegistry: doc note: a registry that maintains all mappings to handler methods, exposes methods that perform lookups, and provides concurrent access.

Its structure is not discussed here. If you are interested, you can click in and continue to have a look.

We continue to explore what our register method does

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        //Create a HandlerMethod instance.
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        //Validation method mapping
        validateMethodMapping(handlerMethod, mapping);

        //Here is to directly obtain the path. The value of mapping is GET[/login]
        // After it is obtained, it is / login
        Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
        for (String path : directPaths) {
            //this.pathLookup is defined as follows:
            //	 private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
          	//   In fact, new is a new LinkedHashMap < > ();
            // Here is to save path as key and mapping as value
            this.pathLookup.add(path, mapping);
        }

        String name = null;
        // The meaning here can be summarized as follows:
        if (getNamingStrategy() != null) {
            ///Determines the name of the given HandlerMethod and mapping.
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        // The following lines deal with cross domain issues, which are not discussed in this chapter. If you are interested, you can go and have a look.
        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();
    }
}
this.registry.put(mapping,
                          new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));

The definition of this.registry here is as follows: private final map < T, mappingregistration < T > > registry = new HashMap < > ();

Different ways to get here, in fact, the difference is not very big

In fact, after reading this startup process, we can probably find two answers to our first three questions.

2.6 summary

How does your SpringBoot backend framework store API interface information? What data structure is used to store it?

The first answer: it is roughly related to the registry class MappingRegistry

The second answer: when we saw that the information is stored by HashMap related classes, we can know that its underlying data structure is array + linked list + red black tree

Note: the version of SpringBoot in this article is 2.5.2;JDK version is jdk 11

There is no comparison between multiple versions, but presumably, most of them are

So our next step is to see how to find the corresponding interface when spring boot requests. Where is our focus.

3, Summary process

  1. Scan all registered beans
  2. Traverse these beans, judge whether they are processors in turn, and detect their HandlerMethod
  3. Traverse all the methods in the Handler to find the methods marked by the @ RequestMapping annotation.
  4. Gets the @ RequestMapping instance on the method.
  5. Check whether the class to which the method belongs has @ RequestMapping annotation
  6. Combine class level RequestMapping with method level RequestMapping (createRequestMappingInfo)
  7. Register circularly, and it will be used again when requesting

4, Follow up

Follow up articles 👉 When sending a request from the browser to the SpringBoot backend, how exactly do you find which interface?

If it weren't for the three questions mentioned by my partner, I don't think I would be so interested. Go to Debug step by step and read the relevant source code. This article may be stillborn.

Thank you very much@ Xiaoyu . To tell you the truth, he invited me to read the ORM framework source code. But we'll have to wait a while.

Personal talk:

In the process of reading the source code, it is really interesting and boring.

After reading some key things, you will be very happy; And like "I forget where the debug is, and my mind is cold again", I will start complaining (I often want to scold a word or two). Then I will continue to force me to see it.

Hello, I'm blogger Ning Zaichun: homepage

A young man who likes literature and art but embarks on the road of programming.

Hope: when we meet on another day, we have achieved something.

In addition, I can only say that I provide a personal opinion here. Due to the lack of writing skills and knowledge, I can't write a very terminological article.

If you think this article makes you gain something, I hope you can praise it and give you encouragement.

I also hope you can communicate actively. If there are deficiencies, please correct them in time. Thank you here.

Digging friends can click here 👉 It's better to dig gold in spring

Keywords: Spring Boot Back-end Spring MVC

Added by mamavi on Tue, 26 Oct 2021 10:08:49 +0300