Spring MVC簡單原理
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簡單原理