Servlet基礎之ServletConfig與ServletContext接口詳解

​ 在之前的博客中,我們講到了Serlvet的創建和虛擬路徑的映射,關於其中的知識相信大家也都已經掌握了,下面我們就要來學習下和Servlet息息相關的兩個接口:ServletConfig和ServletContext。

ServletConfig描述和方法

​ 首先我們來看下源碼中對兩個接口的解釋,首先是ServletConfig和其中定義的方法:

/**
 * A servlet configuration object used by a servlet container
 * to pass information to a servlet during initialization. 
 */
 public interface ServletConfig {
   /**
     * Returns the name of this servlet instance.
     * The name may be provided via server administration, assigned in the 
     * web application deployment descriptor, or for an unregistered (and thus
     * unnamed) servlet instance it will be the servlet's class name.
     *
     * @return	the name of the servlet instance
     */
    public String getServletName();

    /**
     * Returns a reference to the {@link ServletContext} in which the caller
     * is executing.
     *
     * @return	a {@link ServletContext} object, used
     * by the caller to interact with its servlet container
     * 
     * @see ServletContext
     */
    public ServletContext getServletContext();

    /**
     * Gets the value of the initialization parameter with the given name.
     *
     * @param name the name of the initialization parameter whose value to
     * get
     *
     * @return a <code>String</code> containing the value 
     * of the initialization parameter, or <code>null</code> if 
     * the initialization parameter does not exist
     */
    public String getInitParameter(String name);

    /**
     * Returns the names of the servlet's initialization parameters
     * as an <code>Enumeration</code> of <code>String</code> objects, 
     * or an empty <code>Enumeration</code> if the servlet has
     * no initialization parameters.
     *
     * @return an <code>Enumeration</code> of <code>String</code> 
     * objects containing the names of the servlet's 
     * initialization parameters
     */
    public Enumeration<String> getInitParameterNames();
 }

​ 上面是源碼中的註釋,我給大家解釋下:ServletConfig是Servlet容器在創建servlet時根據web.xml中的配置信息爲對應的Servlet對象創建的配置對象,在Serlvet初始化通過init方法將配置信息傳遞給Servlet。也就是說,每個Servlet都擁有自己的一個ServletConfig,並可以通過此獲取web.xml中的配置信息,下面讓我們看下其中的四個方法的功能描述:

  1. getServletName:返回Servlet實例的名稱,即配置中的<servlet-name>或@WebServlet中的name屬性,如果沒有配置的話,則返回該實例的類名(可通過xxx.class.getName()獲取);
  2. getServletContext:返回當前正在運行的web應用ServletContext的引用,在每個web應用中,ServletContext是全局唯一的,此對象的引用是Servlet容器創建Servlet時初始化的;
  3. getInitParameter:通過傳入的參數名獲取對應的初始化參數的值,即配置中的<init-param>或@WebServlet中的initParams屬性中的一個元素;
  4. getInitParameterNames:以Enumeration<String>的形式返回對應Servlet的初始化參數的名稱(其中的name屬性),如果沒有初始化參數,則返回一個空的Enumeration<String>

​ 其實上述的方法除了getServletContext(),其他三個方法我們在開發中使用的不多,初始化參數我們可以使用配置文件來進行配置,這樣還可以通過給配置文件增加不同的後綴來在不同的環境中使用(測試、預發、生產等)。其中有個比較重要的應用,就是上篇文章中說到的DefaultServlet的配置,可以使用init-param來設置缺省Servlet的一些’行爲’。

ServletContext描述與常用方法

​ 首先我們也來看下ServletContext的源碼(其中定義的方法過多,因此只講述常用的方法):

/**
 * Defines a set of methods that a servlet uses to communicate with its
 * servlet container, for example, to get the MIME type of a file, dispatch
 * requests, or write to a log file.
 *
 * <p>There is one context per "web application" per Java Virtual Machine.  (A
 * "web application" is a collection of servlets and content installed under a
 * specific subset of the server's URL namespace such as <code>/catalog</code>
 * and possibly installed via a <code>.war</code> file.) 
 *
 * <p>In the case of a web
 * application marked "distributed" in its deployment descriptor, there will
 * be one context instance for each virtual machine.  In this situation, the 
 * context cannot be used as a location to share global information (because
 * the information won't be truly global).  Use an external resource like 
 * a database instead.
 *
 * <p>The <code>ServletContext</code> object is contained within 
 * the {@link ServletConfig} object, which the Web server provides the
 * servlet when the servlet is initialized.
 *
 * @author 	Various
 *
 * @see 	Servlet#getServletConfig
 * @see 	ServletConfig#getServletContext
 */

public interface ServletContext {
  //...
}

​ 從上述描述,我們可以知道,每個Java虛擬機上運行的每個web應用都有一個ServletContext(上下文環境),這個web應用是一組Servlet的集合。ServletContext中定義了一組方法用於Servlet實例和Servlet容器來進行通信,比如獲取文件的MIME類型(比如text/html、image/jpeg、application/json等),調度請求和寫入日誌文件等。

​ 需要注意的是,在上段中我們講到了,每個Java虛擬機上運行的每個web應用都有一個ServletContext,因此,當應用是分佈式的情況下,比如我們將xxx項目部署在兩臺主機上形成了兩個應用,這兩個應用就會分別擁有一個獨立的ServletContext,這兩個應用之間的Serlvet是無法共享全局信息的(這也是爲何Session在分佈式的系統中無法直接共享的原因),這個時候就需要中間的一個服務來保證兩個分佈式的服務的通信,比如數據庫、MQ等。

​ 並且每個Servlet在創建的時候,web應用會在ServletConfig對象中初始化ServletContext的引用,也就是說我們可以通過ServletConfig獲取當前應用的ServletContext,並使用ServletContext來幫助我們完成某些操作。

​ 下面我們來講解ServletContext中常用的一些方法:

  1. getAttribute(String name):根據傳入的name返回Servlet容器屬性(通過下面的setAttribute方法設置的),如果沒有對應的屬性,則返回一個null;
  2. setAttribute(String name, Object object):在serlvet容器中將給定的object綁定到對應的屬性名name上(其形如key:value),如果serlvet容器中已經存在該name的屬性,則將其原始值覆蓋,如果傳入的object爲null,則和removeAttribute方法起到的效果一致(注意,這點和我們使用的Map不同);
  3. removeAttribute(String name):根據傳入的name將對應的屬性從Servlet容器中移除;
  4. getAttributeNames():以Enumeration<String>的形式返回對應Servlet容器中可用的屬性名稱(其中的name);
  5. getResourcePaths(String path):返回一個包含目錄列表的集合,如果web應用中所有資源的路徑都不是以path開頭,則返回一個空的集合。參數path是用與匹配資源的起始路徑,且需要以"/"開頭;
  6. getResource(String path):返回映射到給定路徑的資源的URL對象,path需要以"/"開頭;
  7. getResourceAsStream(String path):返回映射到給定路徑的資源的輸入流(InputStream),path需要以"/"開頭,此方法無法獲取到資源的類型和長度屬性;
  8. getRealPath(String path):返回給定虛擬路徑(相對於當前應用)的資源在主機上的真實路徑,當servlet容器無法將給定的虛擬路徑轉換爲真實路徑時,返回null;
  9. getRequestDispatcher(String path):返回一個RequestDispatcher對象,該對象可用於將請求轉發到資源(指定路徑)或將資源包括在響應中,path需要以"/"開頭;

​ 需要注意的是前三個方法中,如果ServletContext中的屬性發生了變化,如果有監聽器監聽ServletContext,Servlet容器會自動的通知對應的監聽器。對於getResourcePaths、getResource等方法中的path,皆要求以"/"開頭,說到這個,這裏需要講一下ServletContext這裏使用的路徑和我們在IDE中看到的區別,我們來通過兩張圖對比一下,首先看下Eclipse中項目的目錄結構:

資源分配圖

​ 然後我們再看下FirstProject項目在tomcat安裝目錄下的webapps目錄中的結構(爲了方便顯示層級關係,在命令行窗口來展示項目目錄),截圖如下:

資源分配圖

​ 對比兩張截圖,其中第一張圖的src(1標註)也就是存放Java代碼的目錄會轉成第二張圖的WEB-INF(1標註)下的classes(3標註),第一張圖中的index.jsp、welcom.html(3標註)會直接到根目錄下(2標註),和WEB-INF平級,如果有靜態資源放入在文件夾下(方便管理),也是一樣,不過相對的也要增加一級目錄。因此在Servlet中獲取資源文件是使用的"/“就會對應到"FirstProject/”(項目名,這裏和在瀏覽器上使用時不同,我們後面在討論)。

​ 因此我們在項目中想要獲取對應的資源文件,就需要使用圖二中的文件目錄結構,path開頭的"/",也就是直接定位到webapps/FirstProject下

使用實例

​ 我們通過例子,來簡單的介紹下ServletConfig、ServletContext中方法的使用。我們首先在web.xml中增加Web應用的初始化信息,配置如下:

<context-param>
  <param-name>BusinessName</param-name>
  <param-value>ZZXY</param-value>
</context-param>
<context-param>
  <param-name>CourseName</param-name>
  <param-value>JAVA WEB</param-value>
</context-param>

​ 我們將HelloServlet修改如下:

package com.zzxy.web.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.Set;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sun.org.apache.bcel.internal.generic.NEW;

/**
 * Servlet implementation class HelloServlet
 */
@WebServlet(
		description = "My First Servlet", 
		urlPatterns = { "/HelloServlet", "/StillMe" }, 
		initParams = { 
				@WebInitParam(name = "name", value = "lizishu"),
				@WebInitParam(name = "password", value = "123456")
		})
public class HelloServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
  
  //...
  
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//設置返回客戶端的contentType
		//text/plain :純文本格式  設置爲text/html println的換行會失效
		response.setContentType("text/plain;charset=utf-8");
		//response.setCharacterEncoding("utf-8"); 
		PrintWriter out = response.getWriter();
		//Servlet容器的上下文對應的相對的根目錄
		out.print("1.Servlet容器的上下文對應的相對的根目錄:");
		out.println("Served at: " + request.getContextPath());
		ServletConfig config = this.getServletConfig();
		//獲取Servlet的初始化參數
		out.print("2.獲取Servlet的初始化參數:");
		out.println("name: " + this.getInitParameter("name"));
		//獲取Servlet的<servlet-name>
		out.print("3.獲取Servlet的<servlet-name>:");
		out.println("訪問的Servle名爲:" + config.getServletName());
		
		// 得到包含所有初始化參數名的Enumeration對象
		Enumeration<String> paramNames = config.getInitParameterNames();
		//遍歷所有的初始化參數名,得到相應的參數值
		out.println("4.遍歷所有的初始化參數名,得到相應的參數值:");
		// 遍歷所有的初始化參數名,得到相應的參數值並打印
		while (paramNames.hasMoreElements()) {
			String name = paramNames.nextElement();
			String value = config.getInitParameter(name);
			out.println(name + ": " + value);
		}
		//得到ServletContext對象
		ServletContext context = this.getServletContext();
		//獲取應用程序的初始化參數(全局的,所有的Servlet可共享)
		out.print("5.獲取應用程序的初始化參數:");
		out.println("name's value: " + context.getInitParameter("BusinessName"));
		
		//一次獲取所有的應用程序的初始化參數的name
		Enumeration<String> attributeNames = context.getInitParameterNames();
		out.println("6.遍歷所有的應用程序的初始化參數:");
		while (attributeNames.hasMoreElements()) {
			String attributeName = attributeNames.nextElement();
			String value = context.getInitParameter(attributeName);
			out.println(attributeName + ": " + value);
		}
		//setAttribute,設置serlvet容器的屬性,在另一個Servlet實例中獲取
		context.setAttribute("setTime", new Date());
		//獲取Servlet容器的上下文對應的相對的根目錄下的文件和目錄的集合,也對應path開頭的"/"
		Set<String> pathSet = context.getResourcePaths("/");
		out.println("7.獲取Servlet容器的上下文對應的相對的根目錄下的文件和目錄的集合:");
		for(String path: pathSet) {
			out.println(path);
		}
		//getResource方法,URL對象中包含資源文件的許多屬性,比如文件類型、文件長度等
		out.println("8.獲取對應資源的URL對象:");
		URL url = context.getResource("/index.jsp");
		out.println(url.toString());
		//getRealPath 獲取對應資源虛擬路徑的真實路徑
		out.println("9.獲取對應資源虛擬路徑的真實路徑:");
		out.println(context.getRealPath("/index.jsp"));
		//getRequestDispatcher實現轉發
		//context.getRequestDispatcher("/AnswerServlet").forward(request, response);
	}
  
	//doPost()
}

​ 其對應的執行結果如下圖:

資源分配圖

​ 下面我們新建一個AnswerServlet來測試ServletContext中的getAttribute方法、使用getResourceAsStream來加載配置文件、getRequestDispatcher的跳轉,前期準備工作如下:

  1. 將HelloServlet中ddoGet方法的最後一行註釋刪掉,讓其可以進行跳轉;
  2. 創建AnswerServlet,配置選項全部默認即可;
  3. 在src下新建config.properties,src右擊–>new–>other–>General–>File,點擊next後輸入文件名;
  4. config.properties文件中增加兩行配置,配置內容如下:
BusinessName = Java Web learning
weapon = Work Hard

下面讓我們一起來看下AnswerServlet,代碼如下:

package com.zzxy.web.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class AnswerServlet
 */
@WebServlet("/AnswerServlet")
public class AnswerServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
  
  //...

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//設置返回客戶端的contentType
		//text/plain :純文本格式  設置爲text/html println的換行會失效
		response.setContentType("text/plain;charset=utf-8");
		//response.setCharacterEncoding("utf-8"); 
		PrintWriter out = response.getWriter();
		
		//獲取ServletContext,可以直接通過this指針
		//調用GenericServlet提供的直接獲取ServletContext的方法
		ServletContext context = this.getServletContext();
		
		out.println("獲取Servlet容器的屬性的值爲:" + context.getAttribute("setTime"));
		
		//獲取資源文件的輸入流InputStream對象
		InputStream inStream = context.getResourceAsStream("/WEB-INF/classes/config.properties");
		//創建一個Properties對象,用於加載配置
		Properties properties = new Properties();
		//從輸入流中讀取參數列表
		properties.load(inStream);
		out.println("從配置文件中讀取信的BusinessName:" + properties.getProperty("BusinessName"));
		out.println("從配置文件中讀取信的weapon:" + properties.getProperty("weapon"));
		//Properties對象同樣提供一個獲取所有屬性name的方法,propertyNames
		
    	//通過getResource獲取URL對象,獲取輸入流
    	URL url = context.getResource("/WEB-INF/classes/config.properties");
		properties.clear();
		properties.load(url.openStream());
		out.println("從配置文件中讀取信的BusinessName:" + properties.getProperty("BusinessName"));
		out.println("從配置文件中讀取信的weapon:" + properties.getProperty("weapon"));
	}

	//doPost

}

​ 在AnswerServlet中的doGet方法裏,我們獲取了在HelloServlet設置的setTime屬性,實現了應用內的Servlet共享(此方法用處不大,單應用的數據完全可以放在內存中來實現共享);還通過getResourceAsStream來獲取資源文件的輸入流,可以看到,使用的路徑爲我們在上面講的打包後運行在tomcat服務器上的路徑,也可以通過getResource方法獲取資源文件的URL對象,在獲取InputStream,可以達到同樣的效果。運行結果如下圖所示:

資源分配圖

​ 可以看到,在瀏覽器上輸入http://localhost:8080/FirstProject/HelloServlet,頁面上顯示的內容爲AnswerServlet輸出到屏幕的內容,這個就是RequestDispatcher對象的轉發功能了(後面再具體討論轉發和重定向)。

總結

​ 本文主要講了ServletConfig和ServletContext兩個接口的定義,首先ServletConfig是相對於每個Servlet實例的,是根據web.xml中的配置或者@WebServlet註解來生成的;ServletContext是相對於web 應用的,在一個web應用(一個虛擬機中運行的一個web應用)中是全局唯一的,ServletContext中封裝了許多功能強大的方法,其中比較重要的就是對資源文件的使用,通過Servlet容器可以方便的使用web應用中的資源,這也需要我們多練習,熟練掌握並能應用。

​ 還有一個比較重要的知識點就是Java web項目的打包路徑和IDE中的開發路徑的不同,大家要能熟練地掌握IDE中的路徑的"轉換",可以準確的通過path找到你所需要的資源。


​ 又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。

​ Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。

​ 有任何疑問,可以評論區留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章