Tomcat是如何加載Spring和SpringMVC及Servlet相關知識


概述

大家是否清楚,Tomcat是如何加載Spring和SpringMVC,今天我們就弄清下這個過程(記錄最關鍵的東西)

其中會涉及到大大小小的知識,包括加載時候的設計模式,Servlet知識等,看了你肯定有所收獲~

Tomcat

tomcat是一種Java寫的Web應用服務器,也被稱為Web容器,專門運行Web程序

tomcat啟動

tomcat啟動了之后會在操作系統中生成一個Jvm(Java虛擬機)的進程,從配置監聽端口(默認8080)監聽發來的HTTP/1.1協議的消息

默認配置文件這樣

 

 

 當Tomcat啟動完成后,它就會加載其安裝目錄下webapps里的項目(放war包會自動解壓成項目)

小提問:webapps里多個項目,是運行在同一個JVM上嗎

是運行在同一個JVM上的(Tomcat啟動時創建的那個),多個項目就是多個線程,之所以項目間數據不共享,是因為類加載器不一樣的緣故

加載Web程序(Spring+SpringMVC框架)

tomcat啟動完畢后,最關鍵的是生成了ServletContext(Tomcat的上下文),然后會根據webapps項目里的web.xml進行加載項目

下面是一個SpringMVC+Spring項目的部分web.xml

 1 <!--以下為加載Spring需要的配置-->
 2 <!--Spring配置具體參數的地方-->
 3 <context-param>
 4   <param-name>contextConfigLocation</param-name>
 5   <param-value>
 6     classpath:applicationContext.xml
 7   </param-value>
 8 </context-param>
 9 <!--Spring啟動的類-->
10 <listener>
11   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
12 </listener>
13 
14  <!--以下為加載SpringMVC需要的配置-->
15 <servlet>
16   <servlet-name>project</servlet-name>
17   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
18   <load-on-startup>1</load-on-startup>   <!--servlet被加載的順序,值越小優先級越高(正數)-->
19 
20   <servlet-mapping>
21         <servlet-name>project</servlet-name>
22         <url-pattern>*.html</url-pattern>
23   </servlet-mapping>
24 </servlet>

 

初始化Spring

tomcat首先會加載進ContextLoaderListener,然后將applicationContext.xml里寫的參數注入進去,來完成一系列的Spring初始化(如各種bean,數據庫資源等等)

這里就是經常聽到的Ioc容器的初始化了,我們搜索這個類發現以下代碼

 1 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
 2     //省略其他方法
 3 
 4     /**
 5      * Initialize the root web application context.
 6      */
 7     @Override
 8     public void contextInitialized(ServletContextEvent event) {
 9         initWebApplicationContext(event.getServletContext());
10     }
11 
12     //省略其他方法
13 }

 

這里最重要的是通過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 一個類 來加載

初始化SpringMVC

看配置文件,標簽是servlet,我們得首先了解下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就實現幾個方法就能用啦

 

 

SpringMVC加載

為什么要講Servlet,因為SpringMVC的核心就是DispatcherServlet(前置控制器),如圖

 

 

 DispatcherServlet由SpringMVC的實現,已經實現的很棒棒了,我們不需要再動它

tomcat從web.xml中加載DispatcherServlet,然后會調用它的init()方法

Servlet配置文件默認在/WEB-INF/<servlet-name>-servlet.xml,所以現在默認叫project-servlet.xml

當然,也能自己指定文件

 1  <!--以下為加載SpringMVC需要的配置-->
 2 <servlet>
 3   <servlet-name>project</servlet-name>
 4   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 5   <load-on-startup>1</load-on-startup>
 6 
 7    <!--指定配置文件-->
 8   <init-param>
 9                    <param-name>contextCOnfigLocation</param-name>
10                    <param-value>classPath:spring-servlet.xml</param-value>
11   </init-param>
12 
13   <servlet-mapping>
14         <servlet-name>project</servlet-name>
15         <url-pattern>*.html</url-pattern>
16   </servlet-mapping>
17 </servlet>

 

當SpringMVC加載好后,瀏覽器有請求過來,如果是.html結尾,tomcat就會交給DispatcherServlet處理

而DispatcherServlet會根據路徑找到對應的處理器處理,可以理解為我們寫的Controller接收到了(具體SpringMVC處理流程會寫一篇博文)

至此,瀏覽器發送請求,到執行我們寫的代碼這個流程就結束了  ~~撒花~~

Spring和SpringMVC的容器問題

既然講到tomcat加載這兩個框架,大家發現沒有,在web.xml中,Spring加載是寫在DispatcherServlet加載前面的

我們不妨來看看DispatcherServlet的初始化方法,由於DispatcherServlet是通過層層繼承而來的,所以初始化的方法也變成了

 1 public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
 2 
 3     //其他方法
 4 
 5     @Override
 6     public final void init() throws ServletException {
 7         
 8         //其他代碼
 9 
10         // Let subclasses do whatever initialization they like.
11         initServletBean();
12     }
13 
14     protected void initServletBean() throws ServletException {
15     }
16 
17     //其他方法
18 }
19 
20 public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
21 
22     //其他方法
23 
24     @Override
25     protected final void initServletBean() throws ServletException {
26         //其他代碼
27 
28         try {
29             this.webApplicationContext = initWebApplicationContext();
30             initFrameworkServlet();
31         }
32         catch (ServletException | RuntimeException ex) {
33             logger.error("Context initialization failed", ex);
34             throw ex;
35         }
36 
37         //其他代碼
38     }
39 
40     protected WebApplicationContext initWebApplicationContext() {
41 
42         //獲得了Spring創造的webApplicationContext,關鍵
43         WebApplicationContext rootContext =
44                 WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 
45         WebApplicationContext wac = null;
46 
47         if (this.webApplicationContext != null) {
48             // A context instance was injected at construction time -> use it
49             wac = this.webApplicationContext;
50             if (wac instanceof ConfigurableWebApplicationContext) {
51                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
52                 if (!cwac.isActive()) {
53                     // The context has not yet been refreshed -> provide services such as
54                     // setting the parent context, setting the application context id, etc
55                     if (cwac.getParent() == null) {
56                         // The context instance was injected without an explicit parent -> set
57                         // the root application context (if any; may be null) as the parent
58                         cwac.setParent(rootContext);      //設置父上下文
59                     }
60                     configureAndRefreshWebApplicationContext(cwac);
61                 }
62             }
63         }
64         
65         //其他代碼
66     }
67 
68     //其他方法
69 }

 

我們可以看到,HttpServlet將init()實現了,留下了initServletBean()抽象方法

而FrameworkServlet實現了initServletBean()方法並定義成final(不允許重寫),在此方法中調用了initWebApplicationContext()

而initWebApplicationContext()中說明了如果tomcat里存在webapplication就獲取它,然后將其設置為SpringMVC的父上下文

至此DispatcherServlet初始化完成(當然我省略了其他的初始化做的事)

因此Spring和SpringMVC容器之間是父子關系,由於子容器可以訪問父容器的內容,而反過來不行,所以不要想在Service里自動注入Controller這種操作

 

因此會有下面情況

Spring配置進行掃包的時候,如果將Controller也掃進來了會怎樣

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:context="http://www.springframework.org/schema/context" 
 4     ...../ 此處省略>
 5 
 6     <!-- 掃描全部包 -->
 7     <context:component-scan base-package="com.shop"></context:component-scan>
 8     
 9   <!--本來正確寫法是這樣的>
10   <!--
11     <!-- 掃描包Service實現類 --> 
12     <context:component-scan base-package="com.shop.service"></context:component-scan>
13   >
14 
15 </beans>

 

那么,Controller將進入Spring的上下文,SpringMVC里就沒Controller了,到時候有請求給DispatcherServlet時,就會找不到Controller而404

小提問:那么SpringMVC掃描所有的包可以嗎

這個是可以的,SpringMVC不需要Spring也能使用,但是加入Spring是為了更好的兼容其他的框架(數據庫框架等等,例如SpringMVC掃了service和dao,那是不支持事務的)

但是如果用了Spring就不能這樣做,包括Spring掃Controller,SpringMVC也掃一次Controller這些操作,會出現各種奇怪的問題

 

小提問:SpringMVC和Spring為什么要兩個容器呢,Springboot一個容器就搞定了呀

其實這和Spring設計理念相關,畢竟一個框架不可能做完一切,總要和第三方框架配合。假設公用一個容器,那么多個第三方框架的Bean沖突概率就很高,由每個第三方框架維護自己的容器,將Spring作為父類容器,則能和平相處。

Springboot之所以被大家喜愛,是因為它遵循默認大於配置,它幫我們配置好了常用的框架(大部分是寫好了配置類,更改了框架的啟動注冊流程),底層該用SpringMVC還是SpringMVC,該用Spring還是Spring。所以可以在框架啟動注冊時,就用一個容器把相關內容合理的安排好(這不是全部框架都支持,例如SpringCloud就有自己的獨有的容器,並把它設置為Springboot的父容器~)

 

 

以上就講完啦,希望大家點點贊,或者一鍵3連不迷路~~

參考:https://www.cnblogs.com/wyq1995/p/10672457.html

參考:https://blog.csdn.net/s740556472/article/details/54879954

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM