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()方法会被自动回调@NullableModelAndView 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:去处理Controllercontroller不会自动执行 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对象就包含视图解析器 然后把视图渲染出来
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 afterCompletionif (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 othersif (!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
@Nullableprotected 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和handlerreturn 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"作为被寻找的配置文件名称。*/
@Overrideprotected 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[]数组中了 @Overrideprotected 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>
<!– prop表示声明一组UrL的映射,其中key表示配置请求地址,prop标签的内容是Controller的bean的id取值–><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对象
@Nullableprivate 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中实现了该方法
@Nullablepublic 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 othersif (!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@Overridepublic boolean supports(Object handler) {//通过传递过来的Handler对象,判断该对象是否是Controller类型return (handler instanceof Controller);}
/
* 参数1:表示一个request请求对象
* 参数2:表示一个response响应对象
* 参数3: 表示一个Controller对象(HelloController)
*/@Override@Nullablepublic 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:";//转发@Nullableprivate Class<?> viewClass;private String prefix = "";//目标视图的前缀(在那一个目录下存放的)private String suffix = "";//目标视图的后缀(.jsp、asp)
}