Vous apprendre comment SpringMVC gère les demandes

De nombreuses personnes utiliseront SpringMVC, mais la manière dont il traite les demandes n'est pas claire. Lorsque nous apprenons une connaissance, la comprendre nous permettra de mieux l'utiliser. Voyons comment SpringMVC traite les demandes.

Méthode de processus de demande

Première image:

Vous apprendre comment SpringMVC gère les demandes

Le framework Spring MVC est également un framework Web axé sur les demandes et utilise le modèle de contrôleur frontal (est utilisé pour fournir un mécanisme de traitement des demandes centralisé, toutes les demandes seront traitées par un seul gestionnaire pour la conception, puis mappées en fonction de la demande. Les règles sont distribuées aux contrôleurs de page correspondants (actions / processeurs) pour traitement. Tout d'abord, examinons le processus de traitement des requêtes Spring MVC dans son ensemble:

  1. Tout d'abord, l'utilisateur envoie une demande, et la demande est capturée par le contrôleur frontal SpringMVC (DispatherServlet);

  2. Le contrôleur frontal (DispatherServlet) analyse l'URL de la demande pour obtenir l'URI de la demande et appelle HandlerMapping en fonction de l'URI;

  3. Le contrôleur frontal (DispatherServlet) obtient le HandlerExecutionChain retourné (y compris l'objet Handler et l'intercepteur correspondant à l'objet Handler);

  4. DispatcherServlet sélectionne un HandlerAdapter approprié en fonction du HandlerExecutionChain obtenu. (Remarque: si le HandlerAdapter est obtenu avec succès, la méthode preHandler (...) de l'intercepteur sera exécutée à ce moment);

  5. HandlerAdapter adapte et exécute le Handler correspondant en fonction du Handler demandé; HandlerAdapter extrait les données du modèle dans la Request, remplit les paramètres d'entrée du Handler et commence à exécuter le Handler (Controller). Dans le processus de remplissage des paramètres d'entrée du gestionnaire, en fonction de la configuration, Spring effectuera un travail supplémentaire:

    HttpMessageConveter: convertit le message de demande (tel que Json, xml, etc.) en un objet et convertit l'objet en informations de réponse spécifiées;

    Conversion de données: conversion de données du message de demande. Telles que la conversion de chaîne en entier, double, etc.

    Formatage des données: comme la conversion de chaînes de caractères en nombres formatés ou dates formatées;

    Vérification des données: vérifiez la validité des données (longueur, format, etc.) et stockez le résultat de la vérification dans BindingResult ou Error);

  6. Une fois Handler exécuté, renvoyez un ModelAndView (c'est-à-dire un modèle et une vue) à HandlerAdaptor;

  7. L'adaptateur HandlerAdaptor renvoie le résultat de l'exécution ModelAndView au contrôleur frontal;

  8. Après avoir reçu ModelAndView, le contrôleur frontal demande le résolveur de vue correspondant;

  9. Le résolveur de vue analyse ModelAndView et renvoie la vue correspondante;

  10. Rendre la vue et renvoyer la vue rendue au contrôleur frontal;

  11. Enfin, le contrôleur frontal répond à l'utilisateur ou au client avec la page rendue.

Pratique de cas

Interprétation du code source d'exécution de la requête SpringMVC

Pour toutes les entrées de requête (à l'exception des ressources statiques) du projet SpringMVC, voici le contrôleur frontal DispatcherServlet configuré dans le fichier web.xml:

<!-- servlet请求分发器 -->
<servlet>
  <servlet-name>springMvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:servlet-context.xml</param-value>
  </init-param>
  <!-- 表示启动容器时初始化该Servlet -->
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>springMvc</servlet-name>
  <!-- 这是拦截请求, /代表拦截所有请求,拦截所有.do请求 -->
  <url-pattern>/</url-pattern>
</servlet-mapping>

Le diagramme d'héritage UML DispatcherServlet est le suivant:

Vous apprendre comment SpringMVC gère les demandes

Nous nous concentrons ici sur la partie ligne bleue de la structure d'héritage: DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet -> GenericServlet -> Servlet. Le diagramme de séquence de base de la requête est le suivant:

Vous apprendre comment SpringMVC gère les demandes

Pour le traitement des requêtes Web, tout le monde sait que la méthode de service est réécrite en héritant de HttpServlet. Ouvrez le code source de DispatcherServlet et constatez que la méthode de service que nous recherchons ne s'affiche pas ici. À ce stade, recherchez la classe parent FrameworkServlet comme suit: Vous pouvez voir la classe parente Remplacez la méthode de service HttpServlet.

Service FrameworkServlet #

/**
 * Override the parent class implementation in order to intercept PATCH requests.
 */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
   HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
   if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
      proce***equest(request, response);
   }
   else {
      super.service(request, response);
   }
}

À partir de l'analyse du code source, la méthode proc *** equest0 est exécutée lorsque la méthode de demande est une demande de correctif ou null. Dans d'autres cas, la méthode de service parent est appelée. Tout le monde sait que la plupart des demandes SpringMVC sont principalement des demandes get | post. Continuez pour le moment Recherchez la classe parent FrameworkServlet HttpServletBean (la classe abstraite hérite de HttpServlet et ne remplace pas la méthode de service, continuez donc à rechercher) -> Méthode de service HttpServlet:

Service HttpServlet #

    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;

        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }

        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;

        service(request, response);
    }
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

On peut voir que le service HttpServlet a été surchargé et que différentes méthodes de traitement sont appelées en fonction de différents types de requêtes. Ici, prenez la requête get comme exemple. Lorsque la méthode request est une requête get, la méthode doGet est appelée dans la méthode de service surchargée pour le traitement. Spécial Notez que: HttpServlet a une implémentation de méthode doGet, mais il existe également une implémentation de méthode doGet dans les sous-classes héritées. Quelle méthode est appelée? Évidemment, en appelant la méthode doGet de la sous-classe (polymorphisme orienté objet !!!) À partir du diagramme UML hérité, la sous-classe la plus externe implémente la méthode doGet pour FrameworkServlet:

FrameworkServlet # doGet & proce *** equest

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   proce***equest(request, response);
}

    protected final void proce***equest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 系统计时开始时间
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // 国际化
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);
        //构建ServletRequestAttributes对象
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        //异步管理
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //初始化ContextHolders
        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException | IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
             //恢复原来的LocaleContext和ServiceRequestAttributes到LocaleContextHolder和RequestContextHolder,避免影响Servlet以外的处理,如Filter
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            logResult(request, response, failureCause, asyncManager);
            //发布ServletRequestHandlerEvent消息,这个请求是否执行成功都会发布消息的
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

// initContextHolders(request, localeContext, requestAttributes);
    private void initContextHolders(HttpServletRequest request,
            @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
    }

Cette méthode fait probablement ces quelques choses: paramètres internationaux, créer l'objet ServletRequestAttributes, initialiser les détenteurs de contexte (c'est-à-dire placer l'objet Request dans le contexte du thread, si vous souhaitez obtenir les objets de requête et de réponse dans la méthode plus tard, vous pouvez appeler LocaleContextHolder peut correspondre à la méthode), puis appelez la méthode doService. Pour la méthode doService, la classe FrameworkServlet ne fournit pas d'implémentation, cette méthode est implémentée par la sous-classe DispatcherServlet:

DispatcherServlet # doService

La méthode d'entrée pour exécuter le traitement dans DispatcherServlet est doService. Comme cette classe hérite de la classe FrameworkServlet, la méthode doService () est remplacée:

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   // Keep a snapshot of the request attributes in case of an include,
   // to be able to restore the original attributes after the include.
   Map<String, Object> attributesSnapshot = null;
   if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
         String attrName = (String) attrNames.nextElement();
         if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
            attributesSnapshot.put(attrName, request.getAttribute(attrName));
         }
      }
   }

    //Spring上下文
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    //国际化解析器
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    //主题解析器
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    //主题
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    //重定向的数据  
   if (this.flashMapManager != null) {
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
         request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
   }

   try {
      //request设置完相关的属性做真正的请求处理
      doDispatch(request, response);
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
         // Restore the original attribute snapshot, in case of an include.
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
   }
}

L'ensemble de la méthode examine les opérations de traitement: traitement de la requête de la balise include, placement du contexte dans l'attribut request, placement de l'analyseur d'internationalisation dans l'attribut request, placement de l'analyseur de thème dans l'attribut request, et placement du thème dans l'attribut request Dans l'attribut request, la méthode principale de doDispatch est appelée pour traiter la demande après le traitement des données de demande redirigées:

DispatcherServlet # doDispatch

Cette méthode est appelée dans la méthode doService et l'ensemble du flux de traitement des demandes est conçu à partir du bas:

  • Trouver un gestionnaire selon la demande
  • Trouvez le HandlerAdapter correspondant selon Handler
  • Utiliser HandlerAdapter pour gérer le gestionnaire
  • Appelez la méthode processDispatchResult pour traiter les résultats ci-dessus (y compris le rendu de vue et la sortie à l'utilisateur)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
          // 校验是否为上传请求 是上传请求执行解析 否则返回request
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // 根据访问的Handler 返回指定对应的HandlerExecutionChain对象 这里从HandlerMapping 集合中查找 HandlerExecutionChain 对象包含Handler与拦截器HandlerInterceptor列表
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // 根据得到的Handler 获取对应的HandlerAdaptor对象
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // 处理GET、HEAD请求的Last-Modified
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            //当数据没有更改时,就直接返回上次的数据,提高效率
             if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         //执行Interceptor的preHandle 
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // 执行Handler 返回ModelAndView
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          //如果需要异步处理,直接返回
         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         //当view为空时,根据request设置默认view,如Handler返回值为void
         applyDefaultViewName(processedRequest, mv);
         //执行相应Interceptor的postHandle 
         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);
      }
       //处理返回结果,包括处理异常、渲染页面,发出完成通知触发Interceptor的afterCompletion
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      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);
         }
      }
   }
}
  1. doDispatcher vérifie d'abord s'il s'agit d'une demande de téléchargement, et si oui, convertit la demande en MultipartHttpServletRequest et définit l'indicateur multipartRequestParsed sur true;

  2. Obtenez HandlerExecutionChain via getHandler;
  3. Le traitement de la dernière modification des requêtes GET et HEAD consiste principalement à déterminer si la valeur Last-Modified a été modifiée pour déterminer s'il faut utiliser les données mises en cache;
  4. Ensuite, appelez le preHandle de l'intercepteur correspondant à son tour pour exécuter l'opération d'interception de l'intercepteur;
  5. Une fois la méthode d'intercepteur preHandle exécutée, elle commence à adapter l'exécution correspondante du Handler via HandlerAdapter (voici la méthode Controller à exécuter). Une fois que le Handler a traité la demande, si un traitement asynchrone est requis, il retourne directement. Si un traitement asynchrone n'est pas requis, Lorsque la vue est vide, définissez la vue par défaut, puis exécutez le postHandle de l'intercepteur correspondant.
  • Handler: Le processeur correspond directement au C dans MVC, qui est la couche Controller. Il a de nombreuses manifestations spécifiques, qui peuvent être des classes ou des méthodes (généralement des méthodes), car sa définition est Object. Toutes les méthodes de @RequestMapping marquées dans la méthode peuvent être considérées comme un Handler, tant que la requête peut être réellement traitée peut être considérée comme un Handler.
  • HandlerMapping: utilisé pour rechercher le gestionnaire. De nombreuses demandes sont traitées dans SpringMVC. Chaque demande nécessite un gestionnaire pour être traitée. Après avoir reçu la demande, que le gestionnaire doit traiter, la recherche est effectuée via HandlerMapping.
  • HandlerAdapter: adaptateur, différents gestionnaires doivent trouver différents HandlerAdapter pour appeler Handler. Tout comme les outils sont nécessaires dans l'usine, les ouvriers (HandlerAdapter) utilisent des outils (Handler) pour terminer le travail, et HandlerMapping est utilisé pour trouver les outils correspondants en fonction du travail à effectuer.

DispatcherServlet # processDispatchResult

La méthode processDispatchResult est principalement utilisée pour traiter les résultats précédemment renvoyés, comprenant trois parties: la gestion des exceptions, le rendu de la page et le déclenchement de la méthode afterCompletion de l'Interceptor. Les exceptions traitées sont générées lors du traitement de la méthode doDispatch de la requête.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;
   // 如果请求过程中有异常抛出则处理异常
   if (exception != null) {
      if (exception instanceof ModelAndViewDefiningException) {
         logger.debug("ModelAndViewDefiningException encountered", exception);
         mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      }
      else {
         Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   //执行页面渲染操作
   if (mv != null && !mv.wasCleared()) {
      render(mv, request, response);
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace("No view rendering, null ModelAndView returned.");
      }
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
   }

   // Handler请求处理完,触发Interceptor的afterCompletion
   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

Rendu de la vue de rendu:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);

   View view;
   String viewName = mv.getViewName();
   if (viewName != null) {
      // We need to resolve the view name.
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   else {
      // No need to lookup: the ModelAndView object contains the actual View object.
      view = mv.getView();
      if (view == null) {
         throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
               "View object in servlet with name '" + getServletName() + "'");
      }
   }

   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view [" + view + "] ");
   }
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
       // 渲染页面处理
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throw ex;
   }
}

Aujourd'hui, nous en sommes venus à comprendre les idées fondamentales de MVC dans le cadre SpringMVC, l'analyse du processus de demande interne de SpringMVC et l'interprétation du code au niveau source, afin que tout le monde puisse vraiment comprendre l'exécution originale de l'ensemble du cadre à partir du niveau inférieur, et enfin une image pour résumer le processus d'exécution actuel de l'analyse du code source .

Vous apprendre comment SpringMVC gère les demandes

Extension ~ MVC

Model-View-Controller (MVC) est une idée de conception bien connue basée sur la conception d'applications d'interface. Il dissocie la logique métier de l'interface principalement en séparant les rôles de modèle, de vue et de contrôleur dans l'application. Habituellement, le modèle est chargé d'encapsuler les données d'application et de les afficher dans la couche de vue. La vue affiche uniquement les données et ne contient aucune logique métier. Le contrôleur est chargé de recevoir les demandes des utilisateurs et d'appeler les services d'arrière-plan (service ou DAO) pour traiter la logique métier. Après le traitement, la couche de gestion d'arrière-plan peut renvoyer des données à afficher dans la couche de vue. Le contrôleur collecte ces données et prépare le modèle pour l'affichage dans la couche de vue. L'idée centrale du modèle MVC est de séparer la logique métier de l'interface et de leur permettre d'être modifiées séparément sans affecter l'autre.

Vous apprendre comment SpringMVC gère les demandes

Je suppose que tu aimes

Origine blog.51cto.com/14819669/2541655
conseillé
Classement