Spring MVC系列之Spring MVC的前端控制器(DispatcherServlet)工作流程及原理
Posted 一宿君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC系列之Spring MVC的前端控制器(DispatcherServlet)工作流程及原理相关的知识,希望对你有一定的参考价值。
五、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/javaee http://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("构造方法"); } @Override public void init() throws ServletException { System.out.println("初始化方法---作用是为了存储通过注解扫描controller类下所有@RequestMapping注解标注的value值(请求名)"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected 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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/mvc https://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/javaee http://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<>(); @Override public 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方法
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求URI String 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方法:
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1 获取请求URI String 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:
我觉类通过上述简单的详解,多少有点懂了吧,Spring MVC强不强,你我说了都不算,就像苏大强差不多,没有蔡根花小宝贝,苏大强是真的强!!!妥了~
以上是关于Spring MVC系列之Spring MVC的前端控制器(DispatcherServlet)工作流程及原理的主要内容,如果未能解决你的问题,请参考以下文章
[Interview]Java 面试宝典系列之 Spring MVC
Shiro系列之Shiro+Spring MVC整合(Integration)
Shiro系列之Shiro+Spring MVC整合(Integration)
Shiro系列之Shiro+Spring MVC整合(Integration)