SpringMVC执行流程

2023-05-16

https://www.bilibili.com/video/BV1vs4y1S7cZ?p=2&vd_source=dfa1c0cf0e415bd95bc2263617b13437

Spring MVC源码解析

1 Spring MVC基于XML配置开发

需要开发者手动去定义配置文件,处理器的配置、实现特定接口、视图的配置、包扫描等的配置.…都是基于XML
文件来完成。
Spring MVC核心组件。SpringMVC执行流程:

在这里插入图片描述
首先 左边客户端
步骤1 客户端发送请求 请求后台web容器也就是Tomcat 当然咱们在这个Tomcat中由 1. DispatcherServlet表示前端控制器,是整个SpringMVC的控制中心,用户发出请求,DispatcherServlet接收请求并拦截请求。它是springmvc最为核心的组件 起到中央控制的作用
http://localhost:8080/springmvc-02-HelloSpringMVC/hello
以上的URL拆分成三部分:
http://localhost:8080 : 服务器域名
springmvc-02-HelloSpringMVC :部署在服务器上的Web站点
hello : 表示控制器
以上的URL解释:请求位于http://localhost:8080上的springmvc-02-HelloSpringMVC站点的hello资源(可以是servlet, 页面)
步骤2 具体如何控制,其他组件就开始起作用了 第二个组件叫HandlerMapping:处理器映射器。具体作用是处理映射 谁去处理我们的cotrol去处理例如咱们都在control包下放了userControl,员工Control 这都叫控制器 映射器 映射一个请求的路径 前端过来的请求他其实里面包含一个具体的圆也就是url 那么url到底要更哪个控制器control进行映射
其实handler mapping 就做这样的一件事 第二步根据请求找到处理器映射器
步骤3 这步找完之后 处理器映射器就有返回了 他返回了一组叫做处理器映射器的映射啊 其实就是一个control和一个url的映射
步骤4 这组映射给到前端控制器 前端控制器拿到这一组映射之后 继续往后走 找到 HandlerAdapter 我们称之为处理器适配器
步骤5 找到处理器适配器 他在这而里面就有具体的去根据这个请求找到具体的处理者 寻找处理器就是寻找谁可以去做这样的一个请求 也就是此时找到Handle
步骤6 Handle他就知道他要处理的是一个control 他去找control 这个control注意是我们手写下的control包下的东西userControl control往后走就找到我们的业务层了service service往后走他就找到具体所处理的这dao层 数据库
步骤9 ,10 返回数据 给control
步骤11 control 把数据返回给handler
步骤12 这个数据handler拿到之后 交回来 交给处理器适配器
步骤13 处理器适配器继续把数据再返回数据给dispatcherservlet 前端控制器就持有了这个真正的数据了 前端控制器他要去 接下来告诉这个数据由谁来解析和呈现 他前端控制器肯定不是自己解析 他接下来要找一个组件叫视图解析器ViewResolver
步骤14 前端控制器将数据交给了视图解析器 那么他再把这个数据(他是放在了一个将数据绑定在这个model and view中)交给视图解析器的时候
步骤15 视图解析器拿到这个数据之后继续返回 他返回一个view对象 这个view对象其实是一个视图的路径 比如你是一个jsp的文件路径 此时前端控制器 他知道你最后返回的是要把这个数据交给一个view对应的路径下的一个文件进行渲染
步骤16 视图的渲染
步骤17 视图渲染之后 这个当然要有前端控制器统一的去相应给前端 最后一步就是响应
图中重点 5个重要的组件对象
还有2 个对象 ModelAndView对象(绑定数据的)和 View对象(视图对象)

疑问解答
问:springmvc中 url请求过来 是因为他dispatcharservlet中央控制处理之后交给了handlemapping 这样理解的对吗?
gpt是的,Spring MVC中的请求处理流程是由DispatcherServlet作为中央控制器,根据请求的URL分发给相应的HandlerMapping进行处理。HandlerMapping会根据请求的URL匹配到对应的Controller,然后将请求转发给Controller进行处理。在Controller中,可以通过调用Service层或者其他组件来完成业务逻辑处理,最终返回ModelAndView对象给DispatcherServlet进行视图渲染。
问:我想知道的是 springmvc中 各个组件比如handlerAdapter和DispachterServlet和viewResover之间的数据链路一直是联通的。所以我们发送数据自动这样传送处理对吗,也就是他们不需要建立连接。直接就传送数据了
是的,Spring MVC中的各个组件之间的数据链路是通过DispatcherServlet来实现的,它们之间不需要建立连接,而是通过Servlet容器提供的请求响应机制来传递数据。具体来说,当客户端发送请求时,Servlet容器会将请求封装成HttpServletRequest对象,并将其传递给DispatcherServlet。DispatcherServlet会根据请求的URL选择合适的HandlerMapping和HandlerAdapter来处理请求,并将处理结果封装成ModelAndView对象。最后,DispatcherServlet会将ModelAndView对象传递给ViewResolver来选择合适的视图进行渲染,并将渲染结果封装成HttpResponse对象返回给客户端。
在整个请求处理过程中,各个组件之间的数据链路是始终保持联通的,因为它们都是在同一个请求上下文中运行的。这种方式可以有效地降低系统的耦合度,提高代码的可维护性和可扩展性。

2 Spring MVC基于XML配置的实现

1.创建一个项目:springmvc_code。创建成划ava企业级的项目。

JsP本质上就是Servlet(组件)。

  • 如果使用原生的Servlet响应网页给客户端,需要使用PrintWriter的write(“xxx”)或者是printIn(“xxx”)。
  • 将TML代码抽离出来,定义在其他的文:中,使用动态拼接渲染,最终响应给客户端。
  • 在SP文件中可以直接定义网页的代码结构。可以通过EL表达式、」STL标签库来完成页面的动态数据加载。
  • Tomcati在编译sP文件(Hello.jsp)的时候,将jsp转化成-个Servlet组件(Hello_jsp.java-Java源文件)。最后经过VM虚拟机将Servlet组件转化成一个字节码文件(Hello_jsp.class)。

2.创建了一个Hello.jsp文件。

 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>SpringMvc基于XML文件的配置开发</title>
</head>
<body>
    <h1>SpringMVC基于XML文件的配置开发</h1>
    <p>数据展示:${msg}</p>
</body>
</html>

3.编写Web.xml核心配置文件:配置DispacherServlet、配置编码设置、springmvc-servlet,Xml文件。

<!--  1.注册Servelt-->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
<!--  2.配置映射-->
  <servlet-mapping>
    <!--上下定义的名称需要保持一致-->
    <servlet-name>springmvc</servlet-name>
    <!-- 表示请求的url映射:"/"表示能够匹配浏览器提交过来的所有请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

4.Servlet原理分析

在这里插入图片描述
在这里插入图片描述

客户端 首先前端有一个按钮也好点击 发送一个请求 浏览器内部有通信组件 服务器Tomcat端也有一个通信组件
步骤1 请求到达通信组件 通信组件把这个数据进行一个打包
步骤2 完成请求数据的打包
步骤3 发送请求数据包 给指定ip这台服务器 连接早已建立好之后 直接发送数据就行了
步骤4 解析数据包 相当于字节码文件
步骤5 java程序处理之前做了前置动作 创建了两个对象 request对象 response对象 每个请求都会创建这两个对象 同时在创建这两个对象的时候会把请求过来的数据包解析的内容啊放到这个requesty对象里所以此时request!里它其实包含有咱们的请求数据呢
在这里插入图片描述
步骤6 创建Servlet
步骤7 根据请求类型调用方法
步骤8 将步骤5的 请求对象和响应对象传递给咱们的service方法 这里也可以是doget 或 dopost方法 然后这个方法就开始运行了 通常在这个方法当中做两件事
(1)读取请求对象request中的数据
(2)响应数据给客户端 通过response对象来响应
步骤9 响应数据给通信组件
步骤10 通信组件将数据进行打包成响应数据包
步骤11 这个数据包才可以在互联网上通过http协议进行传输
步骤12 请求数据包到达客户端 客户端也不能直接读取,通信组件对响应数据包进行解析
步骤13 接续完成之后这个数据包才能被浏览器的nodejs或浏览器引擎才好进行渲染和呈现 然后客户端呢看到一个页面了
这个Servlet原理依赖的是http协议

5.创建一个HelloController类

/**
 * 自定义控制器类 SpringMVC框架提供的一个接口Controller
 */
//@Controller 现在我们不用注解方式开发 ,采用实现接口的方式
public class HelloController implements Controller { //URL访问地址基于XML配置的方式进行开发
    /*
     *该方法什么时候被调用?
     * 1.请求的urL找了到了对象的Controller类之后,此Controller类中的handleRequest()方法会被自动回调
     * 2.ModelAndView:模型和视图。
     *  -模型:表示数据,可以在ModelAndViews对象上绑定数据。
     *  ·视图:表示一个数据展示的页面 例如是一个JSP页面
     */
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        /**
         * 需求:配置的请求地址是"/hello",一旦访问该请求,服务器打开Helo.jsp文件,然后该jsp文件输出"Hell0 SpringMVC"信息
         */
        //1.创建一个模型和视图对象
        ModelAndView modelAndView = new ModelAndView();
        //2.在modeLAndView对象上绑定数据(key-valve键值对)
        modelAndView.addObject("msg","Hello Spring MVC");
        /**
         * 3,设置视图:表示将来跳转到那一个指定的视图页面(HLLo,jsp页面)
         *  - 前缀 /WEB-INF/
         *  - 后缀 .jsp
         *  这里前缀和后缀不是在这配的。所以没必要/web-inf/hello.jsp
         */
        modelAndView.setViewName("hello"); //表示访问的是/WEB-INF/hello.jsp页面
        // 4,将模型和视图对象返回给调用者
        return modelAndView;
    }
}

ModelAndView中的addObject方法 数据将保存在request域对象中。
然后我们就可以在hello.jsp通过el表达式${}符号将数据取出来

6.Controller接口分析

@FunctionalInterface //函数式接口
public interface Controller {
	//用来处理请求和响应(request和response.对象会自动的传递给此方法)Tomcat内部组件帮我们完成的 前端有数据request里面就有
	//该方法什么时候被调用?
	//请求的Url找了到了对象的Controller类之后,此Controller类中的handleRequest()方法会被自动回调
	@Nullable
	ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

7.默认情况下,SpringMVC框架自动去WEB-lNF/springmvc-servlet.xml此配置文件。

在这里插入图片描述

前端传递过来一个地址/hello springmvc怎么知道找HelloController这个类 此HandlerMapping会完成这样的一个映射 HandlerMapping起到中间寻找的过程 HandlerMapping拿着/hello 看你框架容器当中谁的name取值更url一样 发现这个bean的name是一样的 所以匹配那个HelloController那个类
在这里插入图片描述
springmvc-servlet.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1,注册处理器映射器:HandlerMapping
        作用:处理器映射器会根据请求的UrL地址,将urL和Spring容器中的一个bean的name属性值进行匹配
       简单来说就是将一个请求的url和一个Controller进行绑定
       说明:bean标签没有显示的什么id属性的取值,Spring容器会自动的为其分配一个id的值(自定生成),
id生成规则是:包名,类名#数字。例如:id=com,qf.springmvc_.code.controller.HelloController#l
        还有不用id去映射而使用name的原因 id基于注解式的写法 同时id的取值是不可以使用斜杠开头的 原生写法语法对不上
        -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">

    </bean>
<!--    2.注册处理器(HelloController类)
         name属性:表示对应一个前端的请求,name属性的取值必须使用"/"开头
         class属性:表示对应请求从DispatcherServlet发送过来的请求,对应的的后台处理器
         你这个配置 springmvc不认识,所以需要配置上面的handleMapping
         -->
    <bean name="/hello" class="com.qf.springmvc_code.controller.HelloController">

    </bean>
<!--    3.配置处理器适配器:HandlerAdapter
        作用:根据HandlerMapping返回的Controller,执行一个匹配的规则,主要是找到对应的Handler:去处理Controller
        controller不会自动执行  Controller执行必须依赖handler执行 controller自己不会自动执行 处理器才能执行Controller
        也就是说Adapter他去找到一个能够处理和运行该Controller的handle处理器 去执行Controller
      -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!--    4,配置视图解析器:ViewResolver-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--    4.1配置资源文件的路径前缀
        作用:根据路径的前缀和后缀找到对应的视图文件,然后进行渲染-->
        <property name="prefix" value="/WEB-INF/"></property><!--表示给一个属性配置初始化取值的数据-->
        <!--    4,2配置资源文件的后缀(文件扩展名称)-->
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

在这里插入图片描述
在这里插入图片描述
第6步 return modelAndView view对象就包含视图解析器 然后把视图渲染出来

疑问解答

:我想问的是tomcat是如何开始,读取这些web-inf目录下的文件的。还是浏览器输入url时候才开始读取这些文件?
new bing:当您启动Tomcat时,它会读取并解析web.xml文件。web.xml文件定义了servlet和servlet映射,以及其他应用程序配置信息。在您的web.xml文件中,定义了一个名为springmvc的servlet,该servlet使用org.springframework.web.servlet.DispatcherServlet类。此servlet具有一个初始化参数,用于指定Spring MVC配置文件的位置,即WEB-INF/springmvc-servlet.xml。因此,在启动Tomcat时,它会读取并解析这些文件。
当您在浏览器中输入URL时,Tomcat会根据web.xml中定义的servlet映射将请求映射到相应的servlet进行处理。在您的web.xml文件中,定义了一个servlet映射,将springmvc servlet映射到URL模式/。因此,当您在浏览器中输入URL时,Tomcat会将请求映射到springmvc servlet进行处理。

:也就是说tomcat运行起来,自动读取web-inf目录下的web.xml,不需要我们写任何配置类或启动类?
new bing:是的,当您启动Tomcat时,它会自动读取并解析web.xml文件。web.xml文件是Java Web应用程序的部署描述符,它定义了应用程序的配置信息,包括servlet和servlet映射。您不需要编写任何配置类或启动类来读取web.xml文件。

:这么理解吗 启动时候已经读取了web.xml,通过url访问请求时。交给springmvc处理,所以读取了webapp/web-INF/springmvc-servlet.xml文件,我这样理解没错吧?
new bing:您的理解是正确的。当您启动Tomcat时,它会读取并解析web.xml文件。在您的web.xml文件中,定义了一个名为springmvc的servlet,该servlet使用org.springframework.web.servlet.DispatcherServlet类。此servlet具有一个初始化参数,用于指定Spring MVC配置文件的位置,即WEB-INF/springmvc-servlet.xml。因此,在启动Tomcat时,它会读取并解析这些文件。
当您在浏览器中输入URL时,Tomcat会根据web.xml中定义的servlet映射将请求映射到相应的servlet进行处理。在您的web.xml文件中,定义了一个servlet映射,将springmvc servlet映射到URL模式/。因此,当您在浏览器中输入URL时,Tomcat会将请求映射到springmvc servlet进行处理。

:这个web-INF目录下的为什么访问不了?
new bing:WEB-INF目录下的文件只能由服务器级别才能访问,客户端并不能访问。这是因为WEB-INF目录是被保护起来的,其下的jsp页面不能直接运行,只能通过控制器跳转来访问;静态资源(js、css、img)也不能被WEB-INF目录外的其他文件直接引用12. 如果您想要访问WEB-INF目录下的文件,您需要在控制器中进行跳转.

:转发的话,能否使用重定向访问web-inf目录下的文件?
new bing:不能。重定向是让客户端去访问重定向的地址,而WEB-INF下的文件是不能通过外部访问的123. 只能用转发方式来访问WEB-INF目录下的JSP,不用采用重定向的方式请求该目录里面的任何资源

我:springmvc那些组件是默认自动立即加载 例如DispatcherServlet类HandlerMapping 然后我们在xml里面去配置他们就行了是这么理解的嘛?
new bing:SpringMVC中的DispatcherServlet、HandlerMapping、HandlerAdapter、ViewResolver等组件是默认自动立即加载的,无需在xml中配置

3 SpringMVC源码解析

3.1前端控制器DispatcherServlet

请求入口和中央控制都是这个类DispatcherServlet

1.继承关系:

DispatcherServlet继承-FrameworkServlet继承-HttpServletBean继承-HttpServlet继承-GenericServlet:实现-Servlet接口。
在这里插入图片描述

2.DispatcherServlet前端控制器:

任何servlet 自动被触发
servlet知道 对象创建由Tomcat自动完成 servlet对象加载的方式有两种
第一种:懒加载
不访问这个servlet 这个servlet所对应的对象永远不会被创建
第二种:立即加载
服务器启动 servlet对象立即会被加载 业务层控制的servlet一般都控制成懒加载 不用他就不需要创建它
一个servlet抽离出来是为其他servlet服务的 这种servlet通常是配置servlet 比如这个DispatcherServlet
在Spring MVC中,Servlet是通过DispatcherServlet来加载和管理的。DispatcherServlet在Web应用程序启动时就会被容器加载,并初始化相关的Bean。这种方式被称为立即加载。

但是,为了优化Web应用程序的性能,Spring MVC也支持懒加载。懒加载是指在需要使用Bean时才进行初始化,而不是在应用程序启动时就全部初始化。这种方式可以减少应用程序的启动时间和内存占用。
在Spring MVC中,我们可以通过在Bean的定义中设置lazy-init属性来实现懒加载。例如:

<bean id="userService" class="com.example.UserService" lazy-init="true">
  <property name="userDao" ref="userDao" />
</bean>

在上面的配置中,我们将userService设置为懒加载,这意味着在应用程序启动时不会立即初始化UserService的实例。只有在需要使用userService时,才会进行初始化。 需要注意的是,懒加载可能会导致应用程序在使用Bean时出现延迟,因此需要根据实际情况进行选择。通常情况下,非常重要或频繁使用的Bean应该使用立即加载,而不太重要或不经常使用的Bean可以使用懒加载。

//servlet接口中提供的一个方法,根据不同的请求类型来进行请求的处理
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
//调用了doDispatch()方法,传递了请求和响应对象
		this.doDispatch(request, response);
	}

前端所有请求都进入doDispatch这个方法里面
DispatcherServlet这个是doDispathc类的

3.doDispatch方法定义

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		//创建一个处理器执行器链(将来执行请求的流程)
		//将来要找到哪个controller和controller地址
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				//通过getHandler(request)返回一个处理器执行链对象
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = HttpMethod.GET.matches(method);
				if (isGet || HttpMethod.HEAD.matches(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

				applyDefaultViewName(processedRequest, mv);
				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);
			}
			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);
				}
			}
		}
	}

4.查看getHandler(processedRequest)的源码

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

4,查看getHandler(processedRequest)的源码。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) { //维护url和Controller的映射
		// HandlerMapping是Servlet所支持的处理器映射器的一个集合 这里可能会有N个处理器映射器
		//举例
		// HandlerMapping 相当于/hello-HelloController对象
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request); //这里我们继续升入
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

5.查看getHandler(request)方法的源码。

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取handler对象 我们去研究下他
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// Ensure presence of cached lookupPath for interceptors and others
		if (!ServletRequestPathUtils.hasCachedPath(request)) {
			initLookupPath(request);
		}
//获取处理器执行链。根据request请求对象和一个处理器Handler对象来获取处理器执行链
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); // 研究下他 

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = getCorsConfiguration(handler, request);
			if (getCorsConfigurationSource() != null) {
				CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
				config = (globalConfig != null ? globalConfig.combine(config) : config);
			}
			if (config != null) {
				config.validateAllowCredentials();
			}
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

6.查看getHandlerInternal(request)方法。

//获取一个Controller
@Nullable
	protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

7.查看getHandlerExecutionChain(handler,requst)方法。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//获取一个处理器执行链对象
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				//request:对象中就有目标的url地址,例如是/hello
				//HandleMapping获取的映射,url地址
				if (mappedInterceptor.matches(request)) {
				//这里可以看出具有多个拦截器,而且拦截器根据请求做匹配
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		//处理器执行链:包含两部分内容。interceptor和handler
		return chain;
	}

DispathcherServleti前端控制器,经过一系列的处理返回一个处理器执行链对象(HandlerExecutionChain)。在该对象封装了两部信息,分别是Interceptor拦截器和一个Handler处理器。

8.配置初始化参数信息。

 <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--    给当前的Servlet配置初始化参数:配置的是springmvc框架的核心配置文件的位置-->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>WEB-INF/springmvc-servlet.xml</param-value>
    </init-param>
  </servlet>

9.XmlWebApplicationContext类维护了SpringMVC的核心配置文件的加载。

/**
 * The default location for the root context is "/WEB-INF/applicationContext.xml",
 * and "/WEB-INF/test-servlet.xml" for a context with the namespace "test-servlet"
 * (like for a DispatcherServlet instance with the servlet-name "test").
 * 项目的根目录的上下文位置"/WEB-NF/applicat1 onContext.xml"找核心配置文件。或者通过"/WEB-INF/test-servlet,ml"路径来找配置文件,规则是:通过DispacherServlet的name属性的取值,来进行获取文件的,在name属性的取值后自动添加"-servlet"作为被寻找的配置文件名称。
 */
@Override
	protected String[] getDefaultConfigLocations() {
		if (getNamespace() != null) {
			return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
		}
		else {
			return new String[] {DEFAULT_CONFIG_LOCATION};
		}
	}

疑难解答
这两个文件是Spring MVC应用程序的配置文件,它们之间有如下关系:

  • web.xml文件是Web应用程序的部署描述文件,用于定义Servlet、Filter、Listener等组件的声明和映射。在这个文件中,我们声明了一个名为springmvc的Servlet,并将其映射到了所有的请求路径上。同时,我们还指定了springmvc-servlet.xml文件的位置,以便让Spring MVC的前端控制器能够加载和解析配置文件。

  • springmvc-servlet.xml文件是Spring MVC的配置文件,用于定义处理器映射、控制器、适配器和视图解析器等组件。在这个文件中,我们定义了一个处理器映射器、一个控制器、一个适配器和一个视图解析器,以便让Spring MVC能够正确地处理请求和渲染视图。

因此,这两个文件是Spring MVC应用程序的必要配置文件,它们协同工作才能让应用程序正确地运行。web.xml文件负责整个应用程序的部署和配置,而springmvc-servlet.xml文件则负责Spring MVC的配置和管理。

3.2处理器映射器HandlerMapping

HandlerMapping接口负责将request对象和请求找到的Handler对象及interceptor对象,封装到处理器执行
链(HandlerExcecationChain)中,返回给前端控制器(DispacherSerlvet)。
HandlerMapping接口提供了两个实现类:

  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping

1.BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping:处理器映射器会根据请求的url与Spring中定义Controller的name属性的取值进行匹配(Spring容器中定义的Bean类的name属性进行匹配)。如果匹配成功,则返回一个处理器对象(Controller对象)

1.xml文件中的配置。
<!--    1,注册处理器映射器-->
 <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
 <!--    2.注册处理器(HelloController类)  -->
 <bean name="/hello" class="com.qf.springmvc_code.controller.HelloController">
2.BeanNameUrlHandlerMapping类的源码解析。
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
// 检查给定的bean的name属性的取值是否以“/"开头,如果是,则添加到String[]数组中了	 
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>(); // List集合是用来存储url的(name属性的值的集合)
		if (beanName.startsWith("/")) { // 判断name属性的取值是否以"/"开头
			urls.add(beanName);// 将这个name的取值添加到List集合中
		}
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias); // 如果bean别名以“/"开头,则将别名添加到List集合中
			}
		}
		// List<String>转化成String[]
		return StringUtils.toStringArray(urls);
	}

}
3.需求:

。访问hello,可以打开HelloController。
。访问world,可以打开HelloController。

  <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>

    <bean name="/hello" class="com.qf.springmvc_code.controller.HelloController"></bean>
    <bean name="/word" class="com.qf.springmvc_code.controller.HelloController"></bean>
4 .BeanNameUrlHandlerMapping缺点:
  • 注册处理器的时候,如果有多个url映射同一个Controller,.代码极为冗余。
  • Controller处理器url要求不能重复,而bean标签name属性的值是可以重复。如果就显得不伦不类。

2.SimpleUrlHandlerMapping

- <!--    另一种方式 1.注册处理器映射器:SimpleUrlHandlerMapping-->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<!--        方式2:urlMap的属性来实现请求地址的映射-->
        <property name="urlMap">
            <map>
<!--                entry表示表示一组UrL与Controller的映射。key表示请求的UrL地址,value:表示Controller的bean标签对应的id属性的值-->
                <entry key="/hello" value="helloController"/>
                <entry key="/word" value="helloController"/>
            </map>
        </property>
       <!-- 方式1:通过mappings属性完成的
       mappings属性:通过mappings属性来为当前的处理器同时配置多个urL地址的请求映射
        <property name="mappings">
            <props>
&lt;!&ndash;                prop表示声明一组UrL的映射,其中key表示配置请求地址,prop标签的内容是Controller的bean的id取值&ndash;&gt;
                <prop key="/hello">helloController</prop>
                <prop key="/word">helloController</prop>
            </props>
        </property>-->
    </bean>
<!--    2.注册处理器:HelLoController-->
    <bean id="helloController" class="com.qf.springmvc_code.controller.HelloController"></bean>

3.HandlerMapping的源码解析

1.DispacherServlet类中doService(request,response)。

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);
		....
	this.doDispatch(request, response);
	}

2.doDispatch(request,response)方法。需要接受一个requesti请求对象。

// List集合存放的是所有的HandlerMapping对象
@Nullable
	private List<HandlerMapping> handlerMappings;

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		mappedHandler = this.getHandler(processedRequest);
}		

3.getHandler(processedRequest)方法。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
		//遍历HandlerMapping的集合
			for (HandlerMapping mapping : this.handlerMappings) {
			//获取一个HandlerMapping类型的对象
			//通过request请求来获取HandlerMapping来获取处理器执行链
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

4.getHandler(request)方法。是在AbstractHandlerMapping中实现了该方法

@Nullable
	public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	// 根据请求对象获取一个处理器 
		Object handler = getHandlerInternal(request);
		if (handler == null) {
			handler = getDefaultHandler();
		}
		if (handler == null) {
			return null;
		}
		// Bean name or resolved handler?
		if (handler instanceof String) {
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}

		// Ensure presence of cached lookupPath for interceptors and others
		if (!ServletRequestPathUtils.hasCachedPath(request)) {
			initLookupPath(request);
		}
	//根据request和response对象来获取一个处理器执行链(HandlerExecutionChain)
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}

		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = getCorsConfiguration(handler, request);
			if (getCorsConfigurationSource() != null) {
				CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
				config = (globalConfig != null ? globalConfig.combine(config) : config);
			}
			if (config != null) {
				config.validateAllowCredentials();
			}
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}

5.getHandlerExecutionChain()方法。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//实例化一个HandlerExecutionchain对象
		HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
				(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

		for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
			if (interceptor instanceof MappedInterceptor) {
			//创建一个处理器过滤器对象 
			//转化为MappeInterceptor过滤器
				MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
				if (mappedInterceptor.matches(request)) { // 判断请求对象中的ur1和配置是否相同
					//把这个过滤器添加到HandlerExecutionchain对象中
					chain.addInterceptor(mappedInterceptor.getInterceptor());
				}
			}
			else {
				chain.addInterceptor(interceptor);
			}
		}
		// 返回一个HandlerExecutionChain对象:handler、interceptor(request)
		return chain;
	}

HandlerMapping处理器映射器,通过源码分析返回一个HandlerExecutionChain的对象(包含了url和Controller的映射)。
这是之前的
DispathcherServleti前端控制器,经过一系列的处理返回一个处理器执行链对象(HandlerExecutionChain)。在该对象封装了两部信息,分别是Interceptor拦截器和一个Handler处理器。

3.3处理器适配器HandlerAdapater

1.HandlerAdapateri源码分析

1,找到DispacherSerlvet类中doService方法。

 doDispatch(request, response);

2.查看doDispatch(request,response)方法。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

}

3.getHandlerAdapter(mappedHandler.getHandler(())方法。

//方法通过Handler对象来返回一个HandlerAdapter对象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {// List<HandlerAdapter>:集合中
			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");
	}

4.supports(handler)方法
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

//传递的Handler对象是HelloController
//HelloController类的定义public class HelloController implements Control1er
	@Override
	public boolean supports(Object handler) {
	//通过传递过来的Handler对象,判断该对象是否是Controller类型
		return (handler instanceof Controller);
	}
/**
* 参数1:表示一个request请求对象
* 参数2:表示一个response响应对象
* 参数3: 表示一个Controller对象(HelloController)
*/
	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
// 多态:向上转型,调用handleRequest(request,response )方法完成请求处理业务逻辑
		return ((Controller) handler).handleRequest(request, response);
	}
//与Httpserlvet的getLastModified方法的约定时间。
	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

}

2.HandlerAdapter的执行

DispacherServlet:会根据HandlerMappingt传递回来的Controller注册到以配置的HandlerAdapter.上。HandlerAdapater来判断Controller的数据类型是否满足满足要求(Controller),如果类型满足要求,则将该Controller进行类型转化(Controller接口类型)。调用目标Controller的Handle Request(request,response)方法,来完成目标业务逻辑方法的调用。

3.4视图解析器ViewResolver

1.ViewResolver源码分析

1.InternalResourceViewResolver类的定义。

public class InternalResourceViewResolver extends UrlBasedViewResolve{
//通过JSTL标签库来解析目标JSP文件
	private static final boolean jstlPresent = ClassUtils.isPresent(
			//加载JSTL的标签标签库
			"javax.servlet.jsp.jstl.core.Config", 
			//获取当前类的类加载器
			InternalResourceViewResolver.class.getClassLoader());
	//...
	}

}

2.UrlBasedViewResolver类

public class UrlBasedViewResolver extends AbstractCachingViewResolver implements Ordered {
	/**
	 * Prefix for special view names that specify a redirect URL (usually
	 * to a controller after a form has been submitted and processed).
	 * Such view names will not be resolved in the configured default
	 * way but rather be treated as special shortcut.
	 */
	public static final String REDIRECT_URL_PREFIX = "redirect:"; //重定向
	/**
	 * Prefix for special view names that specify a forward URL (usually
	 * to a controller after a form has been submitted and processed).
	 * Such view names will not be resolved in the configured default
	 * way but rather be treated as special shortcut.
	 */
	public static final String FORWARD_URL_PREFIX = "forward:";//转发
	@Nullable
	private Class<?> viewClass;
	private String prefix = "";//目标视图的前缀(在那一个目录下存放的)
	private String suffix = "";//目标视图的后缀(.jsp、asp)
}

2.ViewResolver流程

1.将SpringMVC框架的前端控制器中的返回结果封装成一个ModelAndView对象。
2.通过视图解析器,将返的ModelAndView对象进行解析,解析成一个物理地址的映射视图。
物理地址 :目标的资源文件在对应哪台计算机上 哪个目录结构下 以及这个文件名 这叫物理存储的路径
3.将这个物理地址的视图通过View接口来进行渲染。

4 SpringMVC执行流程

HandlerMapping 返回一个处理器执行器链
HandlerAdapter 把你这个请求找到目标要运行的Controller
然后目标Controller在运行的时候转发的时候设计渲染一个视图 渲染视图我们就需要配置视图解析器
ViewResolver就是把返回的url地址解析成物理地址,然后通过view对象进行渲染 这个过程还会涉及modelandview对象 该对象会绑定数据以及封装一个视图的地址 这个就是springmvc执行流程
在这里插入图片描述
步骤1 :客户端发出请求到达web容器(DisPatcherServlet前端控制器)前端控制器知道是个url
步骤2:他将url给HandlerMapping 处理器映射器处理url 因为我们在springmvc-servlet.xml springmvc核心配置文件中配置url地址和Contronller地址 HandlerMapping做的就是url和对应Controller类的绑定 进行一一映射
步骤3:绑定的映射规则传递给前端控制器(以处理器执行链的方式传递过来)返回给dispatcherservlet
步骤4: 处理器想要处理controller(此时其实是handler对象) dispatcherservlet有对应的判断规则 所以传入handler 给handlerAdapter
步骤5:HandlerAdapter拿到handler对象 for循环主要是判断是不是handler对象是否能变成Controoller对象
步骤6:handler可以匹配目标的handlerAdapter 适配器,那么适配器,就会适配器就会让 hanler去运行这个handlerRequest方法,这个方法里面写着打开目标的jsp文件,以及向jsp文件传递了字符串数据过去了 执行完之后返回一个ModelAndView对象

步骤7-9 ModelAndView 里面包含目标的视图在哪儿就是jsp的地址在哪儿 还有目标视图有没有让他展示数据
步骤10 前端控制器不会渲染 它会将数据交给 ViewResolver
视图解析器返回一个view对象 返回view对象包含目标渲染地址 jsp文件路径 给dispatcherservlet 然后把路径交给Viewjsp 渲染完成之后
步骤11 渲染出来后整体数据的响应依靠servlet 把数据传递给前端 即响应回来

疑难解答
我:HandlerAdapter的作用?
new-bing:HandlerAdapter字面上的意思就是处理适配器,它的作用用一句话概括就是调用具体的方法对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理123. HandlerAdapter是Spring MVC中的一个接口,它定义了处理请求的方法,包括处理请求前、后以及异常情况下的处理方式。Spring MVC提供了多种HandlerAdapter实现类,如SimpleControllerHandlerAdapter,AnnotationMethodHandlerAdapter等
我:for (HandlerAdapter adapter this.handlerAdapters){ if (adapter.supports(handler)){ return adapter; }HandlerAdapter类源码的这段就是为了不同类型的请求处理器(这里理解为不同的Handler)对象)需要不同的HandlerAdapteri进行适配。。是这么理解的吗??
newbing:是的,您的理解是正确的。HandlerAdapter类源码中的这段代码是为了不同类型的请求处理器(这里理解为不同的Handler)对象需要不同的HandlerAdapter进行适配.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringMVC执行流程 的相关文章

随机推荐

  • Hibernate旅程(四)Hibernate对数据库删除、查找、更新操作

    上篇 xff0c 我们以向数据库添加操作来演示 hibernate 持久化对象的三种状态 本节继续 hibernate 对数据库的其他操作 xff0c 删除 查询 修改 Hibernate 对数据删除操作 删除 User 表中个一条数据 x
  • 二分查找算法(递归与非递归两种方式)

    首先说说二分查找法 二分查找法是对一组有序的数字中进行查找 xff0c 传递相应的数据 xff0c 进行比较查找到与原数据相同的数据 xff0c 查找到了返回对应的数组下标 xff0c 没有找到返回 1 xff1b 如下示例 xff0c 其
  • 调用微信高级群发接口--视频群发接口出问题(微信官方文档错误纠正)

    这几天在弄项目与微信对接 xff0c 我主要负责将素材 xff08 视频 xff0c 图片 xff0c 缩略图 xff0c 音频 xff09 材料上传到微信服务器上 xff0c 并推送到所关注本平台的用户中 xff0c 从获取accessT
  • (七)Intellij 中的git操作

    git原理以后会分章节介绍 xff0c 本次主要说一下intellij怎样操作git intellij有很好的git操作界面 xff0c 可以拉取代码 xff0c 拉取分支详情 xff0c 提交代码到本地仓库 xff0c 提交代码到远程仓库
  • git 远程代码回滚master

    人总是会有犯错的时候 xff0c 所以我们的代码有时候就需要回滚 当我们要回滚的代码还没有提交到远程的时候 xff0c 可以进行本地回滚 xff0c 较为简单 一 本地回滚 git reset 回退内容到上一个版本 就像现在的自己为成年人
  • Java相对路径、绝对路径的概述(自用)

    绝对路径 xff1a 绝对路径 xff1a 绝对路径是指文件在硬盘上真正存在的路径 例如 1 txt 这个文件是存放在硬盘的 C Users Desktop 目录下 xff0c 那么 1 txt 这个文档的绝对路径就是 C Users De
  • (2)mysql--查询部门人数最多的部门

    简述 xff1a 找出部门人数大于或等于10个人的部门 要点 在上一篇的基础上 xff0c 考察having的使用 上篇链接 xff1a http blog csdn net lovesummerforever article detail
  • ll -bash: ls: command not found

    问题简单描述 xff1a centos6安装软件的时候 不知道执行了啥操作 ll ls 命令都不好用了 问题所在 在centos6系统上安装jdk 配置 etc profile 只配置了jdk的环境变量 忘记加入系统的变量 1 网上查了查是
  • (3)mysql index

    生活中的索引 最常见的书籍是有目录的 xff0c 也可以叫做为索引 为啥用索引 xff1f 为快不破 xff0c 为了快速的查找到我们想要的东西 xff0c 书中的索引可以快速查询到我们想看的章节内容 DB中的索引亦是如此 索引本质 本质就
  • 博客乔迁

    开通了个人博客网站 欢迎访问 http www codingfuns com 博客地址http www codingfuns com 刚开通多有不足之处 请留言哈 非常感谢 最近更新的一些文章 xff1a 文章题目文章链接地址更新日期com
  • 好书好人生--读书的步骤

    写在开始 xff1a 作为一名IT人士 xff0c 我们要经常接触新的技术 xff0c 也要多读一些技术的书和生活的书 xff0c 怎样读书最重要 xff0c 读书并不是读得多就好 xff0c 读书就像吃牛肉一样 xff0c 我们要吃了吸收
  • 年终工作总结:给新手程序员的几个建议

    转自 xff1a 伯乐在线 马上到年末了 xff0c 自己也对今年做了一下总结 xff0c 自己有哪些优点 xff0c 有哪些还需要加强 每当我想到今年比较满意的地方 xff0c 对比之前 xff0c 就会有这样一个思考 xff1a 如果以
  • 解决Mac M1架构系统无法调试go的问题

    背景 xff1a 周末在新Mac 使用 Goland 断点调试 go程序 xff0c 发现无法调试报错 xff0c 最终发现问题是 xff1a xcode select 版本的问题 xff0c 更新完后完美解决 xff1b 最近买了台202
  • vscode配置文件之.eslintrc.json

    eslintrc json span class token punctuation span span class token string 34 env 34 span span class token operator span sp
  • ranger文件管理器使用

    简介 ranger是一个终端下的文件管理器 xff0c 剩下的就不知道了 从名字就可以看出来它会显示你的文件树 使用 命令行输入ranger进入文件管理器 xff0c 显示三列分别是 xff0c 上一级目录 xff0c 当前目录 xff0c
  • 新学期flag-适合每个人的专业课

    在果壳上看了各学科入门书籍推荐这个帖子 xff0c 决定自己好好扩展一下自己的眼界 xff0c 开启一个伟大的阅读计划 xff1a 1 师从天才 作者 美 罗伯特 卡尼格尔 出版社 上海科技教育出版社 评语 sunfield推荐 xff08
  • Android部署TFLite模型并启用GPU加速

    项目准备 训练并量化好的TFLite模型 model tflite需要使用TFLite的安卓工程开发用手机 部署流程 在Gradle中配置TFLite相关库 xff0c 在build gradle中补充依赖库 xff0c 具体如下 xff0
  • Android指纹识别,看这一篇就够了

    在Android6 0 xff08 Api23 xff09 的时候 xff0c Android系统加入了指纹识别的api接口 xff0c 即FingerprintManager xff0c 定义了最基础的指纹识别接口 不过 xff0c 在A
  • 展讯平台如何调试之打LOG--串口log

    公司最近做一款数据卡 xff0c 还是用的展讯的8800 xff0c 板子还没回来 xff0c 想先在8802上实验一下 但是在移植到8802上的时候板子一直都跑不起来 以前出现问题的时候第一反映就是去打LOG xff0c 查异常 xff0
  • SpringMVC执行流程

    https www bilibili com video BV1vs4y1S7cZ p 61 2 amp vd source 61 dfa1c0cf0e415bd95bc2263617b13437 Spring MVC源码解析 1 Spri