This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
Cover: Clouds on the school basketball court
The paper come zhongjue shallow, and must know this to practice
Note: This article’s SpringBoot version is 2.5.2; The JDK version is JDK 11.
Follow-up article 👉 How do I find exactly which interface when sending a request from the browser to the SpringBoot back end? (the next)
Preface:
When I write an article, I always make a habit of recording what prompted me to write the article. And actually for the things that are interested in, write also care, also more handy, 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 yan,” welcome to leave a comment exchange.
The reason for writing this article is that yesterday a back-end friend of Go asked me a question.
The questions are as follows:
Why does the browser know which interface it is looking for when it makes a request to the back end? What matching rules are used?
How does the SpringBoot backend store API information? What data structure is it stored in?
@ResponseBody
@GetMapping("/test")
public String test(a){
return "test";
}
Copy the code
To be honest, after listening to him ask, I feel I am not enough volume, it is soul torture, I can not answer. Let’s go and have a look.
I for SpringBoot framework source code reading experience may be a 👉SpringBoot automatic assembly principle is it, so to a certain extent I personally for SpringBoot framework understanding is still very shallow.
If there are deficiencies in the article, please be sure to approve in time! I hereby solemnly thank you.
First, annotation derived concept
It’s a little bit of a premise
In the Java architecture, classes can be inherited and interfaces implemented. But annotations don’t have these concepts, they have a derived concept. For example, note A. Note B is marked at the head of note B, so we can say that note B is derived from note A.
Such as:
Just like the @getMapping annotation also has @requestMapping (method = requestMethod.get), so we are essentially using @requestMapping as well.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
}
Copy the code
Same thing with @Controller and @RestController.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
}
Copy the code
Without further ado, go straight to the liver.
Second, the startup process
We’re going to go straight to the entrance.
I have made a general analysis flow chart for your reference, which is also my personal line of inquiry.
2.1, AbstractHandlerMethodMapping
/** The abstract base class of the HandlerMapping implementation, which defines the mapping between requests and handlerMethods. For each registered handler method, a unique mapping is maintained */ by the subclass that defines the details of the mapping type
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
// ...
/** Checks handler methods at initialization. The entrance, so to speak
@Override
public void afterPropertiesSet(a) {
initHandlerMethods();
}
/** Scans beans in ApplicationContext to detect and register handler methods. * /
protected void initHandlerMethods(a) {
//getCandidateBeanNames() : Determine the name of the candidate bean in the application context.
for (String beanName : getCandidateBeanNames()) {
if(! beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {DetectHandlerMethods is called if it is identified as a handler type
// The handlers here are the interface methods we wrote in controllerprocessCandidateBean(beanName); }}// The logic here is not discussed
handlerMethodsInitialized(getHandlerMethods());
}
// ...
}
Copy the code
Enter the processCandidateBean method only if the scan is modified by the @RestController or @RequestMapping annotation, which is what we’re looking for. Other beans are not our discussion points, so we will not discuss them.
Let’s move on to the processing logic of the processCandidateBean and see what it does.
DetectHandlerMethods is called if detectHandlerMethods is identified as a handler type. * /
protected void processCandidateBean(String beanName) { Class<? > beanType =null;
try {
// Determine the type of bean to inject
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// Unparsed bean
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex); }}// the isHandler method determines if it is a Web resource class.
if(beanType ! =null && isHandler(beanType)) {
// This is the most important part of the routedetectHandlerMethods(beanName); }}Copy the code
The isHandler method determines if it is a Web resource class. When a class is tagged @Controller or @requestMapping. Note that @RestController is a derived class of @Controller. So it’s just @Controller or @requestMapping.
Moreover isHandler definition in AbstractHandlerMethodMapping < T >, realize the RequestMappingHandlerMapping
/** Whether the given type is a handler with a handler method. Handlers are interface methods we write in the Controller class that expect handlers to have type-level Controller annotations or type-level RequestMapping annotations. * /
@Override
protected boolean isHandler(Class
beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
Copy the code
Moving on:
2.2. DetectHandlerMethods () method
DetectHandlerMethods (beanName); What does it do?
Its method annotation reads: find handler methods in the specified handler bean.
The detectHandlerMethods Method really starts parsing Method logic. By parsing @requestMapping or other derived annotations on Method. Generate request information.
/** Finds handler methods in the specified handler bean. * /
protected void detectHandlerMethods(Object handler) { Class<? > handlerType = (handlerinstanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if(handlerType ! =null) {
// Return the user-defined class of the given class: Usually just the given class, but if it is a subclass generated by CGLIB, the original class is returned.Class<? > userType = ClassUtils.getUserClass(handlerType);//selectMethods:
// Select a method of the given target type based on the lookup of the relevant metadata.
. / / the caller by MethodIntrospector MetadataLookup parameter definition of interest method, allows the associated metadata collected as a result, mapping
// Parse RequestMapping information
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// Provide mapping for handler methods. A method for which you cannot provide a mapping 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));
}
// The information that will be parsed here is registered in a loopmethods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); }}Copy the code
2.3, getMappingForMethod
GetMappingForMethod defined in AbstractHandlerMethodMapping < T >, implemented under the RequestMappingHandlerMapping class
RequestMapping and method level RequestMapping (creatermappinginfo)
/** Create RequestMappingInfo using RequestMapping annotations 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 the class
String prefix = getPathPrefix(handlerType);
if(prefix ! =null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); }}return info;
}
Copy the code
CreateRequestMappingInfo:
Request requestMAppingInfo (RequestMapping, RequestCondition), which provides the appropriate custom RequestCondition based on whether the provided annotatedElement is a class or method. * /
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping @requestMapping @requestMapping @requestMapping @requestMappingRequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<? > condition = (elementinstanceofClass ? getCustomTypeCondition((Class<? >) element) : getCustomMethodCondition((Method) element));return(requestMapping ! =null ? createRequestMappingInfo(requestMapping, condition) : null);
}
Copy the code
2.4, MethodIntrospector. SelectMethods () method
Select a method of the given target type based on a lookup of the associated metadata.
There are a lot of miscellaneous things in it, it’s hard to explain, but I just gave you a brief overview.
public static <T> Map<Method, T> selectMethods(Class<? > targetType,final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = newLinkedHashMap<>(); Set<Class<? >> handlerTypes =newLinkedHashSet<>(); Class<? > specificHandlerType =null;
if(! Proxy.isProxyClass(targetType)) { specificHandlerType = ClassUtils.getUserClass(targetType); handlerTypes.add(specificHandlerType); } handlerTypes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetType));for(Class<? > currentHandlerType : handlerTypes) {finalClass<? > targetClass = (specificHandlerType ! =null ? specificHandlerType : currentHandlerType);
Performs the given callback on all matching methods of the given class and superclass (or the given interface and superinterface).
ReflectionUtils.doWithMethods(currentHandlerType, method -> {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if(result ! =null) {
// BridgeMethodResolver: Given a synthesized bridge Method returns the Method to be bridged.
// When extending a parameterized type whose method has parameterized parameters, the compiler may create a bridge method. During a run-time invocation, you can call and/or use a bridging Method through reflection
//findBridgedMethod: Find the original Method of the provided Bridge Method.
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return methodMap;
}
Copy the code
Doc comments on methods:
Select a method of the given target type based on a lookup of the associated metadata. . The caller by MethodIntrospector MetadataLookup parameter definition of interest method, allows the associated metadata collected as a result, mapping
If you can’t explain clearly at a glance, just post a picture of debug for everyone to see.
2.5. RegisterHandlerMethod
The essence of this piece of code is the information that’s going to be parsed here, registered in a loop
methods.forEach((method, mapping) -> {
// Select the callable method on the target type: given the method itself, or one of the interfaces of the target type, or the corresponding method on the target type itself, if actually exposed on the target type.
// Return a method
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
Copy the code
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
Copy the code
This. Here is mappingRegistry AbstractHandlerMethodMapping < T > is an inner class.
MappingRegistry: DOC annotation: A registry that maintains all mappings to handler methods, exposes methods that perform lookups, and provides concurrent access.
Its structure will not be discussed here. If you’re interested, click on it.
Let’s move on to what does our register method do
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// Create an instance of HandlerMethod.
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// Validate method mappings
validateMethodMapping(handlerMethod, mapping);
GET[/login]
// Get it, then /login
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
//this.pathLookup is defined as follows:
// private final MultiValueMap
pathLookup = new LinkedMultiValueMap<>();
,>
// A new LinkedHashMap<>();
// Save path as key and mapping as value
this.pathLookup.add(path, mapping);
}
String name = null;
// This is an example of the following:
if(getNamingStrategy() ! =null) {
/// Determine the name of the given HandlerMethod and mapping.
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
// The following lines deal with cross-domain issues that are not covered 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,
newMappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig ! =null));
}
finally {
this.readWriteLock.writeLock().unlock(); }}Copy the code
this.registry.put(mapping,
newMappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig ! =null));
Copy the code
Private final Map
> registry = new HashMap<>();
Different ways to get here, it’s not that different
In fact, after looking at this startup process, we’re probably going to have two answers to our first three questions.
2.6, summary
How does your SpringBoot backend framework store API information? What data structure is it stored in?
The first answer is roughly related to the MappingRegistry class.
The second answer is that the underlying data structure is array + linked list + red-black tree
Note: This article’s SpringBoot version is 2.5.2; The JDK version is JDK 11.
No comparisons have been made between multiple versions, but presumably they are.
So our next step is to look at how SpringBoot requests find interfaces. Where is a key point for us.
Three, summary process
- Scan all registered beans
- These beans are iterated over, in turn, to determine if they are processors, and to detect their HandlerMethod
- Iterate through all the methods in the Handler to find those marked by the @requestMapping annotation.
- Get @requestMapping instance on method.
- Check that the class to which the method belongs has @requestMapping annotations
- RequestMapping of class level and method level together (CreatermappingInfo)
- Loop in and use it again when you request it
Four, subsequent
Follow-up article 👉 How do I find exactly which interface when sending a request from the browser to the SpringBoot back end? (the next)
Personal Remarks:
Reading the source code process, in fact, is really full of fun and boring.
Read some key things, is very happy; Things like “I forgot where I debug again, my mind is cold again”, and I start to complain full (I always want to swear). Then continue to helplessly painful to see.
Hello everyone, I am ning Zaichun: homepage
A young man who likes literature and art but takes the path of programming.
Hope: by the time we meet on another day, we will have achieved something.
In addition, it can only be said to provide a personal opinion here. Because of the lack of writing skills, knowledge, can not write a very terminology of the article. Hope forgive me
If you find this article useful, please give a thumbs up and encouragement.
We also hope that we can have positive exchanges. If there are any shortcomings, please timely approve, thank you.