SpringMvc source code articles #DisPatcherServlet initialization

DisPatcherServlet initialization

1. Foreword

Spring mvc is a framework extended on the basis of Spring. The core components are DispatcherServlet, WebApplicationContext, Resolver, HandlerMapping, ViewResolver, FlashMapManager

DispatcherServlet

DispatcherServlet is the central distributor of the handler/controller of http requests, such as http remote calls and web ui controllers. Provides distributed registered processors for processing web requests, and provides convenient mapping processing and exception handling functions.
In simple terms, handle requests from the outside world, process them through DispatcherServlet, and return the results.

2. Initialization process

When tomcat starts, it loads the web.xml file

init()

In the tomcat call stack, the init() method of the servlet will be called, and in this method, the properties of the bean will be initialized first, and then the initServletBean() will be called to initialize the servletBean

initServletBean()

This method mainly starts to initialize the bean, and initializes after the bean property is set, and starts to call initWebApplicationContext() to initialize the context of the application, which is also the core execution logic of this method

initWebApplicationContext()

This method mainly initializes and publishes the current servlet context.

Core code:

1. Create a parent container

WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  1. Configure and refresh the container
WebApplicationContext wac = null;
//xml will be created here
wac = createWebApplicationContext(rootContext);

Then jump to the following code

//Listener (delegation mode), when the container is created, the method of the listener will be executed
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

Then when the container is created, the listener method will be executed, as follows

Note: this.refreshEventReceived = true; After setting to true, in the initWebApplicationContext() method, the onRefresh() method can be executed repeatedly according to whether this value is present.

onRefresh(ApplicationContext context) ->initStrategies(ApplicationContext context)

This is where DispatcherServlet starts executing and initializes

The initialization operation is completed when the execution is completed here.

3. Initialized HandlerMapping

Initialize HandlerMapping in the initHandlerMapping() method.
HandlerMapping stores the association mapping between the path of handler/controller and bean/handler. It is convenient to match the corresponding method or bean through the URL when processing the request.
First, it will check whether there are other HandlerMappings in the container. If not found, the default strategy will be used, that is, the default HandlerMapping will be loaded.
Core code:

//Register a default HandlerMapping If there is no other mapping, make sure we have at least one HandlerMapping,
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this. handlerMappings == null) {<!-- -->
//Create HanlderMapping using the default strategy
//Get the specified mapping from DispatcherServlet.properties, and use reflection to create HandlerMapping
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {<!-- -->
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
Core method getDefaultStrategies()

This is an interface method that returns a collection of default policy objects. Use the values in the DispatcherServlet.properties file to determine the name of the class. That is to say, this method will initialize the configuration in the DispatcherServlet.properties file, and use reflection to instantiate all the classes configured in it, add them to the collection, and return them through the method.
The DispatcherServlet.properties file is as follows:

Many default classes are configured in the file, including various parsers, adapters and HandlerMapping
The configured value is obtained through the configured key, and the string is cut to obtain the full path name of the class, and then instantiated through Class.forName();.

4. Initialization of RequestMappingHandlerMapping

Since Spring provides the extension of Bean initialization, it allows beans to perform some additional operations during initialization, such as the afterPropertiesSet() method of the initializingBean interface

RequestMappingHandlerMapping scans all beans in the container in the afterPropertiesSet() method, and detects and registers the corresponding handler.

 /**
* Detects handler methods at initialization.
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {<!-- -->
initHandlerMethods();
}

Scan all beans and process

 /**
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {<!-- -->
//Get all beans and process
for (String beanName : getCandidateBeanNames()) {<!-- -->
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {<!-- -->
//Process all the beans obtained
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}

processed bean

 protected void processCandidateBean(String beanName) {<!-- -->
Class<?> beanType = null;
try {<!-- -->
//Get the bean type by bean name
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);
}
}
//Check the corresponding bean type, that is, whether it can be processed
if (beanType != null & amp; & amp; isHandler(beanType)) {<!-- -->
//Get the corresponding handler method from the corresponding bean
detectHandlerMethods(beanName);
}
}

Determine whether the bean conforms to

 /**
* {@inheritDoc}
* <p>Expects a handler to have either a type-level @{@link Controller}
* annotation or a type-level @{@link RequestMapping} annotation.
*/
@Override
protected boolean isHandler(Class<?> beanType) {<!-- -->
//Check if the bean type is @Controller or @RequestMapping bean
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

Find the handler method from the bean of the specified handler

 /**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {<!-- -->
//Process bean type
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());

if (handlerType != null) {<!-- -->
//Get the corresponding handler class
Class<?> userType = ClassUtils. getUserClass(handlerType);
// Get the method corresponding to the handler, and the annotation above the method including the attribute of the annotation
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));
}
//Traverse the found methods and save them in the mappingRegistry
methods.forEach((method, mapping) -> {<!-- -->
Method invocableMethod = AopUtils. selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

Create RequestMappingInfo with method and @RequestMapping annotation

 /**
* Uses method and type-level @{@link RequestMapping} annotations to create
* the RequestMappingInfo.
* @return the created RequestMappingInfo, or {@code null} if the method
* does not have a {@code @RequestMapping} annotation.
* @see #getCustomMethodCondition(Method)
* @see #getCustomTypeCondition(Class)
*/
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {<!-- -->
//Find @RequestMapping on the method, and get other condition conditions of the annotation
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {<!-- -->
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {<!-- -->
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {<!-- -->
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
return info;
}

Then save the found method or bean to the mappingRegistry

methods.forEach((method, mapping) -> {<!-- -->
Method invocableMethod = AopUtils. selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});

Store the mapping relationship between URL and handler, so that after the request comes, you can find the corresponding handler through the path

 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) & amp; & amp; "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {<!-- -->
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
this.readWriteLock.writeLock().lock();
try {<!-- -->
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);

// Get the URL from the method, so that you can find the corresponding method through the request path after the subsequent request
//that is, the association mapping between the storage path and the handler
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {<!-- -->
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();
}
}

The rest of the HandlerMappings are similar and will not be listed one by one.