Request Path Matching Routing Mode of Spring Source Code
- 2021-11-10 09:31:48
- OfStack
Request path matching route
In spring, path matching will be done when a request comes over, so we will analyze path matching under 1 from the source code level.
Example:
@RequestMapping(value = "/user/{aid}/online/**", method = RequestMethod.GET)
Let's look at how this method is found and some corresponding tool classes
Entrance
My project uses the automatically configured RequestMappingHandlerMapping class, in the getHandlerInternal () method:
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
The above line is based on your request path and request to find the appropriate method. When the project starts, Spring loads the path and corresponding method into memory.
Enter the above method
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
You can see that if you match directly according to lookupPath, go to the first method; If you don't, you need to match according to the rules and go to the second method.
mappingRegistry. getMappings (). keySer () This method takes the type of RequestMappingInfo, followed by the getMatchingCondition () method of RequestMappingInfo:
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (methods == null || params == null || headers == null || consumes == null || produces == null) {
return null;
}
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
You can see that the code will check whether various conditions match, including the request method methods, the parameter params, the request header headers, and the related consumers, produces, etc. The last line is the path we are looking for matches patternsCondition. getMatchingCondition (request).
This method goes to the getMatchingPattern method of PatternRequestCondition, and then calls the following method to get pattern:
if (this.pathMatcher.match(pattern, lookupPath)) {
return pattern;
}
The type of pathMatcher above is the AntPathMatcher class, which is to call the match method of the AntPathMatcher class to see if it matches, and then return pattern.
SpringMVC will request to find a matching process
In SpringMVC mode, how does a browser request map to the specified controller?
Initialize the mapping relationship
When the web server is started, the Spring container stores a data structure of map, which records the correspondence between the controller and url requests. So how did the data in this map come from?
First of all, let's look at the initHandlerMethods method of AbstractHandlerMethodMapping (as to why I found this method directly, I also searched online, and the previous call chain didn't struggle)
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
// Get Spring All of container assembly bean Name of
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// Traversal
for (String beanName : beanNames) {
// Determine the bean Whether there is @controller Or @RequestMapping Annotation
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
// If you have the above annotations, you need to save the corresponding relationship
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void detectHandlerMethods(final Object handler) {
// Get passed over handler Class information of
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
// Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
// Initialization 1 Object that holds mapping information map
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
// Get the mapping information of all methods in this class T For RequestMappingInfo
//mapping Values take the form of {[/test/test1],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
// Add information to map
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
// Registration HandlerMethod , carried out inside 1 Some duplicate validation, etc.
registerHandlerMethod(handler, method, mappings.get(method));
}
}
A more important method, getMappingForMethod, is called in the above method, and an RequestMappingInfo object that we will use in the following 1 straight is generated by this method. The specific methods are as follows:
@Override
// The method takes two parameters, 1 One is the specific method, 1 Is the class where the method is located
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
// Find a way @RequestMapping Annotation
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
// This method returns null
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
// Create RequestMappingInfo Object
info = createRequestMappingInfo(methodAnnotation, methodCondition);
// Find the @RequestMapping Annotation
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
// This method also returns 1 A null
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
// If both classes and methods have @RequestMapping Annotation, then proceed combine Operation
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
So how does the createRequestMappingInfo method called in the above method really create an RequestMappingInfo object?
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition<?> customCondition) {
// Get @RequestMapping On the annotation value Value
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
// Create 1 A RequestMappingInfo With the parameter of 1 Heap condition , out PatternsRequestCondition , and the rest are all used @RequestMapping Values on annotations
return new RequestMappingInfo(
annotation.name(),
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new RequestMethodsRequestCondition(annotation.method()),
new ParamsRequestCondition(annotation.params()),
new HeadersRequestCondition(annotation.headers()),
new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
new ProducesRequestCondition(annotation.produces(), annotation.headers(), this.contentNegotiationManager),
customCondition);
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//handler Here is with the @controller Or @RequestMapping The name of the class of
// Initialization 1 A HandlerMethod , including 1 Information such as the names and methods of some classes
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
// Determine if there is handlerMethods Whether there is duplicate data, throw an exception if there is, and add it if there is no handlerMethods map Medium
if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
}
this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
}
// There will be no * Number and hello pattern Join to urlMap Medium
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
// Maintenance 1 A nameMap , key Is the first name, formatted as congroller Quasi-capital letters +#+ Method name
// For example TestBank Class getBank Method, which can be TB#getBank
if (this.namingStrategy != null) {
String name = this.namingStrategy.getName(newHandlerMethod, mapping);
updateNameMap(name, newHandlerMethod);
}
}
From the above registerHandlerMethod method, we can see that this method maintains three map:
handlermethods
key is RequestMappingInfo value is HandlerMethod
urlMap
: key for no * and? pattern (eg/test/test1) value is RequestMappingInfo
nameMap
key is the first name, formatted as congroller class uppercase + # + method name, such as getBank method of TestBank, key is TB # getBank
The above three map will be used emphatically when matching which method is used to process browser requests.
Looking for matching methods from mapping relations
So how does DispatcherServlet handle a request?
Let's start with the doService method of DispatcherServlet, where the lookupHandlerMethod method of the AbstractHandlerMethodMapping class is eventually called to determine which method should handle this request. The code is as follows:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// From urlMap Looking for matching processing methods in
List<T> directPathMatches = this.urlMap.get(lookupPath);
// If from urlMap If a matching processing method is found in the addMatchingMappings Method, put the matching method into the matches Set
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
// If urlMap No direct matching method was found in
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.handlerMethods.keySet(), matches, request);
}
if (!matches.isEmpty()) {
// If a matching method is found, get the 1 A comparator
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
// Sort the matched methods by comparator
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
// If the only successful matching methods are 1 A, take this method back. If multiple methods are matched, compare the first two best matches.
// If the comparison result is 0 Is thrown without finding the only 1 Exceptions of appropriate handling methods
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
// If no match is found, return null
return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
}
}
As can be seen from the above code, the program will first look for a matching method from this. urlMap, so when did the data in this urlMap load? We looked at the registerHandlerMethod method online, which initialized the data in urlMap when the web server started.
From the above analysis, we can get a general understanding of how the Spring container maintains the mapping relationship between url and methods, and how to match the requests to the correct methods when they are received.
As for what the combine operation triggered when both classes and methods have @ RequestMapping annotations does, and how multiple matching methods are sorted by comparators, we will analyze it next time.