DispatcherServlet inheritance structure

HttpServlet .java << HttpServletBean.java << FrameworkServlet.java << DispatcherServlet.java

When the request comes in, it is processed by doGet/doPost

FrameworkServlet.java

​ .doGet(request,response)/doPost(request,response)

​ .processRequest(request,response)

Abstract doService(Request, Response) subclass implementation

DispatcherServlet.

​ .doService(request,response)

**.doDispatch(Request, Response)** core method, which handles the subsequent logic of the request

The general flow for SpringMVC to handle requests

DispatcherServlet. DoDispatch () method of the core steps:

  1. Call getHandler to get the HandlerExecutionChain (Handler + interceptor) that can handle the current request
  2. GetHandlerAdapter (mappedHandler.gethandler ()) gets the adapter capable of executing the handler
  3. Perform front intercept mappedHandler. ApplyPreHandle (), positive sequence perform interceptor preHandle () method
  4. The adapter calls Handler to perform ha.handle(always returns a ModelAndView object)
  5. ApplyDefaultViewName () is called to process the resulting view object
  6. Perform rear intercept mappedHandler. ApplyPostHandle ((), flashback perform interceptor postHandle () method
  7. Call the processDispatchResult() method to complete the view jump
  8. The interceptor’s afterCompletion() method is eventually executed
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    try {
        ModelAndView mv = null;
		Exception dispatchException = null;
        try {
            // Check if it is a file upload request
            processedRequest = checkMultipart(request);

            // Omit some code
            / /...

            /* 1. Get the Controller that handles the current request. Instead of returning the Controller directly, return the HandlerExecutionChain request Handler object that encapsulates Handler and Inteceptor */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                // If handler is empty, 404 is returned
                noHandlerFound(processedRequest, response);
                return; 
            }

            // 2. Get the HandlerAdapter that processes the request
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // Omit some code
            / /...

            // 3. Execute the front interceptor
            if(! mappedHandler.applyPreHandle(processedRequest, response)) {return; 
            }

            // 4. The actual processor processes the request and returns the result View object (ModelAndView)
            mv = ha.handle(processedRequest, response,mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return; 
            }
            // 5. Result view object processing
            applyDefaultViewName(processedRequest, mv);
            
            // 6. Execute the rear interceptor
            mappedHandler.applyPostHandle(processedRequest, response, mv);
            
        }catch (Exception ex) {
            dispatchException = ex; 
        }catch (Throwable err) {
        // As of 4.3, we're processing Errors thrown from handler methods as well,
        // making them available for @ExceptionHandler methods and other scenarios.
        	dispatchException = new NestedServletException("Handler dispatch failed",err);
        }
        // 7. Jump to page and render view
        processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
        
    }catch (Exception ex) {
        // The afterCompletion method of the HandlerInterceptor will eventually be called
        triggerAfterCompletion(processedRequest, response, mappedHandler,ex);
    }catch (Throwable err) {
        // The afterCompletion method of the HandlerInterceptor will eventually be called
        triggerAfterCompletion(processedRequest, response, mappedHandler,
        new NestedServletException("Handler processing failed", err));
    }finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if(mappedHandler ! =null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }}else {
            // Clean up any resources used by a multipart request.
            if(multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}Copy the code

GetHandler method resolution

  1. Traverse two hanlderMappings: BeanNameUrlHandlerMapping RqeustMappingHandlerMapping
  2. Get the corresponding handler from the request link

getHandlerAdapter

  1. Walk through the three HandlerAdapters: HttpRequestHandlerAdapter SimpleControllerHandlerAdapter, RequestMappingHandlerAdapter whether implementation types implement corresponding HttpRequest (inheritance) / Controller (inherited) / @requestMapping

Nine components

  • MultipartResolver MultipartResolver

  • LocaleResolver Internationalization parser

  • ThemeResolver Topic resolver

  • List handlerMappings Processor mapper component

  • List handlerAdapters Processor adapter components

  • The List handlerExceptionResolvers parser component

  • RequestToViewNameTranslator viewNameTranslator default view name converter component

  • FlashMapManager (Redirection properties)

  • List viewResolvers viewResolvers

The nine components define interfaces, which define specifications. Concrete subclasses are used to implement concrete business logic

Custom implementation MVC

Custom MVC implements the front-end request processing flow.

The annotations involved are: @controller, @requestMapping, and @interceptor

@controller indicates a request handler and is added to the IOC container

@requestMapping Specifies the requested mapping path

The @interceptor specifies a class as an Interceptor that will be added to the IOC container

Where the @Controller, @interceptor annotation class, will be added to the IOC container, implementation in ioc-AOP project judgment processing added to the container. See the custom implementation IOC-AOP project for details.

The @requestmapping annotation is used when initializing the Servlet to identify the relevant RequestMapping path, and is used to determine which class and function the front-end request path needs to execute

Implementation steps

  1. Define class: DispatcherServlet, inheriting HttpServlet
  2. Rewrite the init (ServletConfig config)
    1. Get the context and check if it has been initialized
    2. Context is not initialized. Create a new one. Already initialized, we use the context
    3. Call the refresh method of the Context to initialize the configuration of the MVN (that is, perform the ioc initialization process)
    4. Call onRefresh method to initialize MVC (including all processors, interceptors)
  3. The first time a request is received, the initialization method init() is executed
  4. Perform the requested
    1. GetHandlerMapping () to get the processor mapper
    2. GetRequestArgs (), gets an array of all request parameters
    3. DoHandler (), the execution handler

DispatcherServlet

The logic for the context of the WebApplicationContext is implemented in the project for custom IOC-AOP. Since this is the process for implementing MVC, the implementation details of WebApplicationContext are not covered here.

public class DispatcherServlet extends HttpServlet {

    WebApplicationContext context;

    /**
     * 处理器映射器
     */
    List<HandlerMapping> handlerMappings = new ArrayList<>();
    /** * interceptor mapper */
    List<HandlerInterceptorMapping> handlerInterceptorMappings = new ArrayList<>();
    /** * URI interceptor cache * 

* Key is the uri of the request * value is the chain of interceptors containing the execution order of multiple interceptors */

Map<String, HandlerInterceptorChain> handlerInterceptorCache = new HashMap<>(); Class[] servletInnerParamClasses = new Class[]{ HttpSession.class, HttpServletRequest.class, HttpServletResponse.class }; /** * initialize servlet * <p> * 1. Obtain the context and check whether it has been initialized * 2. Create a new one if the context is not initialized. Context * 3. Call the context refresh method to initialize the configuration of the MVN (that is, perform the ioc initialization process) * 4. Call the onRefresh method to initialize MVC (including all processors, interceptors) * *@param config * @throws ServletException */ @Override public void init(ServletConfig config) throws ServletException { ServletContext servletContext = config.getServletContext(); try { Object context = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); // Get the springMVC configuration path String configLocation = config.getInitParameter(ContextLoader.CONFIG_LOCATION_PARAM); if (null == configLocation || configLocation.length() == 0) { configLocation = "applicationContext.xml"; } if (null == context) { this.context = new XmlWebApplicationContext(servletContext, configLocation); } else { this.context = (XmlWebApplicationContext) context; this.context.setConfigLocation(configLocation); } // Start a refresh, which initializes all bean configuration information this.context.refresh(); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); this.onRefresh(); } catch(Exception e) { e.printStackTrace(); }}@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { executePost(req, resp); } /** * Execute the request * 1. Get the processor mapper * 2. Get all the request parameter arrays * 3. Execute processor * *@param request * @param response */ private void executePost(HttpServletRequest request, HttpServletResponse response) { // Query the corresponding handler based on the requested URL HandlerMapping handlerMapping = getHandlerMapping(request); if (null == handlerMapping) { response.setStatus(404); return; } try { // Get the request parameters Object[] args = getRequestArgs(request, response, handlerMapping); / / execution this.doHandler(request, response, handlerMapping, args); } catch (IllegalAccessException e) { e.printStackTrace(); response.setStatus(400); } catch (InvocationTargetException e) { response.setStatus(400); e.printStackTrace(); }}/** * get all parameters of the request **@param request * @param response * @param handlerMapping * @return* / private Object[] getRequestArgs(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) { Map<String, Integer> paramsIndexMapping = handlerMapping.getParamsIndexMapping(); /* Iterates over the parameters of the request, recording the parameter values of the same type and name to the corresponding subscript store */ Object[] args = new Object[paramsIndexMapping.size()]; Map<String, String[]> requestParameterMap = request.getParameterMap(); String paramStr = null; for (Map.Entry<String, String[]> param : requestParameterMap.entrySet()) { String key = param.getKey(); if(! paramsIndexMapping.containsKey(key)) {continue; } String[] value = param.getValue(); if (value.length > 1) { // Multiple values of the same type, use, concatenate. Name = 1 & name = 2 - > 1, 2 StringJoiner stringJoiner = null; for (String s : value) { if (null == stringJoiner) { stringJoiner = new StringJoiner(s); } else { stringJoiner.add(s); } } paramStr = stringJoiner.toString(); if(! paramsIndexMapping.containsKey(key)) {continue; }}else { paramStr = value[0]; } Integer index = paramsIndexMapping.get(key); args[index] = paramStr; } /* Handle three special arguments: HttpServletRequest, HttpServletResponse, and HttpSession */ for (Class servletInnerParamClass : servletInnerParamClasses) { if(! paramsIndexMapping.containsKey(servletInnerParamClass.getSimpleName())) {continue; } if (servletInnerParamClass == HttpServletRequest.class) { args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = request; } else if (servletInnerParamClass == HttpServletResponse.class) { args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = response; } else if(servletInnerParamClass == HttpSession.class) { HttpSession session = request.getSession(); args[paramsIndexMapping.get(servletInnerParamClass.getSimpleName())] = session; }}return args; } /** * Formally executes the request * 1. Obtains the interceptor first * 2. Executes the preintercept logic * 3. Execute handler * 4. Execute post-intercept logic * *@param request * @param response * @param handlerMapping * @param args * @throws InvocationTargetException * @throws IllegalAccessException */ private void doHandler(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping, Object[] args) throws InvocationTargetException, IllegalAccessException { /* Get interceptor chain */ HandlerInterceptorChain interceptorChain = getInterceptorChain(request, response, handlerMapping); Method method = handlerMapping.getMethod(); Object handler = handlerMapping.getHandler(); /* 执行前置拦截 */ if (null! = interceptorChain && ! interceptorChain.doPreHandle(request, response, handlerMapping.getHandler(), method)) {return; } /* Execute the controller logic */ method.invoke(handler, args); /* Perform a post-intercept */ if (null != interceptorChain) { interceptorChain.doPostHandle(request, response, handlerMapping.getHandler(), method); } } /** * get interceptor chain * 1. Get interceptor chain from cache * 2. Add to uri interceptor cache * 4. Return interceptor chain * *@param request * @param response * @param handlerMapping * @return* / private HandlerInterceptorChain getInterceptorChain(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) { String requestURI = request.getRequestURI(); // Get the URI interceptor chain from the cache if (handlerInterceptorCache.containsKey(requestURI)) { return handlerInterceptorCache.get(requestURI); } List<HandlerInterceptor> matchInterceptList = new ArrayList<>(); // If there is no interceptor chain in the cache, it calculates the interceptors that match for (HandlerInterceptorMapping interceptorMapping : handlerInterceptorMappings) { String[] interceptUris = interceptorMapping.getInterceptUri(); String[] excludeUris = interceptorMapping.getExcludeUri(); // Determine whether interception is required for (String uri : interceptUris) { boolean intercept = true; // Check whether the interception URI is in the configuration Pattern pattern = Pattern.compile(uri); Matcher matcher = pattern.matcher(requestURI); if(! matcher.matches()) {continue; } // Determine if this URI is excluded for (String excludeUri : excludeUris) { Pattern excludePattern = Pattern.compile(excludeUri); Matcher excludeMatcher = excludePattern.matcher(requestURI); if(! excludeMatcher.matches()) {continue; } intercept = false; } if (intercept) { matchInterceptList.add(interceptorMapping.getInterceptor()); } } } HandlerInterceptorChain chain = null; if(! matchInterceptList.isEmpty()) {Key = uri, value = HandlerInterceptorChain chain = new HandlerInterceptorChain(matchInterceptList); } // Add cache handlerInterceptorCache.put(requestURI, chain); return chain; } /** * get processor mapper **@param request * @return* / private HandlerMapping getHandlerMapping(HttpServletRequest request) { if (handlerMappings.isEmpty()) { return null; } String requestURI = request.getRequestURI(); // Iterate through all handlerMapping // Match handlerMapping with the same URL for (HandlerMapping handlerMapping : handlerMappings) { Pattern urlPattern = handlerMapping.getPattern(); // Regex matches Matcher matcher = urlPattern.matcher(requestURI); if(! matcher.matches()) {continue; } return handlerMapping; } return null; } private void onRefresh(a) { initHandlerMapping(); initHandlerInterceptions(); } /** * Initializes all interceptors */ private void initHandlerInterceptions(a) { ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory(); if (beanFactory instanceofDefaultListableBeanFactory) { DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; List<String> beanDefinitionNames = listableBeanFactory.getBeanDefinitionNames();for (String beanDefinitionName : beanDefinitionNames) { Object bean = this.context.getBean(beanDefinitionName); Class<? > beanClass = bean.getClass();// Not adding an Interceptor annotation or implementing a HandlerInterceptor interface is not an Interceptor if(! beanClass.isAnnotationPresent(Interceptor.class) || ! (beaninstanceof HandlerInterceptor)) { continue; } Interceptor interceptor = beanClass.getAnnotation(Interceptor.class); String[] interceptUri = interceptor.interceptUri(); String[] excludeUri = interceptor.excludeUri(); handlerInterceptorMappings.add(newHandlerInterceptorMapping(interceptUri, excludeUri, (HandlerInterceptor) bean)); }}}/** * Build the HandlerMapping handler to map the url to the method */ private void initHandlerMapping(a) { ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory(); if (beanFactory instanceof DefaultListableBeanFactory) { DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; List<String> beanDefinitionNames = listableBeanFactory.getBeanDefinitionNames(); processHandlerMappings(beanDefinitionNames); } } private void processHandlerMappings(List<String> beanDefinitionNames) { for (String beanDefinitionName : beanDefinitionNames) { Object bean = this.context.getBean(beanDefinitionName); processHandlerMapping(bean); }}/** * handles the mapping between bean url and method **@param bean */ private void processHandlerMapping(Object bean) { // Check if there is an @controller annotationClass<? > beanClass = bean.getClass();if(! beanClass.isAnnotationPresent(Controller.class)) {return; } // Get RequestMapping annotation information on the bean String baseUrl = ""; if (beanClass.isAnnotationPresent(RequestMapping.class)) { RequestMapping requestMapping = beanClass.getAnnotation(RequestMapping.class); baseUrl = standardizedUrl(requestMapping.value()); } // Iterate through all the bean methods to see if there is a RequestMapping annotation. Method[] methods = beanClass.getMethods(); for (Method method : methods) { if(! method.isAnnotationPresent(RequestMapping.class)) {// No comments continue; } RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); String methodUrl = standardizedUrl(requestMapping.value()); String url = baseUrl + methodUrl; // Create a mapping container HandlerMapping handlerMapping = new HandlerMapping(bean, method, Pattern.compile(url)); // Calculate method parameter position Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; Class<? > parameterType = parameter.getType();// Check whether the parameter type is inside the servlet boolean isInnerParamType = false; for (Class servletInnerParamClass : servletInnerParamClasses) { if (parameterType == servletInnerParamClass) { isInnerParamType = true; handlerMapping.getParamsIndexMapping().put(servletInnerParamClass.getSimpleName(), i); break; }}if(! isInnerParamType) {// It is not a parameter of type servletInnerParamClasseshandlerMapping.getParamsIndexMapping().put(parameter.getName(), i); }}// Save the mappinghandlerMappings.add(handlerMapping); }}/** * standardize url **@param url * @return* / private String standardizedUrl(String url) { if (!"".equals(url) && ! url.startsWith("/")) { url += "/"; } returnurl; }}Copy the code

HandlerInterceptor Interceptor interface

/** * interceptor interface */
public interface HandlerInterceptor {

    /** * request preintercept **@param request
     * @param response
     * @paramHandler Intercepted controller *@paramMethod The method that is intercepted@return* /
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
        return true;
    }

    /** * request post-intercept **@param request
     * @param response
     * @paramHandler Intercepted controller *@paramMethod Intercepted method */
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {}}Copy the code

The interceptor executes the HandlerInterceptorChain

/** * Interceptor chain is used to execute all interceptors corresponding to the URI * 

* pre-intercept, preHandle() method of the interceptor list * post-intercept, preHandle() method of the interceptor list */

public class HandlerInterceptorChain { private List<HandlerInterceptor> interceptorList; public HandlerInterceptorChain(List<HandlerInterceptor> interceptorList) { this.interceptorList = interceptorList; } /** * Execute intercepting logic **@param request * @param response * @param handler * @param handler * @return method */ public boolean doPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) { for (HandlerInterceptor handlerInterceptor : interceptorList) { if(! handlerInterceptor.preHandle(request, response, handler, method)) {return false; }}return true; } /** * perform interception logic in reverse order **@param request * @param response * @param handler * @param method */ public void doPostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) { for (int i = interceptorList.size() - 1; i >= 0; i--) { interceptorList.get(i).postHandle(request, response, handler, method); }}}Copy the code

HandlerInterceptorMapping processor interceptor mapper

/** * processor interceptor mapper */
public class HandlerInterceptorMapping {
    private String[] interceptUri;
    private String[] excludeUri;
    private HandlerInterceptor interceptor;

    public HandlerInterceptorMapping(String[] interceptUri, String[] excludeUri, HandlerInterceptor interceptor) {
        this.interceptUri = interceptUri;
        this.excludeUri = excludeUri;
        this.interceptor = interceptor; }}Copy the code

Processor mapper HandlerMapping

/** * Processor mapper * mapping information between URL and method */
public class HandlerMapping {
    /** * The controller class to map */
    private Object handler;
    /** * mapping method */
    private Method method;
    /** * the url of the regular expression */
    private Pattern pattern;
    /** * the coordinate position of the method parameter */
    private Map<String, Integer> paramsIndexMapping;

    public HandlerMapping(Object controller, Method method, Pattern pattern) {
        this.pattern = pattern;
        this.handler = controller;
        this.method = method;
        this.paramsIndexMapping = newHashMap<>(); }}Copy the code

The Interceptor annotation @interceptor

/** * interceptor annotations * Annotated classes represent interceptors that will be added to the IOC container. * /
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Interceptor {
    // Intercepted URI
    String[] interceptUri() default {};

    // UrIs that need not be intercepted
    String[] excludeUri() default {};
}
Copy the code

Handler annotation @Controller

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
Copy the code

The processor mapper annotation @requestMapping

@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    String value(a) default "";
}
Copy the code

use

Configure web. XML

<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>com.otoomo.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>mvc.xml</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
Copy the code

Configuration of MVC. XML


      
<beans>
    <component-scan base-package="com.xxx.controller"/>
</beans>
Copy the code

Define handler

@Controller
@RequestMapping("/transfer")
public class TransferController {

    @RequestMapping
    public void transfer(HttpServletRequest request, HttpServletResponse response, String fromCardNo, String toCardNo, String money) throws IOException {
        / /... The business logic}}Copy the code

Defining interceptors

@Interceptor( interceptUri = {"/transfer"} )
public class SecurityInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
        System.out.println("SecurityInterceptor preHandle......");
		/ /... The business logic
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Method method) {
        System.out.println("SecurityInterceptor postHandle......"); }}Copy the code