Spring MVC简单原理
针对有Java Web基础、Spring基础和Spring MVC使用经验者,文章比较简单,权当自己的一个总结和备忘吧。
前言
目前基于Java的web后端,Spring生态应该是比较常见了。虽然现在流行前后端分离,MVC和后端模板渲染越来越少,后端专注向前端提供数据接口。但由于笔者维护着一个老项目,既有JSP技术也有只返回JSON的接口,两者都是基于Spring MVC这一套技术实现的,所以暂且觉得了解一下Spring MVC原理还是有所裨益的。
Spring MVC工作流
想必大家第一次学习Spring MVC时都见过这张图
看完这张图,Spring MVC的工作流基本是一目了然了。建好工程然后web.xml里配个DispatcherServlet(甚至连web.xml都不需要配置,直接通过Java类和@Configuaration
进行配置),注解@Controller
, @RequestMapping
, @Service
等等一顿注解搞起来,在IDE中把Tomcat配好,OK,一个Spring MVC的Hello World就跑起来了。问起Spring MVC的原理,我也能按着上图工作流巴拉几句。
然而,与其说以上是Spring MVC的原理不如说是MVC的模型。Spring MVC怎么就知道把一个请求路由到对应Controller中方法的呢?方法返回了一个ModelAndView对象甚至是一个视图名String,框架带着Model和视图名找到视图(比如最常见的jsp)后怎么搞呢?半路出家的笔者来,可以说是相当好奇了。
从servlet、web.xml和WEB-INF说起
Servlet
Java的Servlet我就不赘述了,直接看类的注释
A servlet is a small Java program that runs within a Web server.Servlets receive and respond to requests from Web clients, usually across HTTP
Servlet只是一个规范,必须部署到Servlet容器中才能工作,当然本文说的肯定是HttpServlet,所以常用的容器也就是Tomcat、Jetty等。
先看Servlet
接口,每个方法的注释请参见源码
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
其中init方法在Servlet准备对外提供服务时被容器调用,并且整个生命周期中只调用一次,destroy方法当然就是结束时调用了,service方法处理ServletRequest并响应。
web.xml
上面说到把Servlet部署到Servlet容器中后,Servlet就可以接受请求了,那容器怎么知道哪个请求对应哪个Servlet?
OK,我们的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">
<servlet>
<servlet-name>servlet</servlet-name>
<servlet-class>com.foo.bar.SomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>/some/url</url-pattern>
</servlet-mapping>
</web-app>
如上,最简单的一个web.xml描述文件,描述了什么呢?应该都懂,SomeServlet这个Servlet会处理URL为/some/url的请求,这个SomeServlet大概长这样
public class SomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("hello boys");
writer.flush();
writer.close();
}
}
当GET someHost:port/some/url
时,就会返回hello boys了。不难想象,请求多的话,得在web.xml中servlet的节点将越来越多,维护起来也是个体力活儿(当然用xml维护的Spring MVC配置文件也没少多少,不过维护起来轻松一些,因为结合了Spring bean,下面再讲)。
以上应该是对Servlet相关最简单的描述了。
Spring容器管理bean
这个用过没用过Spring的应该都有所了解,说一下我自己的理解
核心就是DI,通过Spring这个IOC容器来管理各个Bean之间的依赖关系,通过Spring在启动时来注入依赖,而不是在写代码时就new出来。
这里面我觉得比较重要的几个类,也是我认为看Spring代码时比较好串联起来的几个类:
- BeanDefinition: Spring的定义,最常见的我个人认为最好理解的也就是Xml中定义的bean,此处膜拜一下Spring预留的扩展点,要不然dubbo的bean怎么能通过Spring容器管理呢对吧,此处以后再说。
- BeanFactory: 好理解,产生Bean的工厂,不多说自己看代码。
- ApplicationContext: Spring的上下文容器,基本用的就是它,暂时把它当成黑箱,所有的bean从这里getBean获取就行了,其余自行看代码。
DispatcherServlet
好,终于到Spring MVC了,先上一个大家都见过的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">
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</webapp>
注意到几个点:
- DispatcherServlet这个servlet接管了所有的请求(servlet-mapping的配置)
- servlet配置里还有个load-on-startup,且值为1
- init-param这里配置了一个参数contextConfigLocation,其值就是Spring bean的配置文件
DispatcherServlet接管了所有请求,OK,看到这里大家估计也都明白了,是在这个servlet里的service方法里根据不同url找到不同的controller来处理的;load-on-startup这个参数规定,如果值大于0,会在启动时由容器初始化,并且值越小初始化顺序越靠前;init-param名字已经很直白了,表示这个servlet可以配置的参数,对该servlet可见(对应的还有个context-param,对所有servlet可见,web.xml里还有其他一些可配置的参数,有兴趣可以自行查阅),contextConfigLication的值就是多个定义bean的xml配置文件。
讲完这些,大家猜应该八九不离十了,总结起来就是一句话:
Servlet容器启动时调用DispatcherServlet中init方法,同时依托Spring,初始化所有bean,并保存一个url到handler之间的映射
至于映射关系就好定义了,xml配置、@RequestMapping
,都是具体的实现。当然我这是简单粗暴的总结,里面细节性的问题都跳过了不过你自己在init方法里打个断点,debug跟一下,一步一步就摸清全部流程了。
文章比较短也比较简单,大家也可能都了解,但我觉得这几个点对理清整个流程还是有帮助的。提出的几个问题也没解答,我只能说答案都在代码里,跟一下全明白。写的不好大家也别笑,多给我提提建议,大家相互进步就OK。