This paper is mainly divided into two parts of MVC container startup and external service provision, and summarizes the relationship between Tomcat container, IOC container and MVC container in the above two parts. Today’s interview just asked about the father and son container process, incidentally here to do a tidy up.

In normal development, such as the input parameter underscore to hump (camel_demo -> camelDemo), input parameter XSS filtering and other scenarios that need to do conversion and verification of request parameters need to have a specific understanding of MVC request processing process. We can flexibly use the extensibility points provided by the SpringMVC framework to complete the above code development.

1. MVC container startup process

XML file to configure Tomcat container, applicationContext. XML file to configure IOC container, springMVC-servlet. XML file to configure MVC container;

The web. XML configuration file of the Tomcat container is used to start the IOC container and the MVC container in sequence.

<! --Spring IOC configuration file, <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/applicationContext-*.xml</param-value> </context-param> <! - Spring IOC loader - > < listener > < listener - class > org. Springframework. Web. Context. ContextLoaderListener < / listener - class > </listener> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <! -- SpringMVC container configuration file, Classpath *:spring/ springmVC-servlet. XML </param-value> </init-param> <! <load-on-startup>1</load-on-startup> </servlet> <! --------------> <servlet-mapping> <servlet-name>springmvc</servlet-name> <! -- This servlet will handle all path requests --> <url-pattern>/</url-pattern> </servlet-mapping>Copy the code

1.1 Spring IOC container startup

Tomcat uses the observer mode. When the Tomcat container initializes the StandardContext component (the component hierarchy corresponds to our Web application), it generates corresponding life cycle events, which are processed by different event handlers corresponding to different event types.

For IOC container startupServletContextEventEvent corresponding to the handler as described aboveweb.xmlWe defined in the configuration fileContextLoaderListenerListener:

Public class StandardContext extends ContainerBase implements Context{// This method calls protected when Tomcat starts the Context component Synchronized void startInternal() throws LifecycleException {// Call the corresponding process listener method listenerStart(); } public boolean listenerStart() { // 1. Life cycle class for the event handler Object instances [] = getApplicationLifecycleListeners (); ServletContextEvent event = new ServletContextEvent(getServletContext()); For (Object instance: Instances) {if (! (instance instanceof ServletContextListener)) { continue; } ServletContextListener listener = (ServletContextListener) instance; / / call the listener contextInitialized () method of the listener. The contextInitialized (event); } } return ok; }}Copy the code

Of the Spring IOC container is in ContextLoaderListener listener initialized contextInitialized interface method, mainly by calling the child methods initWebApplicationContext () implementation;

Container startup is mainly divided into two steps: creating IOC container and initializing bean objects in IOC container. Let’s first look at the macro code and feel the general process, and then analyze the corresponding methods of each process:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 1. Create Spring IOC container if (enclosing context = = null) {enclosing context = createWebApplicationContext (servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (! Cwac. IsActive ()) {/ / 2. Call onRefresh () method to the IOC container bean initialization configureAndRefreshWebApplicationContext (cwac, servletContext); }} // 3. Put a reference to the IOC container into the ServletContext object corresponding to the current Web application servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); // 4. Return this. Context; }Copy the code

IOC container is used to manage the class objects of the Service and Dao layer, while the class objects of the Controller layer are managed by the Spring MVC container. As for why we need to use it in this way, we will analyze the MVC container.

1.1.1 Creating an IOC Container

CreateWebApplicationContext method is used to instantiate the specified the IOC container, if the web. The XML configuration file failed to pass the context – param > contextClass attribute definition specifies container classes, The XmlWebApplicationContext class is instantiated as an IOC container by default:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 1. Which type of ApplicationContext Class< is instantiated based on the configuration file? > contextClass = determineContextClass(sc); / / 2. Instantiate the concrete ApplicationContext and return to the return (ConfigurableWebApplicationContext) BeanUtils. InstantiateClass (contextClass);  }Copy the code
protected Class<? > determineContextClass(ServletContext servletContext) { // 1. The <param-name> configured in <context-param> in the corresponding web. XML file is the contextClass property String contextClassName = servletContext.getInitParameter("contextClass"); If (contextClassName!) contextClassName = contextClassName (contextClassName!); = null) { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } // 2.2 If this attribute is not set, The 'XmlWebApplicationContext' class is loaded by default as the IOC container contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); / / 3. Return to the IOC container reference return ClassUtils. Class.forname (contextClassName, ContextLoader. Class. GetClassLoader ()); }Copy the code

1.1.2 IOC Container Initialization

ConfigureAndRefreshWebApplicationContext method is used to initialize the IOC container, The container is initialized by retrieving the APPLICationContext. XML configuration file of the IOC container corresponding to the contextConfigLocation address property of the

in the web. XML file:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { wac.setServletContext(sc); // contextConfigLocation = contextConfigLocation = contextConfigLocation = contextConfigLocation = contextConfigLocation The initialization file used for IOC container startup // mainly contains the IOC container Service, Manager, Dao and other non-controller layer Bean package scan information and annotation driver String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam ! = null) { wac.setConfigLocation(configLocationParam); } // 2. Extensibility point, which can be used to do some custom operations before IOC container initialization customizeContext(sc, WAC); // 3. Call the refresh() method to initialize the IOC container. }Copy the code

1.2 Start the Spring MVC container

The IOC container startup described above is initialized by a listener when the Tomcat container starts StandardContext(Web application layer). The MVC container is initialized by init() method of the Servlet interface when the Tomcat container initializes StandardWrapper(Servlet level). Therefore, the initialization of IOC container and MVC container have a sequence relationship.

Since we only define DispatcherServlet as a servlet in the web. XML file and Tomcat is lazy by default, We set

1
to make Tomcat load the servlet at startup. We know that Tomcat loads servlets through init() :

public abstract class HttpServletBean extends HttpServlet { public final void init() throws ServletException { ... // Leave the extension point initServletBean() for subclasses; }}Copy the code
@Override protected final void initServletBean() throws ServletException { // 1. Initialize the MVC container this. WebApplicationContext = initWebApplicationContext (); }}Copy the code

InitWebApplicationContext () method as the core of the MVC container startup method:

Protected WebApplicationContext initWebApplicationContext () {/ / in the second step in the process of the Spring IOC container startup, Finally, we put the IOC container reference into the current Web corresponding servletContext // 1. The corresponding IOC container reference WebApplicationContext rootContext = is obtained via the ServletContext object WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; / / 2. Start the MVC container if (wac = = null) {/ / initializes the MVC 2.1 container, and sets the IOC container to MVC container of the parent container wac = createWebApplicationContext (rootContext); } if (! Enclosing refreshEventReceived) {/ / 2.2 initialization DispactcherServlet corresponding components, Synchronized (this.onRefreshMonitor) {onRefresh(waC); synchronized (this.onRefreshMonitor); }} the if (this. PublishContext) {/ / 2.3 MVC containers references to be included in the ServletContext object String attrName = getServletContextAttributeName (); getServletContext().setAttribute(attrName, wac); } // 3. Return a reference to the MVC container. }Copy the code

1.2.1 MVC container start

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // 1. Set the IOC container to the parent of the MVC container, wac. SetParent (parent); // contextConfigLocation property defined in <init-param> parameter of DispatcherServlet // 2. ConfigLocation = getContextConfigLocation(); configLocation = getContextConfigLocation(); if (configLocation ! = null) { wac.setConfigLocation(configLocation); } / / 3. Based on the MVC MVC container startup configuration file address, main is to call the refresh () method, which is no longer narrative configureAndRefreshWebApplicationContext (wac); return wac; }Copy the code

1.2.2 Why start MVC container and IOC Container respectively?

Why bother to start two MVC containers when we can manage all our beans in MVC containers?

I think the biggest reason is decoupling, because MVC container is a solution for Web container services, and things like Strut2 and so on, so if we need to switch web solutions, You only need to replace spring-servlet. XML with the Struts configuration file struts. XML, which reduces the impact scope and improves scalability.

1.3 summarize

So far, we have combed the container startup process, mainly as follows: When Tomcat initializes the Web application, it initializes the IOC container through the event listener. Then when Tomcat initializes the Servlet, it initializes the MVC container through the init() method of the Servlet, and sets the parent-child relationship between MVC container and IOC container.

After an overview of the MVC startup process, we will then examine how MVC handles requests through a request processing link.

2. Request processing link

2.1 DispatchServlet

As a servlet registered in Tomcat, when processing external requests, the corresponding Request Response object will be generated for each Request. Finally, the request is processed by invoking the service() method of the servlet instance through the base valve of Pipeline in StandardWrapper (Pipeline is based on the chain of responsibility pattern, the base valve is the tail of the chain).

Abstract class HttpServlet implements the servlet interface, and is based on the CHARACTERISTICS of HTTP – GET, POST, HEAD represents the request type, and needs to correspond to different processing methods, so HttpServlet in the service() method to parse HTTP request headers. The corresponding doGet() and doPost() methods are forwarded.

So inherit HttpServlet class, need to implement doGet, doPost() and other methods; The FrameworkServlet abstract class in the SpringMVC framework defines the default processing flow through the template pattern, and defines the abstract method doService() to be implemented by the subclass DispatchServlet.

Below is the class structure diagram described above:

2.2 DispatchServlet.doService

The doService() method adds the MVC container and Resolver reference to the current Request object Request, which will be discussed later.

DoService () then calls the doDispatch() method, which defines the core flow for SpringMVC to handle requests: Find the corresponding Handler and interceptor chain through HandlerMapping, and use Handler through HandlerAdapter adapter interface method Handle (). After processing, the generated ModelAndView is handed over to view parser for page rendering.

2.2.1 Determine the Handler corresponding to the current Request by using HandlerMapping

Initialization:

Add the < MVC :annotation-driven/> namespace configuration to the spring/ Spring MVC-servlet. XML file, Through corresponding NameSpaceHandler initialization RequestMappingHandlerMapping the namespace and BeanName – UrlHandlerMapping two HandlerMapping;

The MVC container starts with a lifecycle event that asynchronously invokes the listener’s onRefresh() method of DispatchServlet, Trigger initHandlerMappings() to inject the default HandlerMapping into the List

handlerMappings property;


Find the corresponding Handler to handle the request using the matching strategy of handlerMapping:

The doDispatch method calls getHandler(Request Request) to get the corresponding Handler:

@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings ! = null) {// Set handlerMappings to (HandlerMapping mapping: This.handlermappings) {// Call the current HandlerMapping to get the handler's policy // Return the corresponding handler that matches the request's interceptor chain HandlerExecutionChain handler = mapping.getHandler(request); if (handler ! = null) { return handler; } } } return null; }Copy the code

The remaining handlerMappings will not be called again. / / “handlerMappings” ¶ Therefore, if we need to customize multiple HandlerMapping, we need to implement Ordered interface for ordering.

Default by RequestMappingHandlerMapping strategy to find in the first place, namely for @ RequestMapping annotation defined in value and match the path of the request, to find the corresponding Controller layer method;

/** * Look up a handler method for the given request. */ @Override protected HandlerMethod GetHandlerInternal (HttpServletRequest Request) throws Exception {// Relative path (alwaysUseFullPath=false) String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); request.setAttribute(LOOKUP_PATH, lookupPath); / / get the mapping relations read lock (used to support @ RequestMapping mapping runtime heat more) enclosing mappingRegistry. AcquireReadLock (); HandlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod ! = null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); }}Copy the code

After finding the corresponding processing method of the request, you also need to find the HandlerInterceptor list that matches the request path, and conduct pre-processing and post-processing of the request.

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HandlerExecutionChain chain = (Handler instanceof) HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); / / get request relative path String lookupPath = this. UrlPathHelper. GetLookupPathForRequest (request, LOOKUP_PATH); For (HandlerInterceptor interceptor: this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }Copy the code

2.2.2 Provide services externally by wrapping handlers with adapters

Since the MVC framework supports HandlerMapping that takes different matching strategies and returns different Handler objects (Object references), the granularity of these handlers may be Method, Controller, or other, you can isolate changes through the adapter pattern. The caller relies on the corresponding adapter object, which is responsible for masking the internal implementation details to expose the unified interface that invokes the corresponding processing methods;

As shown in the figure above, doDispatch only needs to rely on the HandlerAdapter interface, find the suitable adapter object through the support() method, and then call the handler() of this adapter object to complete the processing of the request.

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters ! = null) {// Iterate over the list of HandlerAdapter for (HandlerAdapter adapter: If (Adapter.supports (handler)) {return Adapter; if (adapter.supports(handler)) {return Adapter; } } } throw new ServletException("No adapter for handler"); }Copy the code

2.2.3 Start the process

After the adapter and interceptor chains corresponding to the current request have been generated, call: Interceptor PreHandle(), adapter handle() method and return ModelAndView, pass ModelAndView to the corresponding view parser (currently REST style, separated from the front end, This section will not go into detail), the post-processing of interceptors, and the final processing of interceptors;

3. Extend the method

3.1 Controller layer parameter implementation underline -> hump

The front-end parameter is generally camel_demo, but much of the reusable back-end code is implemented in the hump, so it needs to be converted, the most intuitive way is to convert in the Controller layer method:

@RequestMapping(value = "/xxx", method = RequestMethod.GET) public RespBO xxx (HttpServletRequest request, Queryunderline underlineQuery) { CamelQuery camelQuery = new CamelQuery(); camelQuery.setUserName(underlineQuery.getUser_name()); camelQuery.setUserId(underlineQuery.getUser_id); camelQuery.setUserAge(underlineQuery.getUser_age); / /... Return new RespBO(); }Copy the code

But all Controller layer methods need to be converted, and the optimized way is obvious. The Tomcat level or SpringMVC container level should handle the transformation uniformly. Here’s how it is handled in the SpringMVC container:

So following the above flow, the Model entry is handled in the handler() flow that calls the adapter, With the handler -> handleInternal() -> invokeHandlerMethod() -> invokeAndHandle() method, the getMethodArgumentValues() method is called, This method will traverse the participation and the application List in order of the < HandlerMethodArgumentResolver > argumentResolvers parser List, So we can pass in a MVC container to register a custom for the underline – > hump HandlerMethodArgumentResolver can compare elegant treatment:

<mvc:annotation-driven> <mvc:argument-resolvers> <! <bean class="... "> <bean class="... UnderlineToCamelMethodProcessor"> <constructor-arg name="annotationNotRequired" value="true" /> </bean> </mvc:argument-resolvers> </mvc:annotation-driven>Copy the code

Custom implementation, the need to pay attention to realize HandlerMethodArgumentResolver interface directly or indirectly, otherwise unable to take effect, and then search engine oriented programming, see if there are any more encapsulation Spring had some wheels, if there is, on the basis of the wheel directly is more convenient for customization:

public class UnderlineToCamelMethodProcessorToCamelMethodProcessor extends ServletModelAttributeMethodProcessor { @Resource(name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter") private RequestMappingHandlerAdapter requestMappingHandlerAdapter; public SnakeToCamelMethodProcessor(boolean annotationNotRequired) { super(annotationNotRequired); Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest Request) {// Implement a specific format conversion method, The use of this kind of specific way not an explanation ~ UnderlineToCamelRequestDataBinder utcDataBinder = new UnderlineToCamelRequestDataBinder(binder.getTarget()); requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(utcDataBinder, request); utcDataBinder.bind(request.getNativeRequest(ServletRequest.class)); }}Copy the code

3.2 XSS attacks

For XSS attacks, we can uniformly conduct sensitive character checks on input parameters.

How can XSS be effectively prevented? FilterChain filters incoming requests at the Tomcat level.