引言
对于SpringMVC
相信诸位并不陌生,这是Java开发过程中使用最频繁的框架,在你的项目中可能不一定用MyBatis
,但绝对会使用SpringMVC
,因为操作数据库还有Hibernate、JPA
等其他ORM
框架选择,但SpringMVC
这个框架在其领域中,可谓是独领风骚,因此在面试中也会常常问到一些与之相关的面试题,其中最为经典的则是那道:
SpringMVC
在启动后是如何工作的?(工作原理)
对于这题的答案,相信大家在“Java面试八股文”中绝对背过,但之前大多数小伙伴应该也只是死记,并未真正的理解其核心原理,那本篇的目的就在于让诸位真正的掌握SpringMVC
原理。当然,为了更好的理解,咱们也不会以之前分析底层时的那种源码方式,对其进行长篇概述,本次则使用一种新的方式来对其进行原理讲解。
那新的方式是什么呢?那就是自己手写框架,真正的理解就是自己能够把轮子重新造一次,这原本源码的方式更加形象,也能够更加让我们对其原理印象深刻。
在之后有可能会写的《源码分析》专题中,会再次详细剖析一些常用开源框架的源码实现,同时为了加深对每个技术栈的理解,在剖析清楚源码实现后,也会以本文这种形式,对框架进行迷你版的手写实战,因此本文也算是一个新的尝试。
一、SpringMVC框架的概述与回忆
SpringMVC
是Spring
家族中的元老之一,它是一个基于MVC
三层架构模式的Web
应用框架,它的出现也一统了JavaWEB
应用开发的项目结构,从而避免将所有业务代码都糅合在同一个包下的复杂情况。在该框架中通过把Model、View、Controller
分离,如下:
M/Model
模型:由service、dao、entity
等JavaBean
构成,主要负责业务逻辑处理。V/View
视图:负责向用户进行界面的展示,由jsp、html、ftl....
等组成。C/Controller
控制器:主要负责接收请求、调用业务服务、根据结果派发页面。
SpringMVC
贯彻落实了MVC
思想,以分层工作的模式,把整个较为复杂的web
应用拆分成逻辑清晰的几部分,从很大程度上也简化了开发工作,减少了团队协作开发时的出错几率。
回想最初的servlet
开发,或者说最初我们学习Java时,如稚子般的操作,当时也不会划分模块、划分包,所有代码一股脑的全都放在少数的几个包下。但不知从何时起,慢慢的,每当有一个新的项目需求出现时,我们都会先对其划分模块,再划分层次,SpringMVC
这个框架已经让每位Java
开发彻底将MVC
思想刻入到了DNA
中,无论是最初的单体开发,亦或是如今主流的分布式、微服务开发,相信大家都已经遵守着这个思想。
SpringMVC
框架的设计,是以请求为驱动,围绕Servlet
设计的,将请求发给控制器,然后通过模型对象,分派器来展示请求结果的视图。SpringMVC
的核心类是DispatcherServlet
,它是一个Servlet
子类,顶层是实现的Servlet
接口。
当然,此刻暂且避开其原理不谈,先回想最初的SpringMVC
是如何使用的呢?一起来看看。
1.1、SpringMVC的使用方式
对于SpringMVC
框架的原生使用方式,估计大部分小伙伴都已经忘了,尤其是近些年SpringBoot
框架的流行,由于其简化配置的特性,让我们几乎无需再关注最初那些繁杂的XML
配置。
说到这块就引起了我早些年那些痛苦的回忆,在
SpringBoot
还未那么流行之前,几乎所有的配置都是基于XML
来弄的,而且每当引入一个新的技术栈,都需要配置一大堆文件,比如Spring、SpringMVC、MyBatis、Shiro、Quartz、EhCache....
,这个整合过程无疑是痛苦的。
但随着后续的SpringBoot
流行,这些问题则无需开发者再关注,不过成也SpringBoot
,败也SpringBoot
,尤其是近几年新入行的Java程序员,正是由于未曾有过之前那种繁重的XML
配置经历,因此对于application.yml
中很多技术栈的配置项也并不是特别理解,项目开发中需要引入一个新的技术栈时,几乎靠在网上copy
他人的配置信息,也就成了“知其然而不知其所以然”,这对后续想要深入研究底层也成了一道新的屏障。
就此打住,感慨也不多说了,咱们先来回忆回忆最初
SpringMVC
的使用方式:基于最普通的maven-web
工程构建。
在使用SpringMVC
框架时,一般会首先配置它的核心文件: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"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 通过context:component-scan元素扫描指定包下的控制器-->
<!-- 扫描com.xxx.xxx及子孙包下的控制器(扫描范围过大,耗时)-->
<context:component-scan base-package="com.xxx.controller"/>
<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- viewClass需要在pom中引入两个包:standard.jar and jstl.jar -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 省略其他配置...... -->
</beans>
在springmvc-servlet.xml
这个核心配置文件中,最重要的其实是配置Controller
类所在的路径,即包扫描的路径,以及配置一个视图解析器,主要用于解析请求成功之后的视图数据。
OK~,配置好了springmvc-servlet.xml
文件后,紧接着我们会再修改maven-web
项目核心文件web.xml
中的配置项:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 再这里会添加一个SpringMVC的servlet配置项 -->
<servlet>
<!-- 首先指定SpringMVC核心控制器所在的位置 -->
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- DispatcherServlet启动时,从哪个文件中加载组件的初始化信息 -->
<!--此参数可以不配置,默认值为:/WEB-INF/springmvc-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!--web.xml 3.0的新特性,是否支持异步-->
<!--<async-supported>true</async-supported>-->
</servlet>
<!-- 配置路由匹配规则,/ 代表匹配所有,类似于nginx的location规则 -->
<serv