Spring 的優秀工具類盤點第 1 部分

weixin_30924079發表於2020-04-04

檔案資源操作

檔案資源的操作是應用程式中常見的功能,如當上傳一個檔案後將其儲存在特定目錄下,從指定地址載入一個配置檔案等等。我們一般使用 JDK 的 I/O 處理類完成這些操作,但對於一般的應用程式來說,JDK 的這些操作類所提供的方法過於底層,直接使用它們進行檔案操作不但程式編寫複雜而且容易產生錯誤。相比於 JDK 的 File,Spring 的 Resource 介面(資源概念的描述介面)抽象層面更高且涵蓋面更廣,Spring 提供了許多方便易用的資源操作工具類,它們大大降低資源操作的複雜度,同時具有更強的普適性。這些工具類不依賴於 Spring 容器,這意味著您可以在程式中象一般普通類一樣使用它們。

載入檔案資源

Spring 定義了一個 org.springframework.core.io.Resource 介面,Resource 介面是為了統一各種型別不同的資源而定義的,Spring 提供了若干 Resource 介面的實現類,這些實現類可以輕鬆地載入不同型別的底層資源,並提供了獲取檔名、URL 地址以及資源內容的操作方法。

訪問檔案資源

假設有一個檔案地位於 Web 應用的類路徑下,您可以通過以下方式對這個檔案資源進行訪問:

  • 通過 FileSystemResource 以檔案系統絕對路徑的方式進行訪問;
  • 通過 ClassPathResource 以類路徑的方式進行訪問;
  • 通過 ServletContextResource 以相對於 Web 應用根目錄的方式進行訪問。

相比於通過 JDK 的 File 類訪問檔案資源的方式,Spring 的 Resource 實現類無疑提供了更加靈活的操作方式,您可以根據情況選擇適合的 Resource 實現類訪問資源。下面,我們分別通過 FileSystemResource 和 ClassPathResource 訪問同一個檔案資源:

清單 1. FileSourceExample
 package com.baobaotao.io; 
 import java.io.IOException; 
 import java.io.InputStream; 
 import org.springframework.core.io.ClassPathResource; 
 import org.springframework.core.io.FileSystemResource; 
 import org.springframework.core.io.Resource; 
 public class FileSourceExample { 
    public static void main(String[] args) { 
        try { 
            String filePath = 
            "D:/masterSpring/chapter23/webapp/WEB-INF/classes/conf/file1.txt"; 
            // ① 使用系統檔案路徑方式載入檔案
            Resource res1 = new FileSystemResource(filePath); 
            // ② 使用類路徑方式載入檔案
            Resource res2 = new ClassPathResource("conf/file1.txt"); 
            InputStream ins1 = res1.getInputStream(); 
            InputStream ins2 = res2.getInputStream(); 
            System.out.println("res1:"+res1.getFilename()); 
            System.out.println("res2:"+res2.getFilename()); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 }

在獲取資源後,您就可以通過 Resource 介面定義的多個方法訪問檔案的資料和其它的資訊:如您可以通過 getFileName() 獲取檔名,通過 getFile() 獲取資源對應的 File 物件,通過 getInputStream() 直接獲取檔案的輸入流。此外,您還可以通過 createRelative(String relativePath) 在資源相對地址上建立新的資源。

在 Web 應用中,您還可以通過 ServletContextResource 以相對於 Web 應用根目錄的方式訪問檔案資源,如下所示:

 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> 
 <jsp:directive.page import="
    org.springframework.web.context.support.ServletContextResource"/> 
 <jsp:directive.page import="org.springframework.core.io.Resource"/> 
 <% 
    // ① 注意檔案資源地址以相對於 Web 應用根路徑的方式表示
    Resource res3 = new ServletContextResource(application, 
        "/WEB-INF/classes/conf/file1.txt"); 
    out.print(res3.getFilename()); 
 %>

對於位於遠端伺服器(Web 伺服器或 FTP 伺服器)的檔案資源,您則可以方便地通過 UrlResource 進行訪問。

為了方便訪問不同型別的資源,您必須使用相應的 Resource 實現類,是否可以在不顯式使用 Resource 實現類的情況下,僅根據帶特殊字首的資源地址直接載入檔案資源呢? Spring 提供了一個 ResourceUtils 工具類,它支援“classpath:”和“file:”的地址字首,它能夠從指定的地址載入檔案資源,請看下面的例子:

清單 2. ResourceUtilsExample
 package com.baobaotao.io; 
 import java.io.File; 
 import org.springframework.util.ResourceUtils; 
 public class ResourceUtilsExample { 
    public static void main(String[] args) throws Throwable{ 
        File clsFile = ResourceUtils.getFile("classpath:conf/file1.txt"); 
        System.out.println(clsFile.isFile()); 

        String httpFilePath = "file:D:/masterSpring/chapter23/src/conf/file1.txt"; 
        File httpFile = ResourceUtils.getFile(httpFilePath); 
        System.out.println(httpFile.isFile());        
    } 
 }

ResourceUtils 的 getFile(String resourceLocation) 方法支援帶特殊字首的資源地址,這樣,我們就可以在不和 Resource 實現類打交道的情況下使用 Spring 檔案資源載入的功能了。

本地化檔案資源

本地化檔案資源是一組通過本地化標識名進行特殊命名的檔案,Spring 提供的 LocalizedResourceHelper 允許通過檔案資源基名和本地化實體獲取匹配的本地化檔案資源並以 Resource 物件返回。假設在類路徑的 i18n 目錄下,擁有一組基名為 message 的本地化檔案資源,我們通過以下例項演示獲取對應中國大陸和美國的本地化檔案資源:

清單 3. LocaleResourceTest
 package com.baobaotao.io; 
 import java.util.Locale; 
 import org.springframework.core.io.Resource; 
 import org.springframework.core.io.support.LocalizedResourceHelper; 
 public class LocaleResourceTest { 
    public static void main(String[] args) { 
        LocalizedResourceHelper lrHalper = new LocalizedResourceHelper(); 
        // ① 獲取對應美國的本地化檔案資源
        Resource msg_us = lrHalper.findLocalizedResource("i18n/message", ".properties", 
        Locale.US); 
        // ② 獲取對應中國大陸的本地化檔案資源
        Resource msg_cn = lrHalper.findLocalizedResource("i18n/message", ".properties", 
        Locale.CHINA); 
        System.out.println("fileName(us):"+msg_us.getFilename()); 
        System.out.println("fileName(cn):"+msg_cn.getFilename()); 
    } 
 }

雖然 JDK 的 java.util.ResourceBundle 類也可以通過相似的方式獲取本地化檔案資源,但是其返回的是 ResourceBundle 型別的物件。如果您決定統一使用 Spring 的 Resource 接表徵檔案資源,那麼 LocalizedResourceHelper 就是獲取檔案資源的非常適合的幫助類了。

檔案操作

在使用各種 Resource 介面的實現類載入檔案資源後,經常需要對檔案資源進行讀取、拷貝、轉存等不同型別的操作。您可以通過 Resource 介面所提供了方法完成這些功能,不過在大多數情況下,通過 Spring 為 Resource 所配備的工具類完成檔案資源的操作將更加方便。

檔案內容拷貝

第一個我們要認識的是 FileCopyUtils,它提供了許多一步式的靜態操作方法,能夠將檔案內容拷貝到一個目標 byte[]、String 甚至一個輸出流或輸出檔案中。下面的例項展示了 FileCopyUtils 具體使用方法:

清單 4. FileCopyUtilsExample
 package com.baobaotao.io; 
 import java.io.ByteArrayOutputStream; 
 import java.io.File; 
 import java.io.FileReader; 
 import java.io.OutputStream; 
 import org.springframework.core.io.ClassPathResource; 
 import org.springframework.core.io.Resource; 
 import org.springframework.util.FileCopyUtils; 
 public class FileCopyUtilsExample { 
    public static void main(String[] args) throws Throwable { 
        Resource res = new ClassPathResource("conf/file1.txt"); 
        // ① 將檔案內容拷貝到一個 byte[] 中
        byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile()); 
        // ② 將檔案內容拷貝到一個 String 中
        String fileStr = FileCopyUtils.copyToString(new FileReader(res.getFile())); 
        // ③ 將檔案內容拷貝到另一個目標檔案
        FileCopyUtils.copy(res.getFile(), 
        new File(res.getFile().getParent()+ "/file2.txt")); 

        // ④ 將檔案內容拷貝到一個輸出流中
        OutputStream os = new ByteArrayOutputStream(); 
        FileCopyUtils.copy(res.getInputStream(), os); 
    } 
 }

往往我們都通過直接操作 InputStream 讀取檔案的內容,但是流操作的程式碼是比較底層的,程式碼的物件導向性並不強。通過 FileCopyUtils 讀取和拷貝檔案內容易於操作且相當直觀。如在 ① 處,我們通過 FileCopyUtils 的 copyToByteArray(File in) 方法就可以直接將檔案內容讀到一個 byte[] 中;另一個可用的方法是 copyToByteArray(InputStream in),它將輸入流讀取到一個 byte[] 中。

如果是文字檔案,您可能希望將檔案內容讀取到 String 中,此時您可以使用 copyToString(Reader in) 方法,如 ② 所示。使用 FileReader 對 File 進行封裝,或使用 InputStreamReader 對 InputStream 進行封裝就可以了。

FileCopyUtils 還提供了多個將檔案內容拷貝到各種目標物件中的方法,這些方法包括:

方法說明
static void copy(byte[] in, File out) 將 byte[] 拷貝到一個檔案中
static void copy(byte[] in, OutputStream out) 將 byte[] 拷貝到一個輸出流中
static int copy(File in, File out) 將檔案拷貝到另一個檔案中
static int copy(InputStream in, OutputStream out) 將輸入流拷貝到輸出流中
static int copy(Reader in, Writer out) 將 Reader 讀取的內容拷貝到 Writer 指向目標輸出中
static void copy(String in, Writer out) 將字串拷貝到一個 Writer 指向的目標中

在例項中,我們雖然使用 Resource 載入檔案資源,但 FileCopyUtils 本身和 Resource 沒有任何關係,您完全可以在基於 JDK I/O API 的程式中使用這個工具類。

屬性檔案操作

我們知道可以通過 java.util.Properties 的 load(InputStream inStream) 方法從一個輸入流中載入屬性資源。Spring 提供的 PropertiesLoaderUtils 允許您直接通過基於類路徑的檔案地址載入屬性資源,請看下面的例子:

 package com.baobaotao.io; 
 import java.util.Properties; 
 import org.springframework.core.io.support.PropertiesLoaderUtils; 
 public class PropertiesLoaderUtilsExample { 
    public static void main(String[] args) throws Throwable {    
        // ① jdbc.properties 是位於類路徑下的檔案
        Properties props = PropertiesLoaderUtils.loadAllProperties("jdbc.properties"); 
        System.out.println(props.getProperty("jdbc.driverClassName")); 
    } 
 }

一般情況下,應用程式的屬性檔案都放置在類路徑下,所以 PropertiesLoaderUtils 比之於 Properties#load(InputStream inStream) 方法顯然具有更強的實用性。此外,PropertiesLoaderUtils 還可以直接從 Resource 物件中載入屬性資源:

方法說明
static Properties loadProperties(Resource resource) 從 Resource 中載入屬性
static void fillProperties(Properties props, Resource resource) 將 Resource 中的屬性資料新增到一個已經存在的 Properties 物件中

特殊編碼的資源

當您使用 Resource 實現類載入檔案資源時,它預設採用作業系統的編碼格式。如果檔案資源採用了特殊的編碼格式(如 UTF-8),則在讀取資源內容時必須事先通過 EncodedResource 指定編碼格式,否則將會產生中文亂碼的問題。

清單 5. EncodedResourceExample
 package com.baobaotao.io; 
 import org.springframework.core.io.ClassPathResource; 
 import org.springframework.core.io.Resource; 
 import org.springframework.core.io.support.EncodedResource; 
 import org.springframework.util.FileCopyUtils; 
 public class EncodedResourceExample { 
        public static void main(String[] args) throws Throwable  { 
            Resource res = new ClassPathResource("conf/file1.txt"); 
            // ① 指定檔案資源對應的編碼格式(UTF-8)
            EncodedResource encRes = new EncodedResource(res,"UTF-8"); 
            // ② 這樣才能正確讀取檔案的內容,而不會出現亂碼
            String content  = FileCopyUtils.copyToString(encRes.getReader()); 
            System.out.println(content);  
    } 
 }

EncodedResource 擁有一個 getResource() 方法獲取 Resource,但該方法返回的是通過建構函式傳入的原 Resource 物件,所以必須通過 EncodedResource#getReader() 獲取應用編碼後的 Reader 物件,然後再通過該 Reader 讀取檔案的內容。

 

Web 相關工具類

您幾乎總是使用 Spring 框架開發 Web 的應用,Spring 為 Web 應用提供了很多有用的工具類,這些工具類可以給您的程式開發帶來很多便利。在這節裡,我們將逐一介紹這些工具類的使用方法。

操作 Servlet API 的工具類

當您在控制器、JSP 頁面中想直接訪問 Spring 容器時,您必須事先獲取 WebApplicationContext 物件。Spring 容器在啟動時將 WebApplicationContext 儲存在 ServletContext 的屬性列表中,通過 WebApplicationContextUtils 工具類可以方便地獲取 WebApplicationContext 物件。

WebApplicationContextUtils

當 Web 應用整合 Spring 容器後,代表 Spring 容器的 WebApplicationContext 物件將以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 為鍵存放在 ServletContext 屬性列表中。您當然可以直接通過以下語句獲取 WebApplicationContext:

 WebApplicationContext wac = (WebApplicationContext)servletContext. 
 getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

但通過位於 org.springframework.web.context.support 包中的 WebApplicationContextUtils 工具類獲取 WebApplicationContext 更方便:

 WebApplicationContext wac =WebApplicationContextUtils. 
 getWebApplicationContext(servletContext);

當 ServletContext 屬性列表中不存在 WebApplicationContext 時,getWebApplicationContext() 方法不會丟擲異常,它簡單地返回 null。如果後續程式碼直接訪問返回的結果將引發一個 NullPointerException 異常,而 WebApplicationContextUtils 另一個 getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 屬性列表中一定要包含一個有效的 WebApplicationContext 物件,否則馬上丟擲一個 IllegalStateException 異常。我們推薦使用後者,因為它能提前發現錯誤的時間,強制開發者搭建好必備的基礎設施。

WebUtils

位於 org.springframework.web.util 包中的 WebUtils 是一個非常好用的工具類,它對很多 Servlet API 提供了易用的代理方法,降低了訪問 Servlet API 的複雜度,可以將其看成是常用 Servlet API 方法的門面類。

下面這些方法為訪問 HttpServletRequest 和 HttpSession 中的物件和屬性帶來了方便:

方法說明
Cookie getCookie(HttpServletRequest request, String name) 獲取 HttpServletRequest 中特定名字的 Cookie 物件。如果您需要建立 Cookie, Spring 也提供了一個方便的 CookieGenerator 工具類;
Object getSessionAttribute(HttpServletRequest request, String name) 獲取 HttpSession 特定屬性名的物件,否則您必須通過 request.getHttpSession.getAttribute(name) 完成相同的操作;
Object getRequiredSessionAttribute(HttpServletRequest request, String name) 和上一個方法類似,只不過強制要求 HttpSession 中擁有指定的屬性,否則丟擲異常;
String getSessionId(HttpServletRequest request) 獲取 Session ID 的值;
void exposeRequestAttributes(ServletRequest request, Map attributes) 將 Map 元素新增到 ServletRequest 的屬性列表中,當請求被導向(forward)到下一個處理程式時,這些請求屬性就可以被訪問到了;

此外,WebUtils 還提供了一些和 ServletContext 相關的方便方法:

方法說明
String getRealPath(ServletContext servletContext, String path) 獲取相對路徑對應檔案系統的真實檔案路徑;
File getTempDir(ServletContext servletContext) 獲取 ServletContex 對應的臨時檔案地址,它以 File 物件的形式返回。

下面的片斷演示了使用 WebUtils 從 HttpSession 中獲取屬性物件的操作:

 protected Object formBackingObject(HttpServletRequest request) throws Exception { 
    UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request, 
        "userSession"); 
    if (userSession != null) { 
        return new AccountForm(this.petStore.getAccount( 
        userSession.getAccount().getUsername())); 
    } else { 
        return new AccountForm(); 
    } 
 }

Spring 所提供的過濾器和監聽器

Spring 為 Web 應用提供了幾個過濾器和監聽器,在適合的時間使用它們,可以解決一些常見的 Web 應用問題。

延遲載入過濾器

Hibernate 允許對關聯物件、屬性進行延遲載入,但是必須保證延遲載入的操作限於同一個 Hibernate Session 範圍之內進行。如果 Service 層返回一個啟用了延遲載入功能的領域物件給 Web 層,當 Web 層訪問到那些需要延遲載入的資料時,由於載入領域物件的 Hibernate Session 已經關閉,這些導致延遲載入資料的訪問異常。

Spring 為此專門提供了一個 OpenSessionInViewFilter 過濾器,它的主要功能是使每個請求過程繫結一個 Hibernate Session,即使最初的事務已經完成了,也可以在 Web 層進行延遲載入的操作。

OpenSessionInViewFilter 過濾器將 Hibernate Session 繫結到請求執行緒中,它將自動被 Spring 的事務管理器探測到。所以 OpenSessionInViewFilter 適用於 Service 層使用 HibernateTransactionManager 或 JtaTransactionManager 進行事務管理的環境,也可以用於非事務只讀的資料操作中。

要啟用這個過濾器,必須在 web.xml 中對此進行配置:

…
 <filter> 
    <filter-name>hibernateFilter</filter-name> 
    <filter-class> 
    org.springframework.orm.hibernate3.support.OpenSessionInViewFilter 
    </filter-class> 
 </filter> 
 <filter-mapping> 
    <filter-name>hibernateFilter</filter-name> 
    <url-pattern>*.html</url-pattern> 
 </filter-mapping> 
…

上面的配置,我們假設使用 .html 的字尾作為 Web 框架的 URL 匹配模式,如果您使用 Struts 等 Web 框架,可以將其改為對應的“*.do”模型。

中文亂碼過濾器

在您通過表單向伺服器提交資料時,一個經典的問題就是中文亂碼問題。雖然我們所有的 JSP 檔案和頁面編碼格式都採用 UTF-8,但這個問題還是會出現。解決的辦法很簡單,我們只需要在 web.xml 中配置一個 Spring 的編碼轉換過濾器就可以了:

 <web-app> 
 <!---listener 的配置 --> 
 <filter> 
    <filter-name>encodingFilter</filter-name> 
    <filter-class> 
        org.springframework.web.filter.CharacterEncodingFilter ① Spring 編輯過濾器
    </filter-class> 
    <init-param> ② 編碼方式
        <param-name>encoding</param-name> 
        <param-value>UTF-8</param-value> 
    </init-param> 
    <init-param> ③ 強制進行編碼轉換
        <param-name>forceEncoding</param-name> 
        <param-value>true</param-value> 
    </init-param> 
    </filter> 
    <filter-mapping> ② 過濾器的匹配 URL 
        <filter-name>encodingFilter</filter-name> 
        <url-pattern>*.html</url-pattern> 
    </filter-mapping> 

 <!---servlet 的配置 --> 
 </web-app>

這樣所有以 .html 為字尾的 URL 請求的資料都會被轉碼為 UTF-8 編碼格式,表單中文亂碼的問題就可以解決了。

請求跟蹤日誌過濾器

除了以上兩個常用的過濾器外,還有兩個在程式除錯時可能會用到的請求日誌跟蹤過濾器,它們會將請求的一些重要資訊記錄到日誌中,方便程式的除錯。這兩個日誌過濾器只有在日誌級別為 DEBUG 時才會起作用:

方法說明
org.springframework.web.filter.ServletContextRequestLoggingFilter 該過濾器將請求的 URI 記錄到 Common 日誌中(如通過 Log4J 指定的日誌檔案);
org.springframework.web.filter.ServletContextRequestLoggingFilter 該過濾器將請求的 URI 記錄到 ServletContext 日誌中。

以下是日誌過濾器記錄的請求跟蹤日誌的片斷:

(JspServlet.java:224) - JspEngine --> /htmlTest.jsp 
(JspServlet.java:225) - ServletPath: /htmlTest.jsp 
(JspServlet.java:226) - PathInfo: null 
(JspServlet.java:227) - RealPath: D:\masterSpring\chapter23\webapp\htmlTest.jsp 
(JspServlet.java:228) - RequestURI: /baobaotao/htmlTest.jsp 
…

通過這個請求跟蹤日誌,程度除錯者可以詳細地檢視到有哪些請求被呼叫,請求的引數是什麼,請求是否正確返回等資訊。雖然這兩個請求跟蹤日誌過濾器一般在程式除錯時使用,但是即使程式部署不將其從 web.xml 中移除也不會有大礙,因為只要將日誌級別設定為 DEBUG 以上級別,它們就不會輸出請求跟蹤日誌資訊了。

轉存 Web 應用根目錄監聽器和 Log4J 監聽器

Spring 在 org.springframework.web.util 包中提供了幾個特殊用途的 Servlet 監聽器,正確地使用它們可以完成一些特定需求的功能。比如某些第三方工具支援通過 ${key} 的方式引用系統引數(即可以通過 System.getProperty() 獲取的屬性),WebAppRootListener 可以將 Web 應用根目錄新增到系統引數中,對應的屬性名可以通過名為“webAppRootKey”的 Servlet 上下文引數指定,預設為“webapp.root”。下面是該監聽器的具體的配置:

清單 6. WebAppRootListener 監聽器配置
…
 <context-param> 
    <param-name>webAppRootKey</param-name> 
    <param-value>baobaotao.root</param-value> ① Web 應用根目錄以該屬性名新增到系統引數中
 </context-param> 
…
② 負責將 Web 應用根目錄以 webAppRootKey 上下文引數指定的屬性名新增到系統引數中
 <listener> 
    <listener-class> 
    org.springframework.web.util.WebAppRootListener 
    </listener-class> 
 </listener> 
…

這樣,您就可以在程式中通過 System.getProperty("baobaotao.root") 獲取 Web 應用的根目錄了。不過更常見的使用場景是在第三方工具的配置檔案中通過 ${baobaotao.root} 引用 Web 應用的根目錄。比如以下的 log4j.properties 配置檔案就通過 ${baobaotao.root} 設定了日誌檔案的地址:

 log4j.rootLogger=INFO,R 
 log4j.appender.R=org.apache.log4j.RollingFileAppender 
 log4j.appender.R.File=${baobaotao.root}/WEB-INF/logs/log4j.log ① 指定日誌檔案的地址
 log4j.appender.R.MaxFileSize=100KB 
 log4j.appender.R.MaxBackupIndex=1 
 log4j.appender.R.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n

另一個專門用於 Log4J 的監聽器是 Log4jConfigListener。一般情況下,您必須將 Log4J 日誌配置檔案以 log4j.properties 為檔名並儲存在類路徑下。Log4jConfigListener 允許您通過 log4jConfigLocation Servlet 上下文引數顯式指定 Log4J 配置檔案的地址,如下所示:

① 指定 Log4J 配置檔案的地址
 <context-param> 
    <param-name>log4jConfigLocation</param-name> 
    <param-value>/WEB-INF/log4j.properties</param-value> 
 </context-param> 
…
② 使用該監聽器初始化 Log4J 日誌引擎
 <listener> 
    <listener-class> 
    org.springframework.web.util.Log4jConfigListener 
    </listener-class> 
 </listener> 
…

提示

一些 Web 應用伺服器(如 Tomcat)不會為不同的 Web 應用使用獨立的系統引數,也就是說,應用伺服器上所有的 Web 應用都共享同一個系統引數物件。這時,您必須通過 webAppRootKey 上下文引數為不同 Web 應用指定不同的屬性名:如第一個 Web 應用使用 webapp1.root 而第二個 Web 應用使用 webapp2.root 等,這樣才不會發生後者覆蓋前者的問題。此外,WebAppRootListener 和 Log4jConfigListener 都只能應用在 Web 應用部署後 WAR 檔案會解包的 Web 應用伺服器上。一些 Web 應用伺服器不會將 Web 應用的 WAR 檔案解包,整個 Web 應用以一個 WAR 包的方式存在(如 Weblogic),此時因為無法指定對應檔案系統的 Web 應用根目錄,使用這兩個監聽器將會發生問題。

Log4jConfigListener 監聽器包括了 WebAppRootListener 的功能,也就是說,Log4jConfigListener 會自動完成將 Web 應用根目錄以 webAppRootKey 上下文引數指定的屬性名新增到系統引數中,所以當您使用 Log4jConfigListener 後,就沒有必須再使用 WebAppRootListener 了。

Introspector 快取清除監聽器

Spring 還提供了一個名為 org.springframework.web.util.IntrospectorCleanupListener 的監聽器。它主要負責處理由 JavaBean Introspector 功能而引起的快取洩露。IntrospectorCleanupListener 監聽器在 Web 應用關閉的時會負責清除 JavaBean Introspector 的快取,在 web.xml 中註冊這個監聽器可以保證在 Web 應用關閉的時候釋放與其相關的 ClassLoader 的快取和類引用。如果您使用了 JavaBean Introspector 分析應用中的類,Introspector 快取會保留這些類的引用,結果在應用關閉的時候,這些類以及 Web 應用相關的 ClassLoader 不能被垃圾回收。不幸的是,清除 Introspector 的唯一方式是重新整理整個快取,這是因為沒法準確判斷哪些是屬於本 Web 應用的引用物件,哪些是屬於其它 Web 應用的引用物件。所以刪除被快取的 Introspection 會導致將整個 JVM 所有應用的 Introspection 都刪掉。需要注意的是,Spring 託管的 Bean 不需要使用這個監聽器,因為 Spring 的 Introspection 所使用的快取在分析完一個類之後會馬上從 javaBean Introspector 快取中清除掉,並將快取儲存在應用程式特定的 ClassLoader 中,所以它們一般不會導致記憶體資源洩露。但是一些類庫和框架往往會產生這個問題。例如 Struts 和 Quartz 的 Introspector 的記憶體洩漏會導致整個的 Web 應用的 ClassLoader 不能進行垃圾回收。在 Web 應用關閉之後,您還會看到此應用的所有靜態類引用,這個錯誤當然不是由這個類自身引起的。解決這個問題的方法很簡單,您僅需在 web.xml 中配置 IntrospectorCleanupListener 監聽器就可以了:

 <listener> 
    <listener-class> 
    org.springframework.web.util.IntrospectorCleanupListener 
    </listener-class> 
 </listener>
 

小結

本文介紹了一些常用的 Spring 工具類,其中大部分 Spring 工具類不但可以在基於 Spring 的應用中使用,還可以在其它的應用中使用。使用 JDK 的檔案操作類在訪問類路徑相關、Web 上下文相關的檔案資源時,往往顯得拖泥帶水、拐彎抹角,Spring 的 Resource 實現類使這些工作變得輕鬆了許多。

在 Web 應用中,有時你希望直接訪問 Spring 容器,獲取容器中的 Bean,這時使用 WebApplicationContextUtils 工具類從 ServletContext 中獲取 WebApplicationContext 是非常方便的。WebUtils 為訪問 Servlet API 提供了一套便捷的代理方法,您可以通過 WebUtils 更好的訪問 HttpSession 或 ServletContext 的資訊。

Spring 提供了幾個 Servlet 過濾器和監聽器,其中 ServletContextRequestLoggingFilter 和 ServletContextRequestLoggingFilter 可以記錄請求訪問的跟蹤日誌,你可以在程式除錯時使用它們獲取請求呼叫的詳細資訊。WebAppRootListener 可以將 Web 應用的根目錄以特定屬性名新增到系統引數中,以便第三方工具類通過 ${key} 的方式進行訪問。Log4jConfigListener 允許你指定 Log4J 日誌配置檔案的地址,且可以在配置檔案中通過 ${key} 的方式引用 Web 應用根目錄,如果你需要在 Web 應用相關的目錄建立日誌檔案,使用 Log4jConfigListener 可以很容易地達到這一目標。

Web 應用的記憶體洩漏是最讓開發者頭疼的問題,雖然不正確的程式編寫可能是這一問題的根源,也有可能是一些第三方框架的 JavaBean Introspector 快取得不到清除而導致的,Spring 專門為解決這一問題配備了 IntrospectorCleanupListener 監聽器,它只要簡單在 web.xml 中宣告該監聽器就可以了。

轉載於:https://www.cnblogs.com/koal/p/5161777.html

相關文章