前言
Spring强大的自动配置功能帮我们简化了很多开发的步骤,这是我们能快速上手一个复杂的项目,但是我们对于背后的原理确是一知半解,今天我们对MVC的执行流程进行说明。
1. SpringMVC的执行原理
说真的,着手写这篇文章的时候,我很纠结,因为感觉无论以何种方式入手都很难说明白这个流程;这里我尝试先使用图解,文字说明后,通过一段代码加深图形和文字的理解。
这是官方对于MVC流程的图解说明:
- 用户通过输入网址发送请求到前端控制器 DispatcherServlet
- 前端控制器把用户的请求委托给页面的控制器Controller层(这里的底层通过处理器映射HandlerMapping和处理器适配器HandlerAdapter 找到合适的控制层进行匹配)
3,4. 页面控制器 Controller 通过调用service,dao层的相关业务后,把数据和视图封装成为一个模型视图对象(ModelVIew)并返回 - 页面控制器 (Controller层) 把 模型视图对象 (ModelAndView) 返回给前端控制器 (DispatcherServlet)
- 前端控制器 (DispatcherServlet) 进行相关的视图渲染后,把页面返回给用户并产生相应的响应。
这里我们再以我最喜欢的一个up 主狂神的图解再讲解一次它底层的实现
在开发中,上图的实线是Spring底层帮我们实现的,虚线才是我们需要完成的配置。
1 DispatcherServlet 表示的是前端控制器,是整个SpringMVC的控制中心,用户发送请求,DispatcherServlet 接收请求并拦截请求
假设我们发送的url请求为 :http://localhost:8080/SpringMVC/hello。
这个url 请求可以拆分为三部分:
- http://localhost:8080 服务器域名
- SpringMVC部署在服务器上的web站点
- hello 表示控制器
- 通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器
2 HandlerMapping为处理器映射,被DispatcherServlet所调用,主要是为了根据url 找到相应的处理器 (Handler)
3 HandlerExcution表示处理的结果,这里指的是具体的Handler,根据1我们知找到的控制器名为 hello
4 HandlerExcution将解析后的信息传递给 DispatcherServlet (如解析控制的映射等)
5. HandlerAdapter表示处理适配器,这就是按特定的名字去找Controller层的相关class类
6. Controller 层去调用底层service,dao层的方法。
7. Controller层把数据和页面封装成一个模型视图对象(ModelAndView)返回给处理适配器(HandlerAdapter)
8. HandlerAdapter将视图的逻辑名或模型传递给DispatcherServlet
9. DispatcherServlet调用视图解析器 (ViewResolver)来解析逻辑视图名(即加上的前缀和后缀)
10. 视图解析器将解析的逻辑视图名传给DispatcherServlet
11. DispatcherServlet根据视图解析器解析视图结果,调用具体的视图
12. 最终把相关的前端页面呈现给用户
话说了一大堆,可大多数人还是很懵的,接下来通过一段代码对上面的图片和文字加深理解
2. SpringMVC的代码实现
说在前面,这段代码在我们开发中基本上不会使用,完全是为了契合上面的原理所写。
[1] 创建一个Maven项目(可以是导入web模块的也可以是普通项目 ps:普通项目后面有一个bug需要改)===>这里以普通项目进行讲解
[2] 在pom.xml导入依赖
<!--依赖-->
<dependencies>
<!--junit的测试包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--springMVC的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--servlet层的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!--jsp的包-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!--jstl表达式的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
[3] 添加web.xml文件
由于是普通Maven项目,需要添加web框架的支持
这是我们后期编写完成后的目录结构
[4] 编写web.xml文件
1.这里的核心主要是配置我们的前端控制器DispatcherServlet(Spring底层已经帮我们比编写好了),它需要一个springMVC配置文件(springmvc-servlet.xml)的注入
2.在给这个控制器加映射的时候,我们一般使用的路径是 / 而不是 /* ,因为后者会处理 .jsp的请求,这样我们在调用视图解析器的时候。也会给jsp文件加上相应的前后缀,这样会产生映射错误。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--1.注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--关联一个springmvc的配置文件 [servlet-name] -servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别:1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- / 匹配所有的请求,不包括 .jsp -->
<!-- /* 匹配所有的请求,包括 .jsp -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
[3] 编写SpringMVC的配置文件
springmvc-servlet.xml
主要功能有三个:
- 配置处理器映射器HandlerMapping (这里就是处理我们用户发送的url请求,去获取相应的控制器的名字)
- 配置处理器适配器HandlerAdapter (这里既是把请求交给相应名字的控制器处理)
- 配置视图解析器 ViewResolver
作用:
1.获取了ModelAndView的数据
2.解析ModelAndView视图的名字
3.拼接名字,找到对应的视图 /WEB-INF/jsp/hello.jsp
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器: DispatcherServlet给它的ModelAndView
1.获取了ModelAndView的数据
2.解析ModelAndView视图的名字
3.拼接名字,找到对应的视图 /WEB-INF/jsp/hello.jsp
-->
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
[4] 编写处理器(处理用户的url请求)
HelloController (这里是通过实现Controller接口来完成的,但我们开发中一般不这么使用,这里是为例把逻辑解释清楚)
作用:把视图和数据封装成ModelAndView对象(模型和视图),并返回给我们前端控制器DispatcherServlet,然后它在通过视图解析器完成相关页面的渲染,并返回给用户
package com.gs.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//注意:这里我们导入Controller接口
public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv = new ModelAndView();
//封装对象,就在ModelAndView中,Model
mv.addObject("msg","HelloSpringMVC!");
//封装要转发的视图,放在ModelAndView中
mv.setViewName("hello"); // /WEB-INF/jsp/hello.jsp
return mv;
}
}
[5] 配置处理器
在springmvc-servlet.xml加上
<!--Handler-->
<bean id="/hello" class="com.gs.controller.HelloController"/>
这样我们编写完后,运行时是会报错的,主要是因为这个普通项目并没有导入我们的相关jar包,我们可以手动添加该目录(web项目就不会有这种错误)
这时访问:http://localhost:8080/hello
小结
SpringMVC的执行原理,说难不难,说易不易,我们还是要多通过图解和文字,并结合相关代码反复对应记忆,这样我们才能真正做到知其然不知其所以然。