SpringMVC执行流程

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进行适配.

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值