五、Spring MVC的工作流程执行图
5.1 DispatcherServlet流程中每一步的含义
- 用户通过浏览器向服务器发送请求。请求会被Spring MVC的DispatcherServlet(前端控制器)所拦截;
- DispatcherServlet拦截到请求后,会通过HandlerMapping处理器映射器;
- 处理器映射器根据请求URL找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
- DispatcherServlet会通过返回信息选择合适的HandlerAdapter(处理器适配器);
- HandlerAdpater会调用并执行Handler(处理器),这里的处理器指的就是程序中Controller类,也被称为后端控制器;
- Controller执行后会返回一个ModelAndView对象,该对象中会包含视图名或包含模型和视图名;
- HandlerAdapter将ModelAndView对象返回给DispatcherServlet;
- DispatcherServlet会根据ModelAndView对象选择一个合适ViewResolver(视图解析器);
- ViewResolver解析后,会向DispatcherServlet中返回一个具体的View(视图);
- DispatcherServlet对View进行渲染(即将模型数据填充至视图中);
- 视图渲染结果会返回给客户端浏览器显示。
5.2 DispatcherServlet前端控制器的内部工作原理机制
我们要清楚DispatcherServlet是怎样拦截到我们在浏览器地址栏中发出的请求,以及通过我们的请求找到控制器类中对应的请求方法,并获取到方法的返回值,通过视图解析器找到对应的视图,最终通过请求转发跳转到我们想要请求的页面中。
- web.xml
<?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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1"><display-name>Archetype Created Web Application</display-name><!--配置servlet调度程序--><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--启动tomcat服务器时,默认加载applicationContext-springmvc.xml配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath*:applicationContext-springmvc.xml</param-value></init-param><!--表示容器在启动时立即加载Servlet(此处值越小,加载优先级越高)--><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
我们知道在web.xml文件中配置前端控制器,但是内部原理是怎么实现的,下面可以通过一个简单的例子来演示下原理,我们不使用系统提供的前端控制器类,我们自己
新建一个DispatcherServlet类。
使用maven创建一个springmvc-DispatcherServlet-principle的Web项目,只需导入javax.servlet.api的依赖文件:
-
pom.xml
<!--servlet-api--> <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>compile</scope> </dependency>
-
DispatcherServlet类(要继承HttpServlet类)
package com.servlet;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;/*** @author 一宿君(CSDN : qq_52596258)* @date 2021-07-25 15:04:05*/ public class DispatcherServlet extends HttpServlet { public DispatcherServlet() { System.out.println("构造方法");}@Overridepublic void init() throws ServletException { System.out.println("初始化方法---作用是为了存储通过注解扫描controller类下所有@RequestMapping注解标注的value值(请求名)");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp);}}
-
springmvc-config.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"xmlns:context="http://www.springframework.org/schema/context"xmlns:bean="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-3.2.xsdhttp://www.springframework.org/schema/mvchttps://www.springframework.org/schema/mvc/spring-mvc.xsd"></beans>
-
web.xml
<?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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1"><display-name>Archetype Created Web Application</display-name><!--配置servlet调度程序--><servlet><servlet-name>springmvc</servlet-name><servlet-class>com.servlet.DispatcherServlet</servlet-class><!--启动tomcat服务器时,默认加载springmvc-config.xml配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath*:springmvc-config.xml</param-value></init-param><!--表示容器在启动时立即加载Servlet(此处值越小,加载优先级越高)--><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
-
创建UserControler控制器类
package com.controller;/*** @author 一宿君(CSDN : qq_52596258)* @date 2021-07-25 15:11:26*/ public class UserController { //请求到达登录界面public String toLogin(){ return "login";}//请求到达注册界面public String toRegister(){ return "register";}//请求到达删除界面public String toDelete(){ return "delete";} }
在webapp目录下建三个页面:
- login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>Title</title> </head> <body> <h1>这是login.jsp页面</h1> </body> </html>
- register.jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>Title</title> </head> <body> <h1>这是register.jsp页面</h1> </body> </html>
- delete.jsp
到这一步我们先将项目部署到tomcat服务器上,先保证运行正常!<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>Title</title> </head> <body> <h1>这是delete.jsp页面</h1> </body> </html>
没问题,进行下一步操作!
5.3 Spring MVC注解驱动原理
-
springmvc.xml
<!--开启springmvc注解驱动--> <mvc:annotation-driven/> <!--指定springmvc扫描哪个包--> <context:component-scan base-package="com.controller"/><!--内部资源视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!--扫描以.jsp为后缀的jsp页面--><property name="suffix" value=".jsp"/><!--扫描/WEB-INF下的所有jsp--><property name="prefix" value="/WEB-INF/jsp/"/> </bean>
开启注解驱动,我们就无需关注前端控制器是如何帮我们找到相应的视图以及解析,但是原理我们还是要清楚的,如下重点: -
DispatcherServlet类中init()初始化方法:
/*** 用于存储请求访问名字*/ Map<String,String> mappings = new HashMap<>();@Overridepublic void init() throws ServletException { System.out.println("初始化方法---作用是为了存储通过注解扫描controller类下所有@RequestMapping注解标注的value值(请求名)");mappings.put("toLogin","com.controller.UserController.toLogin");mappings.put("toRegister","com.controller.UserController.toRegister");mappings.put("toDelete","com.controller.UserController.toDelete");}
-
DispatcherServlet类中的doGet方法
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求URIString requestURI = req.getRequestURI();System.out.println(requestURI);//2 获取请求URI名称(从最后一个/处,加1截取)String requestURIName = requestURI.substring(requestURI.lastIndexOf("/") + 1);System.out.println(requestURIName); }
-
到这步我们运行下项目,在地址栏输入toLogin,看控制台:
当我们得到请求地址名后,再去map集合中进行对比,查看我们事先初始化时(此处是模拟springmvc通过开启注解扫描将@RequestMapping注解标注的方法存入集合中)存入集合中的方法,是否有有我们现在所发出的请求地址名,如果有则进行下一步视图解析操作,如果无,则说明该请求无效。
- DispatcherServlet类中的doGet方法:
@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求URIString requestURI = req.getRequestURI();System.out.println(requestURI);//2 获取请求URI名称(从最后一个/处,加1截取)String requestURIName = requestURI.substring(requestURI.lastIndexOf("/") + 1);System.out.println(requestURIName);/*** 获取到请求名后,与我们实现存在map集合中的键值对比,是否存在*/if(mappings.containsKey(requestURIName )){ //获取请求URI名称---在控制类中的所对应的方法的路径String classPath = mappings.get(requestURIName);System.out.println(classPath);//获取方法所在类的路径String className = classPath.substring(0,classPath.lastIndexOf("." ));System.out.println(className);//获取所对应的方法名(从最后一个“.”加1截取)String methodName = classPath.substring(classPath.lastIndexOf(".") + 1);System.out.println(methodName);try { //通过反射加载到类Class clz = Class.forName(className);//创建加载类对象Object obj = clz.getDeclaredConstructor().newInstance();//获取对象中的指定请求方法Method method = clz.getDeclaredMethod(methodName,null);//执行反射中对应的请求方法,并获取返回值(也即是页面名称)Object page = method.invoke(obj,null);System.out.println(page);req.getRequestDispatcher(page + ".jsp").forward(req,resp);} catch (Exception e) { e.printStackTrace();}}else { throw new ServletException("请求的URI不存在!");}}
注意上述请求转发request.getRequestDispatcher(page + “.jsp”).forward(req,resp);
一定要交.jsp 后缀名,因为我们在web.xml配置文件中,配置的是< url-pattern>/< /url-pattern>,不会拦截.jsp文件,其余请求以及静态资源都会被当做请求拦截。
我们重启项目,在地址栏输入toLogin
查看控制台:
同理再地址栏输入toRegister和toDelete: