SpringMVC源码-SpringMVC源码请求执行流程及重点方法doDispatch讲解

一、开始请求

在浏览器访问http://localhost:8080/spring_mymvc/userlist这个接口,是个get请求。
FrameworkServlet类的service方法会被请求到:
调用路径如下:

service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

在这里插入图片描述
可以看到该请求时get请求,走下面分支处理。进入super.service(request, response);发现方法跟丢了。。。。。。。
因为源码时在tomcat里的
在这里插入图片描述
super.service(request, response);Ctrl加鼠标左键点进去发现,进入到HttpServlet类的service方法:
在这里插入图片描述
会进入到FrameworkServlet的doGet方法,然后走processRequest(request, response);最后执行doService(request, response);方法
FrameworkServlet #doGet 执行路径:

doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

可以看到无论git还是post请求最终都会执行processRequest(request, response);方法
在这里插入图片描述
因为不管是什么请求方式除了参数的处理不一样 其他的都有相似之处 所以都执行一个最终的公共方法。
processRequest

/**处理此请求,发布一个事件,而不管结果如何。<p>实际的事件处理是由抽象的{@link doService}模板方法执行的。* Process this request, publishing an event regardless of the outcome.* <p>The actual event handling is performed by the abstract* {@link #doService} template method.*/protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 记录当前时间,用于计算处理请求花费的时间long startTime = System.currentTimeMillis();// 记录异常,用于保存处理请求过程中发送的异常Throwable failureCause = null;// 获取LocaleContextHolder中原来保存的LocaleContext(保存的本地化信息)  事务中就是这样 先把最开始的保存 把新的放进去 用完新的再把最开始保存的恢复回去LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 获取当前请求的LocaleContextLocaleContext localeContext = buildLocaleContext(request);// 获取RequestContextHolder总原来保存的RequestAttribute(管理request和session的属性) spring事务处理的时候 有类似的操作 获取当前的 保存起来 后续恢复RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 获取当前请求的ServletRequestAttributeServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);// 获取异步管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 将当前请求的LocaleContext和ServletRequestAttribute设置到LocaleContextHolder和RequestContextHolderinitContextHolders(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和ServletRequestAttributes到LocaleContextHolder和RequestContextHolder中resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}// 如果日志级别为debug,则打印请求日志logResult(request, response, failureCause, asyncManager);// 发布ServletRequestHandledEvent请求处理完成事件publishRequestHandledEvent(request, response, startTime, failureCause);}}

执行真正的逻辑
doService(request, response);

/**公开dispatcherservlet特定的请求属性,并委托给{@link doDispatch}进行实际的调度。* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}* for the actual dispatching.*/@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {// 如果日志级别为 DEBUG,则打印请求日志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.// 当include请求时对request的Attribute做快照备份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.// 设置Spring框架中的常用对象到request属性中,这四个属性会在handler和view中使用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());//重定向的时候方便参数的传递// FlashMap的相关配置,主要用于Redirect转发时参数的传递,此处有一个应用场景:如果post请求是提交表单,提交完之后redirect到一个显示订单的页面,// 此时需要知道一些订单的信息,但redirect本身没有提交参数的功能,如果想传递参数,那么就必须要写到url,而url有长度的限制同时还容易对外暴露,此时// 可以使用flashMap来传递参数,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 {// 执行请求的分发doDispatch(request, response);}finally {// 异步处理相关if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.// 还原request快照的属性if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}

执行请求的分发
doDispatch(request, response);

/*** 处理实际的分发到处理器中* 内层是捕获在对请求进行处理的过程中抛出的异常,在处理异常的时候会设置到dispatcherException变量,然后在processorDispatcherResult方法中进行处理* 外层是处理渲染页面时抛出的异常,主要是处理processDispatchResult方法抛出的异常** Process the actual dispatching to the handler.* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.* @param request current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 实际处理时所用的request,如果不是上传请求,则直接使用接收到的request,否则封装成上传类型的requestHttpServletRequest processedRequest = request;// 处理请求的处理器链(包含处理器和对应的interceptor)HandlerExecutionChain mappedHandler = null;// 是不是上传请求的标志boolean multipartRequestParsed = false;// 获取异步管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);//这里划为两个try ,第一个是处理请求后端,第二个是处理数据渲染。分别处理异常了try {// 封装model和view的容器ModelAndView mv = null;// 处理请求过程中抛出的异常,但是不包含渲染过程中抛出的异常Exception dispatchException = null;try {// 检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象processedRequest = checkMultipart(request);// 设置上传请求的标志multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller//  如果获取不到,则根据配置抛出异常或返回404错误if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 获得当前handler对应的HandlerAdapter对象 controller或者控制器有多种不同的实现方式 为了方便后续过程中调用 使用适配器模式来 解决HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器会在返回的请求头里包含一个last_modified的属性,// 代表资源最后时什么时候修改的,在浏览器以后发送请求的时候,会同时发送之前接收到的Last_modified.服务器接收到带last_modified的请求后,// 会跟实际资源的最后修改时间做对比,如果过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可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的preHandler// 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 真正的调用handler方法,也就是执行对应的方法,并返回视图mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 如果需要异步处理,直接返回if (asyncManager.isConcurrentHandlingStarted()) {return;}// 当view为空时,根据request设置默认的viewapplyDefaultViewName(processedRequest, mv);// 执行响应的interceptor的postHandler方法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的afterCompletionprocessDispatchResult(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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.// 删除上传请求的资源if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

重要的方法:

  • 检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
    processedRequest = checkMultipart(request);
  • 获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)
    mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller
  • 获得当前handler对应的HandlerAdapter对象 controller或者控制器有多种不同的实现方式 为了方便后续过程中调用 使用适配器模式来 解决
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  • 处理GET、HEAD请求的Last-Modified,当浏览器第一次跟服务器请求资源时,服务器会在返回的请求头里包含一个last_modified的属性,
    // 代表资源最后时什么时候修改的,在浏览器以后发送请求的时候,会同时发送之前接收到的Last_modified.服务器接收到带last_modified的请求后,
    // 会跟实际资源的最后修改时间做对比,如果过期了返回新的资源,否则直接返回304表示未过期,直接使用之前缓存的结果即可
    lastModified
  • 执行响应的Interceptor的preHandler
    // 注意:该方法如果有一个拦截器的前置处理返回false,则开始倒序触发所有的拦截器的 已完成处理
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
  • 真正的调用handler方法,也就是执行对应的方法,并返回视图
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 执行响应的interceptor的postHandler方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
  • 处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

SpringMVC的九大内置组件:
在这里插入图片描述

doDispatch方法的执行流程:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1550075.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

视频美颜SDK与直播美颜工具API的架构设计与实现

视频美颜SDK的出现&#xff0c;不仅大大提升了用户体验&#xff0c;还为开发者提供了实现实时美颜功能的技术支持。接下来&#xff0c;小编将与大家深入探讨视频美颜SDK与直播美颜工具API的架构设计与实现方案&#xff0c;帮助开发者更好地理解这一技术的核心原理。 一、视频美…

完美解决Idea中如何对Java Agent进行断点调试的方式

1、前言 在日常开发中&#xff0c;可能会存在写一个Java Agent到项目中去&#xff0c;Agent的实现可能是复杂的&#xff0c;有时候会出现attach到进程上后&#xff0c;发现没效果&#xff0c;也不知道怎么调试&#xff0c;只能通过打日志的方式实现&#xff0c;效率实在是太低…

开放式蓝牙耳机哪个品牌更靠谱?5款高性价比开放式耳机推荐

谈到开放式蓝牙耳机哪个品牌更靠谱&#xff0c;市场上有许多优秀的选择。以前也经常使用入耳式耳机&#xff0c;但总是会感觉耳机插在耳朵里不舒服&#xff0c;戴久了耳朵很疼&#xff0c;跑步的时候还总掉。还有在过马路的时候接电话、听音乐&#xff0c;几乎感知不到周围环境…

CHARLS数据库系列教程(4)--多模型效应分析、Per SD、P for trend及限制立方样条图绘制

CHARLS 是一项具备中国大陆 45 岁及以上人群代表性的追踪调查&#xff0c;旨在建设一个高质量的公共微观数据库&#xff0c;采集的信息涵盖社会经济状况和健康状况等多维度的信息&#xff0c;以满足老龄科学研究的需要。 为利用国际上最佳的数据采集方式&#xff0c;并确保研究…

TiDB 性能测试的几个优化点

作者&#xff1a; 数据源的TiDB学习之路 原文来源&#xff1a; https://tidb.net/blog/513a4eef 背景 前段时间参与了一个 TiDB 的性能测试&#xff0c;具体是在三台海光服务器&#xff08;512G内存、128 core 分8个NUMA、4块3.5T SSD&#xff09;搭建一个混合部署的 TiDB …

Arthas tt(方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测)

文章目录 二、命令列表2.3 monitor/watch/trace/stack/tt 相关2.3.4 tt&#xff08;方法执行数据的时空隧道&#xff0c;记录下指定方法每次调用的入参和返回信息&#xff0c;并能对这些不同的时间下调用进行观测&#xff09;举例1&#xff1a;记录调用举例2&#xff1a;显示所…

系统集成项目管理工程师难度如何?

软考中级职称系统集成项目管理工程师是中级各专业资格中考试难度较小的&#xff0c;考试相对比较容易&#xff0c;通过率一般在20%左右&#xff0c;适合零基础考生或基础薄弱考生报考。但是考生还是需要认真进行备考&#xff0c;系统集成项目管理工程师综合知识考察的范围比较广…

【AI学习】DDPM 无条件去噪扩散概率模型实现(pytorch)

这里主要使用pytorch实现基本的无条件去噪扩散模型&#xff0c;理论上面的推导这里不重点介绍。 原文理论参考&#xff1a; 前向和反向过程示意图 前向过程和后向过程 扩散过程包括正向过程和反向过程。前向过程是基于噪声调度的预定马尔可夫链。噪声表是一组方差 &#xff0…

物理学基础精解【40】

文章目录 矢量积矢量积&#xff08;又称叉积、外积&#xff09;的几何意义一、面积表示二、垂直性三、方向性四、应用实例五、数学表达 矢量积&#xff08;叉积&#xff09;的坐标表示法矢量积的坐标表示法的几何意义矢量积的性质矢量积的应用 矢量积&#xff08;又称叉积、外积…

OptiTrack与Xsens光、惯动捕中用于动画制作的尖端设备对比

随着动画、电影、游戏等数字内容行业的迅速发展&#xff0c;捕捉演员的动作并将其转化为虚拟角色的技术越来越受到重视。两种主要的动作捕捉技术——光学捕捉系统和惯性动作捕捉系统——代表了当前市场的最前沿。本文将对比两种技术的代表性设备&#xff1a;OptiTrack的光学动作…

服务器数据恢复—raid磁盘故障导致数据库文件损坏的数据恢复案例

服务器存储数据恢复环境&故障&#xff1a; 存储中有一组由3块SAS硬盘组建的raid。上层win server操作系统层面划分了3个分区&#xff0c;数据库存放在D分区&#xff0c;备份存放在E分区。 RAID中一块硬盘的指示灯亮红色&#xff0c;D分区无法识别&#xff1b;E分区可识别&a…

使用PHP获取商品描述API:解锁电商数据的金钥匙

在电子商务领域&#xff0c;获取商品的详细信息对于商家和消费者来说至关重要。taobao作为中国最大的在线购物平台之一&#xff0c;提供了丰富的API接口供开发者使用。其中&#xff0c;商品描述API允许开发者获取商品的详细描述&#xff0c;这对于提升用户体验和优化商品页面至…

How FAR ARE WE FROM AGI?(ICLR AGI Workshop 2024)概览

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 How FAR ARE WE FROM AGI?官网 How FAR ARE WE FROM AGI?&#xff08;ICLR AGI Workshop 2024&#xff09; 该研讨会将于2024年5月11日在奥地利维也纳以混合模式举行&#xff0c;作为 ICLR 2024年会议的一部…

2024平价电容笔推荐!精选五大靠谱电容笔测评盘点!

现在电子设备已经成为我们生活、学习和工作中不可或缺的重要工具。而电容笔作为与电子设备紧密配合的配件&#xff0c;其重要性也日益凸显&#xff0c;为我们的数字操作体验带来极大的便利和提升。然而&#xff0c;市场上电容笔的品牌众多&#xff0c;价格、性能和品质参差不齐…

MES系统实现制造业生产自动化、智能化与透明化

万界星空科技MES系统通过集成硬件和软件&#xff0c;实现对生产过程的实时监控、数据采集、任务调度、资源分配、质量控制、文档管理等功能&#xff0c;旨在优化企业的生产流程&#xff0c;提高生产效率&#xff0c;降低成本&#xff0c;并确保产品质量。涵盖了离散制造、流程制…

诚实的人力资源招聘人员告诉你,为什么大多数求职者无法获得工作机会

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Arthas watch (方法执行数据观测)

文章目录 二、命令列表2.3 monitor/watch/trace/stack/tt 相关2.3.5 watch &#xff08;方法执行数据观测&#xff09;举例1:监控方法举例2&#xff1a;同时观察函数调用前和函数返回后 二、命令列表 2.3 monitor/watch/trace/stack/tt 相关 2.3.5 watch &#xff08;方法执行…

[论文精读]Membership Inference Attacks Against Machine Learning Models

中文译名&#xff1a;针对机器学习模型的成员推理攻击 会议名称&#xff1a;2017 IEEE Symposium on Security and Privacy (SP) 发布链接&#xff1a;Membership Inference Attacks Against Machine Learning Models | IEEE Conference Publication | IEEE Xplore CODE:Git…

34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题

在前面几篇文章中&#xff0c;我们讲了一些跟重构相关的理论知识&#xff0c;比如&#xff1a;持续重构、单元测试、代码的可测试性、解耦、编码规范。用一句话总结一下&#xff0c;重构就是发现代码质量问题&#xff0c;并且对其进行优化的过程。 前面的内容相对还是偏理论。…

中腾国际团餐产业集团经验谈:如何让上海央厨配送更高效、更安心

上海作为国际大都市&#xff0c;近些年对餐饮行业的效率与品质提出了更高要求。中央厨房(央厨)以其规模化、标准化生产的优势&#xff0c;成为提升餐饮供应链效率的关键一环。而央厨配送&#xff0c;作为连接央厨与消费者的重要桥梁&#xff0c;其重要性不言而喻。中腾国际团餐…