Servlet的呼叫圖
前面我們已經學過了Servlet的生命週期了,我們根據Servlet的生命週期畫出Servlet的呼叫圖加深理解
Servlet的細節
一個已經註冊的Servlet可以被多次對映
同一個Servlet可以被對映到多個URL上。
<servlet>
<servlet-name>Demo1</servlet-name>
<servlet-class>zhongfucheng.web.Demo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>/Demo1</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>/ouzicheng</url-pattern>
</servlet-mapping>
複製程式碼
無論我訪問的是http://localhost:8080/Demo1還是http://localhost:8080/ouzicheng。我訪問的都是Demo1。
Servlet對映的URL可以使用萬用字元
萬用字元有兩種格式:
- *.副檔名
- 正斜槓(/)開頭並以“/*”結尾。
匹配所有
匹配副檔名為.jsp的
如果*.副檔名和正斜槓(/)開頭並以“/*”結尾兩種萬用字元同時出現,匹配的是哪一個呢?
- 看誰的匹配度高,誰就被選擇
*.
副檔名的優先順序最低
Servlet對映的URL可以使用萬用字元和Servlet可以被對映到多個URL上的作用:
- 隱藏網站是用什麼程式語言寫的【.php,.net,.asp實際上訪問的都是同一個資源】
- 用特定的字尾宣告版權【公司縮寫】
<servlet>
<servlet-name>Demo1</servlet-name>
<servlet-class>zhongfucheng.web.Demo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>*.net</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>*.asp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>*.php</url-pattern>
</servlet-mapping>
複製程式碼
Servlet是單例的
為什麼Servlet是單例的
瀏覽器多次對Servlet的請求,一般情況下,伺服器只建立一個Servlet物件,也就是說,Servlet物件一旦建立了,就會駐留在記憶體中,為後續的請求做服務,直到伺服器關閉。
每次訪問請求物件和響應物件都是新的
對於每次訪問請求,Servlet引擎都會建立一個新的HttpServletRequest請求物件和一個新的HttpServletResponse響應物件,然後將這兩個物件作為引數傳遞給它呼叫的Servlet的service()方法,service方法再根據請求方式分別呼叫doXXX方法。
執行緒安全問題
當多個使用者訪問Servlet的時候,伺服器會為每個使用者建立一個執行緒。當多個使用者併發訪問Servlet共享資源的時候就會出現執行緒安全問題。
原則:
- 如果一個變數需要多個使用者共享,則應當在訪問該變數的時候,加同步機制synchronized (物件){}
- 如果一個變數不需要共享,則直接在 doGet() 或者 doPost()定義.這樣不會存線上程安全問題
load-on-startup
如果在元素中配置了一個元素,那麼WEB應用程式在啟動時,就會裝載並建立Servlet的例項物件、以及呼叫Servlet例項物件的init()方法。
作用:
- 為web應用寫一個InitServlet,這個servlet配置為啟動時裝載,為整個web應用建立必要的資料庫表和資料
- 完成一些定時的任務【定時寫日誌,定時備份資料】
在web訪問任何資源都是在訪問Servlet
當你啟動Tomcat,你在網址上輸入http://localhost:8080。為什麼會出現Tomcat小貓的頁面?
這是由預設Servlet為你服務的!
- 我們先看一下web.xml檔案中的配置,web.xml檔案配置了一個預設Servlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
複製程式碼
- 什麼叫做預設Servlet?凡是在web.xml檔案中找不到匹配的元素的URL,它們的訪問請求都將交給預設Servlet處理,也就是說,預設Servlet用於處理所有其他Servlet都不處理的訪問請求
- 既然我說了在web訪問任何資源都是在訪問Servlet,那麼我訪問靜態資源【本地圖片,本地HTML檔案】也是在訪問這個預設Servlet【DefaultServlet】
- 證實一下:當我沒有手工配置預設Servlet的時候,訪問本地圖片是可以訪問得到的
- 現在我自己配置一個預設Servlet,Demo1就是我手工配置的預設Servlet,覆蓋掉web.xml配置的預設Servlet
<servlet>
<servlet-name>Demo1</servlet-name>
<servlet-class>zhongfucheng.web.Demo1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
複製程式碼
- 下面我繼續訪問一下剛才的圖片,此時輸出的是Demo1這個Servlet寫上的內容了
- 總結:**無論在web中訪問什麼資源【包括JSP】,都是在訪問Servlet。**沒有手工配置預設Servlet的時候,你訪問靜態圖片,靜態網頁,預設Servlet會在你web站點中尋找該圖片或網頁,如果有就返回給瀏覽器,沒有就報404錯誤
ServletConfig物件
ServletConfig物件有什麼用?
通過此物件可以讀取web.xml中配置的初始化引數。
現在問題來了,為什麼我們要把引數資訊放到web.xml檔案中呢?我們可以直接在程式中都可以定義引數資訊,搞到web.xml檔案中又有什麼好處呢?
好處就是:能夠讓你的程式更加靈活【更換需求,更改配置檔案web.xml即可,程式程式碼不用改】
獲取web.xml檔案配置的引數資訊
- 為Demo1這個Servlet配置一個引數,引數名是name,值是zhongfucheng
<servlet>
<servlet-name>Demo1</servlet-name>
<servlet-class>zhongfucheng.web.Demo1</servlet-class>
<init-param>
<param-name>name</param-name>
<param-value>zhongfucheng</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Demo1</servlet-name>
<url-pattern>/Demo1</url-pattern>
</servlet-mapping>
複製程式碼
- 在Servlet中獲取ServletConfig物件,通過ServletConfig物件獲取在web.xml檔案配置的引數
ServletContext物件
什麼是ServletContext物件?
當Tomcat啟動的時候,就會建立一個ServletContext物件。它代表著當前web站點
ServletContext有什麼用?
- ServletContext既然代表著當前web站點,那麼所有Servlet都共享著一個ServletContext物件,所以Servlet之間可以通過ServletContext實現通訊。
- ServletConfig獲取的是配置的是單個Servlet的引數資訊,ServletContext可以獲取的是配置整個web站點的引數資訊
- 利用ServletContext讀取web站點的資原始檔
- 實現Servlet的轉發【用ServletContext轉發不多,主要用request轉發】
Servlet之間實現通訊
ServletContext物件可以被稱之為域物件
到這裡可能有一個疑問,域物件是什麼呢?其實域物件可以簡單理解成一個容器【類似於Map集合】
實現Servlet之間通訊就要用到ServletContext的setAttribute(String name,Object obj)方法, 第一個引數是關鍵字,第二個引數是你要儲存的物件
- 這是Demo2的程式碼
//獲取到ServletContext物件
ServletContext servletContext = this.getServletContext();
String value = "zhongfucheng";
//MyName作為關鍵字,value作為值存進 域物件【型別於Map集合】
servletContext.setAttribute("MyName", value);
複製程式碼
- 這是Demo3的程式碼
//獲取ServletContext物件
ServletContext servletContext = this.getServletContext();
//通過關鍵字獲取儲存在域物件的值
String value = (String) servletContext.getAttribute("MyName");
System.out.println(value);
複製程式碼
- 訪問Demo3可以獲取Demo2儲存的資訊,從而實現多個Servlet之間通訊
獲取web站點配置的資訊
如果我想要讓所有的Servlet都能夠獲取到連線資料庫的資訊,不可能在web.xml檔案中每個Servlet中都配置一下,這樣程式碼量太大了!並且會顯得非常囉嗦冗餘。
- web.xml檔案支援對整個站點進行配置引數資訊【所有Servlet都可以取到該引數資訊】
<context-param>
<param-name>name</param-name>
<param-value>zhongfucheng</param-value>
</context-param>
複製程式碼
- Demo4程式碼
//獲取到ServletContext物件
ServletContext servletContext = this.getServletContext();
//通過名稱獲取值
String value = servletContext.getInitParameter("name");
System.out.println(value);
複製程式碼
- 試一下Demo3是否能拿到,相同的程式碼
//獲取到ServletContext物件
ServletContext servletContext = this.getServletContext();
//通過名稱獲取值
String value = servletContext.getInitParameter("name");
System.out.println(value);
複製程式碼
讀取資原始檔
第一種方式:
- 現在我要通過Servlet111讀取1.png圖片
- 按我們以前的方式,程式碼應該是這樣的。
FileInputStream fileInputStream = new FileInputStream("1.png");
System.out.println(fileInputStream);
複製程式碼
- 當我們訪問的時候,卻出錯了!說找不到1.png檔案
- 這是為什麼呢?我們以前讀取檔案的時候,如果程式和檔案在同一包名,可以直接通過檔名稱獲取得到的!,原因很簡單,以前我們寫的程式都是通過JVM來執行的,而現在,我們是通過Tomcat來執行的
- 根據web的目錄規範,Servlet編譯後的class檔案是存放在WEB-INF\classes資料夾中的
- 看到這裡,我們知道了要進入classes目錄中讀取檔案,所以我們將程式碼改成以下方式
FileInputStream fileInputStream = new FileInputStream("D:\\zhongfucheng\\web\\WEB-INF\\classes\\zhongfucheng\\web\\1.png");
System.out.println(fileInputStream);
複製程式碼
- 再去讀取時,就發現可以獲取到檔案了。
- 但是現在問題又來了,我讀取檔案的時候都要寫上絕對路徑,這樣太不靈活了。試想一下,如果我將該讀取檔案的模組移到其他的web站點上,我的程式碼就又要修改了【因為web站點的名字不一樣】。
- 我們通過ServletContext讀取就可以避免修改程式碼的情況,因為ServletContext物件是根據當前web站點而生成的
- 程式碼如下所示:
//獲取到ServletContext物件
ServletContext servletContext = this.getServletContext();
//呼叫ServletContext方法獲取到讀取檔案的流
InputStream inputStream = servletContext.getResourceAsStream("/WEB-INF/classes/zhongfucheng/web/1.png");
複製程式碼
第二種方式:
- 如果我的檔案放在web目錄下,那麼就簡單得多了!,直接通過檔名稱就能獲取
- 程式碼如下所示
//獲取到ServletContext物件
ServletContext servletContext = this.getServletContext();
//呼叫ServletContext方法獲取到讀取檔案的流
InputStream inputStream = servletContext.getResourceAsStream("2.png");
複製程式碼
第三種方式:
通過類裝載器讀取資原始檔。
- 我的檔案放在了src目錄下【也叫做類目錄】
- 程式碼如下所示
//獲取到類裝載器
ClassLoader classLoader = Servlet111.class.getClassLoader();
//通過類裝載器獲取到讀取檔案流
InputStream inputStream = classLoader.getResourceAsStream("3.png");
複製程式碼
- 我的檔案放在了src目錄下的包下
- 程式碼如下,新增包名路徑即可。
//獲取到類裝載器
ClassLoader classLoader = Servlet111.class.getClassLoader();
//通過類裝載器獲取到讀取檔案流
InputStream inputStream = classLoader.getResourceAsStream("/zhongfucheng/web/1.png");
複製程式碼
原則:如果檔案太大,就不能用類裝載器的方式去讀取,會導致記憶體溢位
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章的同學,可以關注微信公眾號:Java3y