你們是否清楚,Tomcat是如何加載Spring和SpringMVC,今天咱們就弄清下這個過程(記錄最關鍵的東西)html
其中會涉及到大大小小的知識,包括加載時候的設計模式,Servlet知識等,看了你確定有所收穫~java
tomcat是一種Java寫的Web應用服務器,也被稱爲Web容器,專門運行Web程序web
tomcat啓動了以後會在操做系統中生成一個Jvm(Java虛擬機)的進程,從配置監聽端口(默認8080)監聽發來的HTTP/1.1協議的消息spring
默認配置文件這樣數據庫
當Tomcat啓動完成後,它就會加載其安裝目錄下webapps裏的項目(放war包會自動解壓成項目)設計模式
是運行在同一個JVM上的(Tomcat啓動時建立的那個),多個項目就是多個線程,之因此項目間數據不共享,是由於類加載器不同的緣故數組
tomcat啓動完畢後,最關鍵的是生成了ServletContext(Tomcat的上下文),而後會根據webapps項目裏的web.xml進行加載項目瀏覽器
下面是一個SpringMVC+Spring項目的部分web.xmltomcat
<!--如下爲加載Spring須要的配置--> <!--Spring配置具體參數的地方--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml </param-value> </context-param> <!--Spring啓動的類--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--如下爲加載SpringMVC須要的配置--> <servlet> <servlet-name>project</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <!--servlet被加載的順序,值越小優先級越高(正數)--> <servlet-mapping> <servlet-name>project</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </servlet>
tomcat首先會加載進ContextLoaderListener,而後將applicationContext.xml裏寫的參數注入進去,來完成一系列的Spring初始化(如各類bean,數據庫資源等等)服務器
這裏就是常常聽到的Ioc容器的初始化了,咱們搜索這個類發現如下代碼
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { //省略其餘方法 /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } //省略其餘方法 }
這裏最重要的是經過ServletContext,初始化屬於Spring的上下文WebApplicationContext,並將其存放在ServletContext
WebApplicationContext多重要老鐵們都懂得,咱們常常用webApplicationContext.getBean()來獲取被Spring管理的類,因此這也是IOC容器的核心
Spring採用這種監聽器來啓動自身的方法,也是一種設計模式,叫觀察者模式:
整個過程是這樣的,Tomcat加載webapps項目時,先經過反射加載在web.xml標明的類(統統放入一個數組)
到某個時刻,我tomcat(事件源,事件的起源)會發起一個叫ServletContextEvent的事件(裏面帶着各類參數)
凡是實現了ServletContextListener接口的類,我都會調用裏面的contextInitialized方法,並把這個事件參數傳進去
咳咳,如今我看看這個數組裏有沒符合條件的(遍歷),發現真有實現這個接口的(類 instanceof 接口),就調用contextInitialized方法
因而Spring就被動態加載進來了~~
加載一個類,能夠用用完整的類名,經過java反射加載,Class.forName(類名)
也能直接new 一個類 來加載
看配置文件,標籤是servlet,咱們得首先了解下servlet是什麼東東
Servlet是一個接口,爲web通訊而生(說白了就是一堆sun公司的大佬們開會,拍板造出的類,有固定的幾個方法)
tomcat有一套定義好的程序(其實不僅是tomcat,能跑java寫的web應用服務器如Jetty等,都有這固定程序)
1.當tomcat加載進來一個類時,若是它實現了Servlet接口,那麼會記載到一個Map裏,而後執行一次init()方法進行Servlet初始化
2.當tomcat收到瀏覽器的請求後,就會在Map裏找對應路徑的Servlet處理,路徑就是寫在<url-pattern>標籤裏的參數,調用service()這個方法
3.當Servlet要被銷燬了,就調用一次destroy()方法
各位看到這是否是感受相識,跟Spring加載差很少嘛,都是實現了一個接口後就被命運(tomcat)安排~~
固然,咱們本身實現Servlet接口太雞兒麻煩了,因而有HttpServlet(一個抽象類)幫咱們實現了大部分方法(包含http頭的設置,doXXX方法判斷等等)
因此咱們只要繼承HttpServlet就實現幾個方法就能用啦
爲何要講Servlet,由於SpringMVC的核心就是DispatcherServlet(前置控制器),如圖
DispatcherServlet由SpringMVC的實現,已經實現的很棒棒了,咱們不須要再動它
tomcat從web.xml中加載DispatcherServlet,而後會調用它的init()方法
Servlet配置文件默認在/WEB-INF/<servlet-name>-servlet.xml,因此如今默認叫project-servlet.xml
固然,也能本身指定文件
<!--如下爲加載SpringMVC須要的配置--> <servlet> <servlet-name>project</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> <!--指定配置文件--> <init-param> <param-name>contextCOnfigLocation</param-name> <param-value>classPath:spring-servlet.xml</param-value> </init-param> <servlet-mapping> <servlet-name>project</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </servlet>
當SpringMVC加載好後,瀏覽器有請求過來,若是是.html結尾,tomcat就會交給DispatcherServlet處理
而DispatcherServlet會根據路徑找到對應的處理器處理,能夠理解爲咱們寫的Controller接收到了(具體SpringMVC處理流程會寫一篇博文)
至此,瀏覽器發送請求,到執行咱們寫的代碼這個流程就結束了 ~~撒花~~
既然講到tomcat加載這兩個框架,你們發現沒有,在web.xml中,Spring加載是寫在DispatcherServlet加載前面的
咱們不妨來看看DispatcherServlet的初始化方法,因爲DispatcherServlet是經過層層繼承而來的,因此初始化的方法也變成了
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
//其餘方法
@Override
public final void init() throws ServletException {
//其餘代碼
// Let subclasses do whatever initialization they like.
initServletBean();
}
protected void initServletBean() throws ServletException {
}
//其餘方法
}
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
//其餘方法
@Override
protected final void initServletBean() throws ServletException {
//其餘代碼
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
//其餘代碼
}
protected WebApplicationContext initWebApplicationContext() {
//得到了Spring創造的webApplicationContext,關鍵
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext); //設置父上下文
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
//其餘代碼
}
//其餘方法
}
咱們能夠看到,HttpServlet將init()實現了,留下了initServletBean()抽象方法
而FrameworkServlet實現了initServletBean()方法並定義成final(不容許重寫),在此方法中調用了initWebApplicationContext()
而initWebApplicationContext()中說明了若是tomcat裏存在webapplication就獲取它,而後將其設置爲SpringMVC的父上下文
至此DispatcherServlet初始化完成(固然我省略了其餘的初始化作的事)
所以Spring和SpringMVC容器之間是父子關係,因爲子容器能夠訪問父容器的內容,而反過來不行,因此不要想在Service裏自動注入Controller這種操做
所以會有下面狀況
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
...../ 此處省略>
<!-- 掃描所有包 -->
<context:component-scan base-package="com.shop"></context:component-scan>
<!--原本正確寫法是這樣的>
<!--
<!-- 掃描包Service實現類 -->
<context:component-scan base-package="com.shop.service"></context:component-scan>
>
</beans>
那麼,Controller將進入Spring的上下文,SpringMVC裏就沒Controller了,到時候有請求給DispatcherServlet時,就會找不到Controller而404
這個是能夠的,SpringMVC不須要Spring也能使用,可是加入Spring是爲了更好的兼容其餘的框架(數據庫框架等等)
可是若是用了Spring就不能這樣作,包括Spring掃Controller,SpringMVC也掃一次Controller這些操做,會出現各類奇怪的問題
以上就講完啦,但願你們點點贊,或者一鍵3連不迷路~~
參考:https://www.cnblogs.com/wyq1995/p/10672457.html
參考:https://blog.csdn.net/s740556472/article/details/54879954