Spring5源码14-SpringMVC-DispatcherServlet处理请求核心逻辑

Spring MVC过滤器与拦截器:从原理到实战的完整解析 在Java Web开发中,请求处理链是构建健壮应用架构的核心机制。其基本原理是通过在HTTP请求与响应的生命周期中插入可插拔的组件,实现对流量的统一管控与业务逻辑的织入。这一技术价值在于解耦横切关注点,提升代码复用性与可维护性。典型的应用场景包括全局安全防护、日志记录、权限校验与数据预处理等。本文聚焦于Spring MVC框架中两个关键组件——过滤器和拦截器,深入剖析其核心差异。过滤器作为Servlet规范定义的‘门户保安’,作用于更底层的容器级别,擅长处理全局性、基础性任务;而拦截器作为Spring框架的 阅读详情

1. Spring mvc 流程

其时序图:

整体流程图:

SpringMVC 工作流程描述:

  1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获;
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI)。然后根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain对象的形式返回;
  3. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)
  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
    • HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
    • 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
    • 数据根式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
    • 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
  5. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象;
  6. 根据返回的ModelAndView,选择一个适合的ViewResolver(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet ;
  7. ViewResolver 结合Model和View,来渲染视图
  8. 将渲染结果返回给客户端。

2. HttpServlet & FrameworkServlet

下面我们先看看 DispatcherServlet 的两个父类: HttpServlet 和 FrameworkServlet 。

FrameworkServlet 重写了 HttpServlet 的 service 方法。 我们这里先来看看 FrameworkServlet#service 方法。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
        // 解析请求方式
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
                processRequest(request, response);
        }
        else {
                super.service(request, response);
        }
}

这里我们需要关注两个点 : processRequest(request, response); 和 super.service(request, response);。下面我们一一来看

2.1 HttpServlet#service

我们知道 HttpServlet 类中分别提供了相应的服务方法(doGet、doPost等),如下图

同时,HttpServlet 会根据请求的不同形式引导到对应导函数中处理,如下 HttpServlet#service(HttpServletRequest, HttpServletResponse):

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();
            // 如果是 get 方法
    if (method.equals(METHOD_GET)) {
            //  lastModified  缓存判断
        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 {
            // 如果设置了缓存时间,则判断在ifModifiedSince  之后的时间,是否被修改。
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // 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);
    }
}

 而这几个函数最常用的就是 doGet() 和 doPost() 。这两个方法被 FrameworkServlet 重写了。我们来看看在 FrameworkServlet 中的实现。

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

        processRequest(request, response);
}

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

        processRequest(request, response);
}
... 

我们可以很清楚的看到,对于大部分的请求,还是依赖于 HttpServlet#service(HttpServletRequest, HttpServletResponse) 来进行一个请求的分发。对于我们常见的 doGet() 和 doPost() 方法都是直接调用 processRequest(request, response);, 而processRequest方法 的具体实现在FrameworkServlet#processRequest 中 。

2.2 FrameworkServlet#processRequest

因此。接下来我们就来看看 org.springframework.web.servlet.FrameworkServlet#processRequest:

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   // 记录当前时间,用于记录web请求的记录时间
   long startTime = System.currentTimeMillis();
   Throwable failureCause = null;

   // 1234 的目的是为了保证当前线程的 LocaleContext 和 RequestAttributes 在当前请求后还能恢复,所以提取保存
   // 1. 提取当前线程的 LocaleContext  属性
   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   // 2. 根据当前的request 创建对应的 LocaleContext ,并绑定到当前线程
   LocaleContext localeContext = buildLocaleContext(request);

   // 3. 提取当前线程的 RequestAttributes 属性
   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   // 4. 根据当前的request 创建对应的 RequestAttributes ,并绑定到当前线程
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

   try {
      // todo 5. 委托给 doservice方法进行进一步处理
      doService(request, response);
   }
   catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
   }
   catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
   }

   finally {
      // 6. 请求结束,恢复线程原状
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
         requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      // 发布请求结束的通知事件,无论成功与否
      publishRequestHandledEvent(request, response, startTime, failureCause);
   }
}

由于逻辑都被封装到 doService(request, response); 中,所以这里还是比较简单。而doService又被 DispatcherServlet实现了。因此我们这里来看看 DispatcherServlet#doService。

3.DispatcherServlet#doService

@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));
         }
      }
   }

   // Make framework objects available to handlers and view objects.
   // 基本的东西保存到request作用域中 方便获取, 设置一些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);
   }

   RequestPath previousRequestPath = null;
   if (this.parseRequestPath) {
      previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
      ServletRequestPathUtils.parseAndCache(request);
   }

   try {
      // todo 派发功能
      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);
         }
      }
      if (this.parseRequestPath) {
         ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
      }
   }
}

很显然,这 “爷仨” 一层一层传递,终于传递到了DispatcherServlet 手里,这里我们直接开始看doDispatch(request, response);,这里才是核心逻辑的所在。

3. DispatcherServlet#doDispatch

// SpringMVC处理请求的核心流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   // handler (目标方法)的执行链
   HandlerExecutionChain mappedHandler = null;
   // 文件上传标志
   boolean multipartRequestParsed = false;

   // 对异步请求的支持(Servlet3.0 以后才有的,Webflux)
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

      try {
         // 1. 如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request
         // 检查  当前是否文件上传的请求
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         // 2. 根据request 寻找对应的 handler
         // todo 构造出了【目标方法+拦截器整个链路】  决定使用哪个Handler处理当前请求
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            // 3. 如果没有找到对应的handler,则通过 response 反馈错误信息
            // 如果找不到处理,就报404错误
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         // 4. 根据当前的 handler 找到对应的HandlerAdapter
         // todo 获取适配器
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         // 5. last-modified 的缓存处理
         // 如果当前handler 支持 last-modified 头处理则进行缓存处理
         String method = request.getMethod();
         boolean isGet = HttpMethod.GET.matches(method);
         // 如果是 get请求或者 head 请求则进入该分支
         if (isGet || HttpMethod.HEAD.matches(method)) {
            // todo 调用 HandlerAdapter#getLastModified 方法 来获取最后修改时间
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            // todo 判断到目前为止是否有过修改,没有则直接return。实现缓存的功能
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         // 6.1 执行 所有拦截器 的preHandle 方法,使用mappedHandler整个链
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
         // todo 7. 真正执行目标方法,mappedHandler.getHandler() 并 返回视图
         // 反射执行目标方法,确定参数值,处理返回值【封装成ModelAndView】
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         // // 8. 视图名称转换应用于需要添加前缀的情况 默认的ViewName
         applyDefaultViewName(processedRequest, mv);
         // 6.2 执行 所有拦截器 的postHandle 方法,使用mappedHandler整个链
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         // 记录下来异常,在 9 中统一处理
         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);
      }
      // todo 9. 处理最后的结果
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      // // 6.3 拦截器完成方法的调用 下面的即使执行完了,异常还是抛出去
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      // 6.3 拦截器完成方法的调用
      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);
         }
      }
   }
}

DispatcherServlet#doDispatch 涉及的地方就比较多,下面我们一个一个看:

3.1 checkMultipart(request)

对于请求的处理,Spring首先考虑的是对 Multipart 的处理,如果是 MultipartContent 类型的request 则转换request 为 MultipartHttpServletRequest 类型的request。简单来说,就是判断是否是文件请求。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
   // multipartResolver 文件上传解析器 isMultipart方法
   if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
      if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
         if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
            logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
         }
      }
      else if (hasMultipartException(request)) {
         logger.debug("Multipart resolution previously failed for current request - " +
               "skipping re-resolution for undisturbed error rendering");
      }
      else {
         try {
            return this.multipartResolver.resolveMultipart(request);
         }
         catch (MultipartException ex) {
            if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
               logger.debug("Multipart resolution failed for error dispatch", ex);
               // Keep processing error dispatch with regular request handle below
            }
            else {
               throw ex;
            }
         }
      }
   }
   // If not returned before: return original request.
   return request;
}

核心方法this.multipartResolver.isMultipart(request),我们来看一StandardServletMultipartResolver#isMultipart:

@Override
public boolean isMultipart(HttpServletRequest request) {
   // 所有文件上传请求都会有这个
   return StringUtils.startsWithIgnoreCase(request.getContentType(),
         (this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}

3.2 getHandler(processedRequest);

这一步的目的是 根据request 信息遍历 HandlerMapping 找到对应的handler。 具体代码如下:

// DispatcherServlet#getHandler
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 这里的 this.handlerMappings 在没有手动调整的情况下是加载的默认配置文件中的数据
	if (this.handlerMappings != null) {
		// 遍历每一个 handleMapping,解析 request,直到碰到一个解析成功的,将解析后的 Handler拦截链路返回。
		for (HandlerMapping mapping : this.handlerMappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

关于 mapping.getHandler(request) 的处理。后面详细介绍。

3.3 noHandlerFound(processedRequest, response);

正常情况下,每一个请求都应该对应一个 Handler,因为每个请求都应该在后台有对应的处理逻辑。而逻辑的实现就是在Handler 中。正常情况下,如果没有URL匹配的Handler,我们可以通过设置默认的Handler 来解决这一问题,不过如果没有设置默认的Handler。则只能通过Response 向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (pageNotFoundLogger.isWarnEnabled()) {
                pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
        }
        // 判断DispatcherServlet 属性设置,是否需要抛出异常
        if (this.throwExceptionIfNoHandlerFound) {
                throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                                new ServletServerHttpRequest(request).getHeaders());
        }
        else {
                // 否则直接抛出错误 404
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
}

3.4 getHandlerAdapter(mappedHandler.getHandler());

这一步的目的是根据 Handler 寻找对应的 HandlerAdapter。这里使用了适配器模式,遍历所有的 Adapter。根据 HandlerAdapter#supports 方法来判断是否支持当前Handler 的解析,如果支持,则返回。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
                for (HandlerAdapter adapter : this.handlerAdapters) {
                        if (adapter.supports(handler)) {
                                return adapter;
                        }
                }
        }
        throw new ServletException("No adapter for handler [" + handler +
                        "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

我们这里返回的是RequestMappingHandlerAdapter,其判定条件如下:

@Override
public final boolean supports(Object handler) {
        // 	supportsInternal((HandlerMethod) handler)) 返回 true
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

3.5 Last-Modified 的缓存处理

在客户端第一次输入 URL 时,服务器端返回内容和200状态码,表示请求成功,同时会添加一个 “Last-Modified” 的响应头,表示此文件在服务器上最后的更新时间。

客户端第二次请求此URL时2,客户端会向服务器发送请求头 “If-Modified-Since”,询问服务器该时间之后当前请求是否有被修改过,如果服务端内容没有变化,则会自动返回 304 状态码(只要响应头,内容为空,这样就节省了带宽)。

Spring 实现 Last-Modified 机制,只需要实现 LastModified 接口就可以。如下:

@Component public class BeanNameSayController implements Controller, LastModified { private long lastModified;

@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
    return new ModelAndView("/hello");
}

@Override
public long getLastModified(HttpServletRequest request) {
    if (lastModified == 0L){
        lastModified = System.currentTimeMillis();
    }
    return lastModified;
}

后面具体分析。

3.6 拦截器的调用

我们添加的拦截器,会在下面这些地方被调用合适的方法。

3.6.1 mappedHandler.applyPreHandle(processedRequest, response)

逻辑很简单,遍历所有的拦截器,分别调用 preHandle 前置方法。

// org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取所有的拦截器
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
                for (int i = 0; i < interceptors.length; i++) {
                        HandlerInterceptor interceptor = interceptors[i];
                        // 调用前置方法。如果有一个前置方法返回false,则直接调用完成方法
                        if (!interceptor.preHandle(request, response, this.handler)) {
                                triggerAfterCompletion(request, response, null);
                                return false;
                        }
                        this.interceptorIndex = i;
                }
        }
        return true;
}

3.6.2. mappedHandler.applyPostHandle(processedRequest, response, mv);

逻辑基本相同,没有什么区别,这里调用的是程序执行后的后置方法

// org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
                throws Exception {

        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
                for (int i = interceptors.length - 1; i >= 0; i--) {
                        HandlerInterceptor interceptor = interceptors[i];
                        // 调用后置方法
                        interceptor.postHandle(request, response, this.handler, mv);
                }
        }
}

3.6.3 triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

调用拦截器结束方法(视图呈现之后)。

// org.springframework.web.servlet.DispatcherServlet#triggerAfterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
                throws Exception {

        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
                for (int i = this.interceptorIndex; i >= 0; i--) {
                        HandlerInterceptor interceptor = interceptors[i];
                        try {
                                // 调用结束方法
                                interceptor.afterCompletion(request, response, this.handler, ex);
                        }
                        catch (Throwable ex2) {
                                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                        }
                }
        }
}

3.7 ha.handle(processedRequest, response, mappedHandler.getHandler());

这里是真正调用 Handler 处理业务逻辑的地方。我们这里看的是 RequestMappingHandlerAdapter。 ha.handle(processedRequest, response, mappedHandler.getHandler()); 方法会调用 RequestMappingHandlerAdapter#invokeHandlerMethod 方法。同时在这个方法里面,会通过反射的方式调用 HandlerMthoder。并将返回结果封装成 ModelAndView。

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

   // 装饰器模式,把原生的request, response 封装到一个对象中 方便后续只用这一个参数就可以
   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   try {
      // 数据绑定器
      WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
      // 获取到模型工厂 Model(要交给页面的数据)  View(我们要去的视图)
      ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
      // 将 handlerMethod 转换成 ServletInvocableHandlerMethod
      ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
      // 设置ServletInvocableHandlerMethod  的一些属性
      // todo 参数解析器
      if (this.argumentResolvers != null) {
         invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
      }
      // todo 返回值解析器
      if (this.returnValueHandlers != null) {
         invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
      }
      invocableMethod.setDataBinderFactory(binderFactory);
      invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

      // 模型和视图的容器,以后流程共享ModelAndView数据的临时存储器
      ModelAndViewContainer mavContainer = new ModelAndViewContainer();
      mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
      modelFactory.initModel(webRequest, mavContainer, invocableMethod);
      mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

      // 异步请求
      AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
      asyncWebRequest.setTimeout(this.asyncRequestTimeout);

      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
      asyncManager.setTaskExecutor(this.taskExecutor);
      asyncManager.setAsyncWebRequest(asyncWebRequest);
      asyncManager.registerCallableInterceptors(this.callableInterceptors);
      asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

      if (asyncManager.hasConcurrentResult()) {
         Object result = asyncManager.getConcurrentResult();
         mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
         asyncManager.clearConcurrentResult();
         LogFormatUtils.traceDebug(logger, traceOn -> {
            String formatted = LogFormatUtils.formatValue(result, !traceOn);
            return "Resume with async result [" + formatted + "]";
         });
         invocableMethod = invocableMethod.wrapConcurrentResult(result);
      }

      // todo 开始执行目标方法,反射调用HandlerMethod
      invocableMethod.invokeAndHandle(webRequest, mavContainer);
      if (asyncManager.isConcurrentHandlingStarted()) {
         return null;
      }

      // todo 封装ModelAndView
      return getModelAndView(mavContainer, modelFactory, webRequest);
   }
   finally {
      webRequest.requestCompleted();
   }

我们首先来看下 核心方法invocableMethod.invokeAndHandle(webRequest, mavContainer);:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // todo 目标方法的反射执行
   Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
   setResponseStatus(webRequest);

   if (returnValue == null) {
      if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
         disableContentCachingIfNecessary(webRequest);
         mavContainer.setRequestHandled(true);
         return;
      }
   }
   else if (StringUtils.hasText(getResponseStatusReason())) {
      mavContainer.setRequestHandled(true);
      return;
   }

   mavContainer.setRequestHandled(false);
   Assert.state(this.returnValueHandlers != null, "No return value handlers");
   try {
      // todo 返回值的处理
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
   }
   catch (Exception ex) {
      if (logger.isTraceEnabled()) {
         logger.trace(formatErrorForReturnValue(returnValue), ex);
      }
      throw ex;
   }
}

3.7.1 请求参数解析器argumentResolvers

核心方法invokeForRequest中,

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // todo 获取方法的  请求参数
   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Arguments: " + Arrays.toString(args));
   }
   // 反射执行
   return doInvoke(args);
}

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   // 拿到方法的所有参数
   MethodParameter[] parameters = getMethodParameters();
   if (ObjectUtils.isEmpty(parameters)) {
      return EMPTY_ARGS;
   }

   // 准备args的数组,跟方法的参数长度一样,挨个确定每个参数都是什么值
   Object[] args = new Object[parameters.length];
   for (int i = 0; i < parameters.length; i++) {
      MethodParameter parameter = parameters[i];
      parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
      // 先去已提供的参数里面查找
      args[i] = findProvidedArgument(parameter, providedArgs);
      if (args[i] != null) {
         continue;
      }
      // todo 解析参数
      if (!this.resolvers.supportsParameter(parameter)) {
         throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
      }
      try {
         args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
      }
      catch (Exception ex) {
         // Leave stack trace for later, exception may actually be resolved and handled...
         if (logger.isDebugEnabled()) {
            String exMsg = ex.getMessage();
            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
               logger.debug(formatArgumentError(parameter, exMsg));
            }
         }
         throw ex;
      }
   }
   return args;
}

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
   // 先看缓存中有没有
   HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
   if (result == null) {
      // 27个参数解析器 遍历执行
      for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
         //
         if (resolver.supportsParameter(parameter)) {
            result = resolver;
            // 支持这种参数的解析器也会被放到缓存中argumentResolverCache
            this.argumentResolverCache.put(parameter, result);
            break;
         }
      }
   }
   return result;
}

一共有27种请求参数解析器,针对不同的参数进行使用不同的解析器。

执行目标方法就是通过反射执行的。

3.7.2 返回值参数解析器

this.returnValueHandlers.handleReturnValue(
      returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

 

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

   // todo 找到合适的返回值处理器
   HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
   if (handler == null) {
      throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
   }
   // 处理返回值
   handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
   boolean isAsyncValue = isAsyncReturnValue(value, returnType);
   for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
         continue;
      }
      if (handler.supportsReturnType(returnType)) {
         return handler;
      }
   }
   return null;
}

一共有15种返回值解析器。 

有一个RequestResponseBodyMethodProcessor,只要你标注了@ResponseBody注解,使用这个处理器进行处理,将返回值数据直接返回。

3.7.3 封装ModelAndView

getModelAndView(mavContainer, modelFactory, webRequest);

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
      ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

   // modelFactory 准备模型数据,请求域数据共享,session里面的数据存储到请求域中
   modelFactory.updateModel(webRequest, mavContainer);
   if (mavContainer.isRequestHandled()) {
      return null;
   }
   ModelMap model = mavContainer.getModel();
   ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
   if (!mavContainer.isViewReference()) {
      mav.setView((View) mavContainer.getView());
   }
   if (model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
      HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
      if (request != null) {
         // todo 重定向数据的共享 RedictView ,先把数据转移到request,再把request转移到session中
         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
   }
   return mav;
}

 

3.8 applyDefaultViewName(processedRequest, mv);

当 控制层的返回结果是 null 或者 void 时,则表明没有找到对应视图,Spring 会根据request 信息来进行解析,查找默认的视图。

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
        // 视图转换器应用于添加前后缀的情况
        if (mv != null && !mv.hasView()) {
                String defaultViewName = getDefaultViewName(request);
                if (defaultViewName != null) {
                        mv.setViewName(defaultViewName);
                }
        }
}

3.9 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

这一步的作用是集中处理请求异常并解析最终的ModelAndView,根据解析出来的视图跳转页面。

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);
         // todo 处理异常的情况
         // 如果所有异常都不能处理,这个异常就直接抛出去
         mv = processHandlerException(request, response, handler, exception);
         errorView = (mv != null);
      }
   }

   // 上面所有的异常解析器都没能处理这个异常,就不执行下面的逻辑了。。
   // Did the handler return a view to render?
   // 动态策略
   // @ResponseBody(提前在解析返回值的时候,就已经把数据写出去了)
   // 如果在Handler实例的处理过程中返回了 view,则需要做页面处理
   if (mv != null && !mv.wasCleared()) {
      // todo 渲染,来解析模型和视图
      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;
   }

   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      // 触发 拦截器完成事件
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}

3.9.1 render(mv, request, response);

在最后的处理中,一定会涉及页面的跳转问题。而在render(mv, request, response); 完成了页面的跳转。

// org.springframework.web.servlet.DispatcherServlet#render
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();
        // 如果viewname不为null,则需要通过viewName 解析出来对应的 View
        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 {
                // 如果viewName 为null,则认为 ModelAndView 直接指定了View。不需要解析了。
                // 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() + "'");
                }
        }

        // Delegate to the View object for rendering.
        if (logger.isTraceEnabled()) {
                logger.trace("Rendering view [" + view + "] ");
        }
        try {
                // 设置视图状态。可能是 404, 500 等情况
                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;
        }
}

resolveViewName(viewName, mv.getModelInternal(), locale, request); 是 通过视图解析器进行视图解析。返回合适视图。具体实现如下。

// org.springframework.web.servlet.DispatcherServlet#resolveViewName
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
            Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
            // 遍历视图解析器,直到有解析器能解析出来视图
            for (ViewResolver viewResolver : this.viewResolvers) {
                    View view = viewResolver.resolveViewName(viewName, locale);
                    if (view != null) {
                            return view;
                    }
            }
    }
    return null;
}

我们看一下 viewResolver.resolveViewName(viewName, locale) 方法。这里我们看InternalResourceViewResolver#resolveViewName 方法,其方法是在父类AbstractCachingViewResolver#resolveViewName中实现,如下:

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
        // 如果没有缓存,则直接创建 View
        if (!isCache()) {
                return createView(viewName, locale);
        }
        else {
                // 从缓存中获取视图
                Object cacheKey = getCacheKey(viewName, locale);
                View view = this.viewAccessCache.get(cacheKey);
                if (view == null) {
                        synchronized (this.viewCreationCache) {
                                view = this.viewCreationCache.get(cacheKey);
                                if (view == null) {
                                        // Ask the subclass to create the View object.
                                        view = createView(viewName, locale);
                                        if (view == null && this.cacheUnresolved) {
                                                view = UNRESOLVED_VIEW;
                                        }
                                        if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                                                this.viewAccessCache.put(cacheKey, view);
                                                this.viewCreationCache.put(cacheKey, view);
                                        }
                                }
                        }
                }
                else {
                        if (logger.isTraceEnabled()) {
                                logger.trace(formatKey(cacheKey) + "served from cache");
                        }
                }
                return (view != UNRESOLVED_VIEW ? view : null);
        }
}

createView 方法 被 UrlBasedViewResolver 重写了。UrlBasedViewResolver#createView具体如下:

@Override
protected View createView(String viewName, Locale locale) throws Exception {
        // If this resolver is not supposed to handle the given view,
        // return null to pass on to the next resolver in the chain.
        // 如果当前视图解析器无法解析该视图,则返回null
        if (!canHandle(viewName, locale)) {
                return null;
        }

        // Check for special "redirect:" prefix.
        // 处理前缀为  "redirect:" (重定向)的情况
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
                String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
                RedirectView view = new RedirectView(redirectUrl,
                                isRedirectContextRelative(), isRedirectHttp10Compatible());
                String[] hosts = getRedirectHosts();
                if (hosts != null) {
                        view.setHosts(hosts);
                }
                return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
        }

        // Check for special "forward:" (请求转发)prefix.
        // 处理前缀为  "forward:" 的情况
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
                String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
                InternalResourceView view = new InternalResourceView(forwardUrl);
                return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
        }

        // Else fall back to superclass implementation: calling loadView.
        // 调用父类的方法创建视图
        return super.createView(viewName, locale);
}

super.createView(viewName, locale); 调用 AbstractCachingViewResolver#createView 如下:

// org.springframework.web.servlet.view.AbstractCachingViewResolver#createView
protected View createView(String viewName, Locale locale) throws Exception {
        return loadView(viewName, locale);
}

// org.springframework.web.servlet.view.UrlBasedViewResolver#loadView
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
        AbstractUrlBasedView view = buildView(viewName);
        View result = applyLifecycleMethods(viewName, view);
        return (view.checkResource(locale) ? result : null);
}

// org.springframework.web.servlet.view.UrlBasedViewResolver#buildView
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        Class<?> viewClass = getViewClass();
        Assert.state(viewClass != null, "No view class");

        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
        // 设置视图 url 添加前缀和后缀
        view.setUrl(getPrefix() + viewName + getSuffix());
        view.setAttributesMap(getAttributesMap());

        String contentType = getContentType();
        if (contentType != null) {
                view.setContentType(contentType);
        }

        String requestContextAttribute = getRequestContextAttribute();
        if (requestContextAttribute != null) {
                view.setRequestContextAttribute(requestContextAttribute);
        }

        Boolean exposePathVariables = getExposePathVariables();
        if (exposePathVariables != null) {
                view.setExposePathVariables(exposePathVariables);
        }
        Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
                view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }
        String[] exposedContextBeanNames = getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
                view.setExposedContextBeanNames(exposedContextBeanNames);
        }

        return view;
}

在 org.springframework.web.servlet.view.AbstractView#render 中完成了视图跳转。 对于ModelView 的使用,我们可以将一些属性放入其中,然后在页面上通过 JSTL 语法或者 request 获取属性,这个功能的实现就是在这里完成的。实现原理很简单,就是将要用到的属性方法request中,以便在其他地方可以获取到。

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
                HttpServletResponse response) throws Exception {

        if (logger.isDebugEnabled()) {
                logger.debug("View " + formatViewName() +
                                ", model " + (model != null ? model : Collections.emptyMap()) +
                                (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
        }
        // 将要用到的属性放入到mergedModel  中
        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        // 准备 Response
        prepareResponse(request, response);
        // 处理页面跳转。同时将 mergedModel  保存到request中
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

我们看一下InternalResourceView#renderMergedOutputModel方法:

@Override // 真真的渲染逻辑
protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

   // Expose the model object as request attributes.
   // todo 暴露model的数据作为请求域 中的数据
   exposeModelAsRequestAttributes(model, request);

   // Expose helpers as request attributes, if any.
   exposeHelpers(request);

   // Determine the path for the request dispatcher.
   String dispatcherPath = prepareForRendering(request, response);

   // Obtain a RequestDispatcher for the target resource (typically a JSP).
   RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
   if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
            "]: Check that the corresponding file exists within your web application archive!");
   }

   // If already included or response already committed, perform include, else forward.
   if (useInclude(request, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
         logger.debug("Including [" + getUrl() + "]");
      }
      rd.include(request, response);
   }

   else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      if (logger.isDebugEnabled()) {
         logger.debug("Forwarding to [" + getUrl() + "]");
      }
      // todo 转发
      rd.forward(request, response);
   }
}

SSM框架整合实战:Spring+SpringMVC+MyBatis企业级开发指南 SSM框架整合是Java企业级开发的核心技术栈,由Spring、SpringMVC和MyBatis三大框架组成。Spring框架通过IoC容器实现组件管理,AOP支持面向切面编程;SpringMVC基于MVC模式处理Web请求;MyBatis作为ORM框架简化数据库操作。这种组合相比传统SSH架构更轻量高效,特别适合构建高并发Web应用。在电商系统、金融平台等实际项目中,SSM框架通过分层架构实现业务解耦,配合Maven进行依赖管理,使用Druid连接池优化数据库访问。本文详细演示了从环境搭建、配置整合到C 阅读详情

相关推荐

【SpringBoot 4.x 第61节】Spring MVC 请求处理链路源码解析:DispatcherServlet、HandlerMapping 与 HandlerAdapter!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。本专栏面向 有一定 Java 基础,但尚未系统学习 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,带你从第一个 Hello API 开始,逐步完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 327

【Spring入门-05】DispatcherServlet

Spring MVC 基于模型-视图-控制器(Model-View-Controller,MVC)模式实现,用于构建松散耦合的 Web 应用。 Spring MVC 框架是围绕 DispatcherServlet 设计的,DispatcherServlet 用来处理所有的 HTTP 请求和响应。

LeifChen的博客 321

k8s RoCE 部署: k8s-rdma-shared-dev-plugin + macvlan cni

写给自己的入门篇 文章目录前言一、创建 k8s 集群二、启用 primary network三、启用 secondary networkk8s-rdma-shared-dev-pluginMultus CNISecondary CNIMulti-Network CRD四、启用 pod五、在 pod 中启动 RoCE 流量总结 前言 写给自己的扫盲篇。后续会在原理方面持续更新 一、创建 k8s 集群 k8s 集群的创建有多种方法,可以按照官方文档的说明来.

sunshuying1010的博客 9785

Exception AttributeError: “‘NoneType‘ object has no attribute ‘error‘“ in <bound method Connection._

使用redis 3.5.2 ,自己写了一个redis工具类,外部的python的脚本去调他,在脚本执行完之后,提示该错误。 原因: 真正报错的位置在: connection.py 中 Stack Overflow大神做出了解释:os 模块在调用Connection.__del__之前已经被卸载了,打印了一下,确实是None。 解决的办法,在对redis操作完之后,主动 conn.connection_pool.disconnect() , 或者更新redis包到3.5.3:...

yournevermore的博客 1649

【5G演进】4G到5G演进之网络架构与协议栈

4G到5G演进之网络架构与协议栈

Learning makes me happy (0^◇^0) 5533

VASPKIT绘制平面平均电荷差分密度

平面平均密度差(planar-averaged electron density difference)是密度差的衍生。密度差是个三维空间函数,经常通过绘制平面图、等值面图方式图形化考察。虽然这两种图很直观,但平面图会受到截面选取的影响,而等值面图会受到等值面数值(isovalue)选取的影响,因此都有一定任意性,也不容易定量讨论。对于电子转移有比较明确方向的情况,可以绘制平面平均密度差曲线,由此可以清楚地考察垂直于选取的方向上每个截面上的密度差积分值,在定量讨论、对比上比较便利。

weixin_43948934的博客 1万+

Exception AttributeError: "'NoneType' object has no attribute

  今天根据 &lt;简明 python 教程http://sebug.net/paper/python/ch11s06.html&gt;学习案例时,遇到 下面的异常.       #!/usr/local/python # -*- coding:utf-8 -*- # filename: objvar.py class Person: ''' Represents a...

lucklyming的专栏 820

OFDR解调全同弱反射光纤阵列

全同弱反射光纤光栅阵列是在一根光纤上密集刻写波长相同的WFBG,由于每个光栅的反射都比较弱,可实现一根光纤上复用成千上万个光栅,阵列上每个光栅栅区长度相同,相邻光栅之间栅距相同,呈周期性分布。2、获取测量信号:对弱栅阵列加载状态下,使用OFDR系统再次获取弱栅时域曲线,为测量信号。(这里需要注意的是对弱栅阵列加载后,不能使弱栅中心波长漂移到激光器波长扫描范围之外,若超出范围则OFDR技术不能获取弱栅反射信号,因此在选择弱栅阵列波长时尽量让其中心波长在激光器波长扫描范围中间,确保温度/应变测量范围。

m0_69386663的博客 1958

spring拦截异常统一处理的几种方式以及优缺点(代码只是小部分,逻辑请根据实际情况编写)

spring拦截异常统一处理

qq_39835420的博客 1607

【Zabbix实战之部署篇】Docker部署Zabbix+grafana监控平台

【Zabbix实战之部署篇】docker部署Zabbix+grafana监控平台

jks212454的博客 2580

YOLOv5算法改进(21)— 添加CA注意力机制 + 更换Neck网络之BiFPN + 更换损失函数之EIoU

本节课作者就结合论文对YOLOv5进行组合改进(添加CA注意力机制+更改Neck网络之BiFPN+更换损失函数之EIoU),小白必学!

小哥谈 2820

【Spring】抽丝剥茧SpringMVC-DispatcherServlet

DispatcherServlet 使用SpringMVC框架的同学一定知道DispatcherServlet。SpringMVC处理Http请求就是从DispatcherServlet开始。SpringMC简化了Servlet规范的开发模式,定义了通用了Servlet(DispatcherServlet)与Servlet容器(jetty/tomcat)交互。开发者专注于业务逻辑的开发。下图是DispatcherServlet的类图 可以看出DispatcherServlet其实就是...

beFocused的博客 540

【yolo】如何在 YOLOv8 中添加负样本以减少误检

在YOLOv8目标检测中,误检(False Positive)是一个常见问题,即模型将非目标区域错误识别为目标。通过引入负样本(Negative Samples),即不包含任何目标框的图像,可以有效减少误检。在YOLOv8中添加负样本的方法是为这些图像提供空的标注文件(.txt),确保每张图像都有对应的标注文件,即使内容为空。可以使用Python脚本自动生成这些空标注文件。使用负样本时需注意数量比例、标注一致性、验证集同步和图像质量。适当加入负样本能显著提升模型鲁棒性,降低误判风险。

songhuangong123的博客 2879

Spring MVC请求处理流程深度解析:从源码到实战的架构剖析

Spring MVC请求处理流程解析 摘要:本文通过一场技术面试对话,系统剖析了Spring MVC的核心请求处理机制。首先概述了Spring MVC的架构,包括DispatcherServlet、HandlerMapping等关键组件及其协作关系。重点解读了DispatcherServlet的doDispatch()方法执行流程:从请求解析、处理器查找、拦截器前置处理,到适配器调用控制器方法,最后完成视图渲染。整个流程展现了Spring MVC如何高效处理HTTP请求,体现了其模块化设计和灵活的扩展能力。

weixin_54726354的博客 691

【unity小技巧】委托(Delegate)的基础使用和介绍

在Unity中,委托(Delegate)是一种用于处理方法回调的特殊类型。它允许您将方法作为参数传递给其他方法,并在需要时调用这些方法。委托在游戏开发中非常有用,特别是在事件处理、消息传递和异步操作方面。它提供了一种灵活的方式来注册和触发方法,而无需显式地知道这些方法的实现细节。个人理解:可以把委托理解为一个容器,容器里面放可以放很多函数方法。一调用委托,里面所有的函数方法都会同步被执行。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 7239

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Java八股文面试之框架篇(Spring、SpringMVC、Mybatis、Spring Security)

Java八股文面试之框架篇(Spring、SpringMVC、Mybatis)

y516369的博客 1304

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

面试有没有看过spring源码_面试官:你分析过SpringMVC的源码吗?

1. MVC使用在研究源码之前,先来回顾以下springmvc 是如何配置的,这将能使我们更容易理解源码。1.1 web.xml1 2 mvc-dispatcher3 org.springframework.web.servlet.DispatcherServlet4 8 9 contextConfigLocation10 class...

weixin_39718888的博客 94

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

SpringMVC工作原理

1、Spring mvc介绍 SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。 2、SpringMVC使用 需要在web.xml中配置DispatcherServlet。并且需要配置spring监听...

dingjian0009的博客 674

SpringMVC执行流程

gpt是的,Spring MVC中的请求处理流程是由DispatcherServlet作为中央控制器,根据请求的URL分发给相应的HandlerMapping进行处理。它是springmvc最为核心的组件 起到中央控制的作用。谁去处理我们的cotrol去处理例如咱们都在control包下放了userControl,员工Control 这都叫控制器 映射器 映射一个请求的路径 前端过来的请求他其实里面包含一个具体的圆也就是url 那么url到底要更哪个控制器control进行映射。

lpblog的博客 1063

springmvc与mysql工作原理_SpringMVC工作原理

1、Spring mvc介绍SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。2、SpringMVC使用需要在web.xml中配置DispatcherServlet。并且需要配置spring监听器ContextLoader...

weixin_39714763的博客 402

菜鸟级springmvc+spring+mybatis整合开发用户登录功能(下)

昨天介绍了mybatis与spring的整合,今天我们完成剩下的springmvc的整合工作。 要整合springmvc首先得在web.xml中配置springmvc的前端控制器DispatcherServlet,它是springmvc的核心,为springmvc提供集中访问点,springmvc对页面的分派与调度功能主要靠它完成。 在我们之前配置的web.xml中加入以下springmvc的

bieleyang的博客 1089

java mvc原理图_SpringMVC实现原理及详解

1、Spring mvc介绍SpringMVC框架是以请求为驱动,围绕Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。2、SpringMVC使用需要在web.xml中配置DispatcherServlet。并且需要配置spring监听器ContextLoader...

weixin_28756005的博客 270

《Spring-MVC》系列文章目录

Spring MVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级Web框架,它通过把Model(模型)、View(视图)、Controller(控制器)分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便测试。Spring MVC是Spring框架的一部分,使用Spring MVC可以非常方便的设计出干净的Web层。带家人们学习最新的技术,推荐大家跟着我的博客学习,有问题可以评论或私聊我,尽力帮助大家。我负责把最精华、最干货的内容总结给大家;

zjg 1214

SpringMVC源码分析1:SpringMVC概述

转载自:https://blog.youkuaiyun.com/a724888/article/details/76014532 Web MVC简介 1.1、Web开发中的请求-响应模型: 在Web世界里,具体步骤如下: 1、  Web浏览器(如IE)发起请求,如访问http://sishuok.com 2、  Web服务器(如Tomcat)接收请求,处理请求(比如用户新增,则将把用户保存一下)...

harryptter的专栏 288

Spring源码学习笔记(3)

Spring源码学习笔记(三)   前言----    最近花了些时间看了《Spring源码深度解析》这本书,算是入门了Spring的源码吧。打算写下系列文章,回忆一下书的内容,总结代码的运行流程。推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的《Spring源码深度解析》这本书,会是个很好的入门。 DispatcherServlet 实现核心功能   ...

weixin_30467087的博客 140
上一篇: 面试突击80:说一下 Spring 中 Bean 的生命周期?
下一篇: MyBatis-Plus保姆级快速上手教程
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值