summarize
MVC in SpringMVC is Model-View-Controller, which is based around a DispatcherServlet that distributes requests to various processors and supports configurable processor mapping and view rendering.
The workflow of SpringMVC is shown below:
- The client initiates the HTTP request: the client submits the request to the DispatcherServlet
- Finding the Handler: The DispatcherServlet controller queries one or more HandlerMapping to find the Controller that handled the request
- Call Processor: DispatcherServlet submits the request to the Controller.
- Call the business processing logic and return the result: Controller returns the ModelAndView after calling the business processing logic.
- Process the view mapping and return the model: the DispatcherServlet queries one or more ViewResolver view resolvers to find the view specified by ModelAndView
- HTTP Response: The view is responsible for coloring and displaying the results on the client browser.
DispatcherServlet
In Java, you can use a Servlet to handle requests. Every time the client sends a request, the Servlet will call the service method to handle it, and SpringMVC creates a DispatchServlet to uniformly receive requests and distribute them for processing.
1. Create the DispatcherServlet
There are two ways to create a DispatcherServlet in Tomcat:
The first way is through , Tomcat will load the root path /WEB-INF/ configuration file at startup, according to which the configuration load Servlet, Listener, Filter, etc., the following is the common configuration of SpringMVC:
<servlet>
<servlet-name>dispatcher</servlet>
<servlet-class> </servlet-class>
<init-param>
<! --DispatchServlet holding WebApplicationContext-->
<param-name> contextConfigLocation</param-name>
<param-value> /WEB-INF/</param-value>
<! -- 1: DispatcherServlet is created when tomcat starts, 0: DispatcherServlet is not created when tomcat starts, it is created when a request is received -->.
<load-on-startup>1</load-on-startup>
</init-param>
</servlet>
</servlet-mapping>.
<servlet-name>dispatch</servlet-name>
<servlet-pattern>/*</servlet-pattern>
</servlet-mapping>
The second way is through the WebApplicationInitializer, which simply means that Tomcat will detect and load the implementation class of ServletContainerInitializer and call its onStartup method, while SpringMVC provides the corresponding implementation class, SpringServletContainerInitializer. SpringServletContainerInitializer will detect and load the implementation class of WebApplicationContextInitializer under the ClassPath, and call its onStartUp method
So we can inherit from WebApplicationContextInitializer and implement the onStartUp method, where we configure the DispatchServlet in code.
public class MyWebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
// establish dispatcher Held Context Containers
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
();
// enrollment、configure dispatcher servlet
dispatcher = ("dispatcher", new DispatcherServlet(dispatcherContext));
(1);
("/*");
}
}
When the DispatcherServlet is created, a Spring container WebApplicationContext is created internally to manage the objects in the web application by means of beans.
2. DispatcherServlet initialization
DispatcherServlet is the implementation class of Servlet, the Servlet life cycle is divided into three stages: initialization, operation and destruction. Initialization phase will call init() method, DispatcherServlet after a series of encapsulation, will eventually call initStrategies method for initialization, where we focus on initHandlerMappings and initHandlerAdapters.
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
The initHandlerMappings method is responsible for loading the HandlerMappings, which are the handler mappings that SpringMVC provides by default if not configured by the programmer. each HandlerMapping remains in the container as a bean and performs its respective initialization methods.
The default HandlerMapping has the following two types:
- RequestMappingHandlerMapping: mapping to the corresponding @RequestMapping method based on the request URL
- BeanNameUrlHandlerMapping: Based on the request URL mapped to the name of the corresponding bean (e.g., the name of the bean is /test), the bean will provide a method to handle the request logic.
RequestMappingHandlerMapping will find out all the handler methods (annotated with @RequestMapping) from the processor bean (i.e. annotated by @Controller) during initialization, and parse the @RequestMapping annotation of the handler method into a The @RequestMapping annotation of the handler method is parsed into a RequestMappingInfo object, and the handler method object is wrapped into a HandlerMethod object. Then the RequestMappingInfo and HandlerMethod objects are cached in the form of a map, with the key as RequestMappingInfo and the value as HandlerMethod, which will be used when mapping requests to handlers in the future.
BeanNameUrlHandlerMapping scans all the beans in the Spring container during initialization and gets the name of each bean and the corresponding bean to hold it. The name of each bean is matched against the URL path of the request, and if the name of the bean matches the URL path (ignoring case), then the matching bean is used as the handler for the request. Matching beans are implemented as follows:
@Componet("/welcome*")
public class WelcomeController implements Controller {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
...
}
}
or
@Componet("/welcome*")
public class WelcomeController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
...
}
}
The initHandlerAdapters method is responsible for loading the adapters, which are also kept in the container in the form of beans and perform initialization methods. If the programmer does not configure it, then SpringMVC also has a HandlerAdapter provided by default, which is adapted to get a HandlerAdapter after finding the corresponding handler object according to the request, and the HandlerAdapter executes the request.
The SpringMVC default adapters are:
- RequestMappingHandlerAdapter: the adapter is a HandlerMethod object.
- HandlerFunctionAdapter: The adapter processor is a HandlerFunction object.
- HttpRequestHandlerAdapter: the adapter processor is an HttpRequestHandler object
- SimpleControllerHandlerAdapter: the adaptation handler is a Controller object.
parent-child container
As mentioned earlier, when a DispatcherServlet is initialized, a Spring container is created inside it, so if two different DispatcherServlets are configured, then there will be two Spring containers belonging to different DispatcherServlets
<!-- first DispatcherServlet -->
<servlet>
<servlet-name>app1</servlet>
<servlet-class></servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/</param-value>
<load-on-startup>1</load-on-startup>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<servlet-pattern>/app1/*</servlet-pattern>
</servlet-mapping>
<!-- second reason DispatcherServlet -->
<servlet>
<servlet-name>app2</servlet>
<servlet-class></servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/</param-value>
<load-on-startup>1</load-on-startup>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app2</servlet-name>
<servlet-pattern>/app2/*</servlet-pattern>
</servlet-mapping>
The presence of multiple DispatcherServlets is generally a solution to the problem of multiple versions, for example, there is a TestV1Controller in app1 this DispatcherServlet, and now there is an additional upgraded version of TestV2Controller, which can be placed in app2, using different mapping paths
Sometimes we just want to differentiate between different Controllers and the generic Service doesn't need to keep a copy in each container, so we can configure the parent container to place the Service in the parent container. the DispatcherServlet will automatically look for the existence of a parent container when it is initialized.
<web-app>
<listener>
<listener-class></listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/</param-value>
</context-param>
<servlet>
<servlet-name>app1</servlet-name>
<servlet-class></servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app1</servlet-name>
<url-pattern>/app1/*</url-pattern>
</servlet-mapping>
</web-app>
The ContextLoaderListener is configured to the list of listeners, and the ServletContext is initialized with the configuration file set by the parameter named contextConfigLocation in context-param.
SpringMVC Handling Requests
The SpringMVC request processing flow can be divided into the following steps:
- Find the corresponding Handler according to the path
- Parsing parameters and binding
- Method of implementation
- Parsing the return value
1. Finding a Handler based on a request
When a request arrives, the getHandler method of the DispatcherServlet is executed. Iterating through all the HanlderMapping, each HandlerMapping looks for a Handler based on the request, but in a different way, for example, RequestMappingHandlerMapping looks for a HandlerMethod based on the request path, and BeanNameUrlHandlerMapping maps the request path to the name of the corresponding bean. For example, RequestMappingHandlerMapping looks for a HandlerMethod based on the request path, while BeanNameUrlHandlerMapping maps the request path to the name of the corresponding bean. By traversing the HandlerMapping until the request finds the corresponding Handler
Different HanlderMapping has different types of Handler, so we need to find the corresponding type of adapter. Iterate through all the HandlerAdapters, if you find the right one, return it and execute the handle method of the adapter.
2. Parse the parameters and execute the method
Take RequestMappingHandlerMapping as an example, the actual type of the Handler is HandlerMethod, and it is adapted to RequestMappingHandlerAdapter. method, parse the methods of @initBinder annotation and save them, parse the key-value pairs set by @SessionAttributes annotation, parse the methods of @ModelAttribute annotation, and the results of the above parsing will be saved in the ModelFactory object, which is used to initialize the Model object. SessionAttributes and @ModelAttribute will be initialized into the Model object.
The next step is to create argumentResolvers and returnValueHandlers, there are different types of resolvers for different scenarios, e.g. if you use the @PathVariable annotation to pass a parameter, use the PathVariableMethodArgumentResolver. For example, if you use @PathVariable annotation to pass parameter, you can use PathVariableMethodArgumentResolver, and if the return value is a ModelAndView object, you can use ModelAndViewMethodReturnValueHandler.
Get the method parameter, which is of type MethodParameter and contains not only the name of the parameter, but also information about the parameter, such as whether it has the @ReqeustParam annotation. Iterate over the method parameters and traverse them one by one with a parameter parser, find an applicable parser to parse them, and then get the parameter value from the request based on the parameter name. If a type converter is defined, then the parameter type is converted. Finally the real method logic is executed using reflection
3. Parsing return values
After getting the return value, it is also traversed to find a suitable return value parser for processing. For example, in development, the @ResponseBody annotation is often used to return json, which will be processed using the RequestResponseBodyMethodProcessor processor, which also assumes the role of parsing the parameters. The parsing process requires the use of a message converter, HttpMessageConverter, which converts the return value of a method to a response type that is acceptable to the receiver (e.g., the browser), and which is provided by SpringMVC by default. SpringMVC also provides a default converter. For example, if a method annotated with @ResponseBody returns a return value of type String, it will iterate to determine which message converter can handle the return value of type String, and the default converter in the RequestResponseBodyMethodProcessor handler will be the StringHttpMessageConverter is used by default in the RequestResponseBodyMethodProcessor processor, the next step is the content negotiation, that is, to find the type of content that can be accepted by the client and provided by the server, for example, the client would like to prioritize the return of text/plain type of content, which can be supported by the StringHttpMessageConverter, so it uses the StringHttpMessageConverter to write the method return value to the client in the response message. If we want the method to return the object type directly and automatically serialize it to json, then we need to customize the message converter. In this case, SpringMVC will no longer provide the default converter, but will directly use the custom converter, for example, by introducing the MappingJackson2HttpMessageConverter, it can support the conversion of the object type return value to to json and return it to the client
If the @ResponseBody annotation is not used, then the ModelAndView is used to store the view path and data. springMVC also provides a default view resolver, ViewResolver, which will forward the request to the corresponding view file (jsp) based on the url returned by the method within tomcat (instead of forwarding it via the DispatcherServlet). Instead of forwarding the request via a DispatcherServlet, the ViewResolver uses a native Servlet to forward the request to the corresponding view file such as a jsp. If the url is prefixed with forward: to indicate that this is a forwarded request, such as forward:/app/test, SpringMVC removes the prefix and re-routes the request to a view file such as a jsp, using /app/test. DispatcherServlet to the corresponding processor. If the url is prefixed with redirect:, such as redirect:/test, SpringMVC will remove the prefix, write a redirect header to the client's response with the redirect address /test, and the client will resend the request. The difference between forwarding and redirection is that a forwarded request is the same, while a redirection is a new request each time. The forwarding request goes through the DispatcherServlet, so it creates a new Model each time, while the redirection automatically splices the parameters from the Model into the redirected url.
@EnableWebMvc
Using the @EnableWebMvc annotation helps us to customize SpringMVC configuration in code, such as adding interceptors. The configuration class that uses the @EnableWebMvc annotation must inherit from the WebMvcConfigurer class.
Note that @EnableWebMvc is the older way of configuring SpringMVC. If you are using SpringBoot, which provides auto-configuration, you usually don't need to explicitly use @EnableWebMvc, you just need to configure it in the configuration file
@Configuration
@EnableWebMvc
public class AppConfig implements WebMvcConfigurer {
@Autowired
private BeforMethodInteceptor beforMethodInteceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Registering a Custom Interceptor,Adding blocking paths and excluding blocking paths
(beforMethodInteceptor) //Adding Interceptors
.addPathPatterns("/**") //Adding an Intercept Path
.excludePathPatterns( //Add exclusion of blocking paths
"/index",
"/login",
...
);
(registry);
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// Configuring the View Parser
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
("");
(".html");
(false);
("text/html;charset=UTF-8");
(0);
(viewResolver);
(registry);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Define static resource locations and URL mapping rule
// for example,Combine all the files that begin with /static/ beginning URL map to /resources/ Static resources in the directory
("/static/**")
.addResourceLocations("/resources/");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// increase JSON message converter
(new MappingJackson2HttpMessageConverter());
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// Cross-Domain Configuration
("/**") // Configure paths to allow cross-domain
.allowedOrigins("*") // Configure the request domain names of cross-domain resources that are allowed to be accessed
.allowedMethods("PUT,POST,GET,DELETE,OPTIONS") // Configure the request method that allows access to this cross-domain resource server
.allowedHeaders("*"); // Configuring permission requests header visits
(registry);
}
}
The @EnableWebMvc annotation imports the DelegatingWebMvcConfiguration configuration class, which finds and saves all implementations of the WebMvcConfigurer interface. the DelegatingWebMvcConfiguration configuration class also implements the Aware Callbacks interface, so it will be called during the Spring container lifecycle to customize the configuration.