1. 程式人生 > >Spring MVC簡單原理

Spring MVC簡單原理

都在 是我 down 解答 AR block 前後端分離 代碼 SM

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。

Spring MVC簡單原理