Spring 工具類

来源:http://www.cnblogs.com/leo3689/archive/2016/02/04/5181558.html
-Advertisement-
Play Games

Spring 的優秀工具類盤點---轉 第 1 部分: 文件資源操作和 Web 相關工具類 http://www.ibm.com/developerworks/cn/java/j-lo-spring-utils1/ 文件資源操作 文件資源的操作是應用程式中常見的功能,如當上傳一個文件後將其保存在特定


主要功能:

  • 讀取文件(絕對路徑,相對於classpath:,相對於項目路徑,url地址),
  • 本地化資源(做國際化)
  • 拷貝文件,編輯文件
  • 讀取屬性文件
  • web工具
  • 防轉義
  • 數據檢查

 

Spring 的優秀工具類盤點---轉

第 1 部分: 文件資源操作和 Web 相關工具類

http://www.ibm.com/developerworks/cn/java/j-lo-spring-utils1/

文件資源操作

文件資源的操作是應用程式中常見的功能,如當上傳一個文件後將其保存在特定目錄下,從指定地址載入一個配置文件等等。我們一般使用 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 中聲明該監聽器就可以了。

第 2 部分: 特殊字元轉義和方法入參檢測工具類

http://www.ibm.com/developerworks/cn/java/j-lo-spring-utils2/

特殊字元轉義

由於 Web 應用程式需要聯合使用到多種語言,每種語言都包含一些特殊的字元,對於動態語言或標簽式的語言而言,如果需要動態構造語言的內容時,一個我們經常會碰到的問題就是特殊字元轉義的問題。下麵是 Web 開發者最常面對需要轉義的特殊字元類型:

  • HTML 特殊字元;
  • JavaScript 特殊字元;
  • SQL 特殊字元;

如果不對這些特殊字元進行轉義處理,則不但可能破壞文檔結構,還可以引發潛在的安全問題。Spring 為 HTML 和 JavaScript 特殊字元提供了轉義操作工具類,它們分別是 HtmlUtils 和 JavaScriptUtils。

HTML 特殊字元轉義

HTML 中 <,>,& 等字元有特殊含義,它們是 HTML 語言的保留字,因此不能直接使用。使用這些個字元時,應使用它們的轉義序列:

  • &:&amp;
  • " :&quot;
  • < :&lt;
  • > :&gt;

由於 HTML 網頁本身就是一個文本型結構化文檔,如果直接將這些包含了 HTML 特殊字元的內容輸出到網頁中,極有可能破壞整個 HTML 文檔的結構。所以,一般情況下需要對動態數據進行轉義處理,使用轉義序列表示 HTML 特殊字元。下麵的 JSP 網頁將一些變數動態輸出到 HTML 網頁中:

清單 1. 未進行 HTML 特殊字元轉義處理網頁
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%!
   String userName = "</td><tr></table>";
   String address = " \" type=\"button";
 %>
<table border="1">
   <tr>
     <td>姓名:</td><td><%=userName%></td> ①
   </tr>
   <tr>
     <td>年齡:</td><td>28</td>
   </tr>
</table>
 <input value="<%=address%>"  type="text" /> ②

在 ① 和 ② 處,我們未經任何轉義處理就直接將變數輸出到 HTML 網頁中,由於這些變數可能包含一些特殊的 HTML 的字元,它們將可能破壞整個 HTML 文檔的結構。我們可以從以上 JSP 頁面的一個具體輸出中瞭解這一問題:

<table border="1">
   <tr>
     <td>姓名:</td><td></td><tr></table></td> 
     ① 破壞了 <table> 的結構
   </tr>
   <tr>
     <td>年齡:</td><td>28</td>
   </tr>
</table>
 <input value=" " type="button"  type="text" /> 
 ② 將本來是輸入框組件偷梁換柱為按鈕組件

融合動態數據後的 HTML 網頁已經面目全非,首先 ① 處的 <table> 結構被包含 HTML 特殊字元的 userName 變數截斷了,造成其後的 <table> 代碼變成無效的內容;其次,② 處 <input> 被動態數據改換為按鈕類型的組件(type="button")。為了避免這一問題,我們需要事先對可能破壞 HTML 文檔結構的動態數據進行轉義處理。Spring 為我們提供了一個簡單適用的 HTML 特殊字元轉義工具類,它就是 HtmlUtils。下麵,我們通過一個簡單的例子瞭解 HtmlUtils 的具體用法:

清單 2. HtmpEscapeExample
package com.baobaotao.escape;
import org.springframework.web.util.HtmlUtils;
public class HtmpEscapeExample {
    public static void main(String[] args) {
        String specialStr = "<div id=\"testDiv\">test1;test2</div>";
        String str1 = HtmlUtils.htmlEscape(specialStr); ①轉換為HTML轉義字元表示
        System.out.println(str1);
       
        String str2 = HtmlUtils.htmlEscapeDecimal(specialStr); ②轉換為數據轉義表示
        System.out.println(str2);
       
        String str3 = HtmlUtils.htmlEscapeHex(specialStr); ③轉換為十六進位數據轉義表示
        System.out.println(str3);
       
        ④下麵對轉義後字元串進行反向操作
        System.out.println(HtmlUtils.htmlUnescape(str1));
        System.out.println(HtmlUtils.htmlUnescape(str2));
        System.out.println(HtmlUtils.htmlUnescape(str3));
    }
}

HTML 不但可以使用通用的轉義序列表示 HTML 特殊字元,還可以使用以 # 為首碼的數字序列表示 HTML 特殊字元,它們在最終的顯示效果上是一樣的。HtmlUtils 提供了三個轉義方法:

方法說明
static String htmlEscape(String input) 將 HTML 特殊字元轉義為 HTML 通用轉義序列;
static String htmlEscapeDecimal(String input) 將 HTML 特殊字元轉義為帶 # 的十進位數據轉義序列;
static String htmlEscapeHex(String input) 將 HTML 特殊字元轉義為帶 # 的十六進位數據轉義序列;

此外,HtmlUtils 還提供了一個能夠將經過轉義內容還原的方法:htmlUnescape(String input),它可以還原以上三種轉義序列的內容。運行以上代碼,您將可以看到以下的輸出:

str1:&lt;div id=&quot;testDiv&quot;&gt;test1;test2&lt;/div&gt;
str2:&#60;div id=&#34;testDiv&#34;&#62;test1;test2&#60;/div&#62;
str3:&#x3c;div id=&#x22;testDiv&#x22;&#x3e;test1;test2&#x3c;/div&#x3e;
<div id="testDiv">test1;test2</div>
<div id="testDiv">test1;test2</div>
<div id="testDiv">test1;test2</div>

您只要使用 HtmlUtils 對代碼 清單 1 的 userName 和 address 進行轉義處理,最終輸出的 HTML 頁面就不會遭受破壞了。

JavaScript 特殊字元轉義

JavaScript 中也有一些需要特殊處理的字元,如果直接將它們嵌入 JavaScript 代碼中,JavaScript 程式結構將會遭受破壞,甚至被嵌入一些惡意的程式。下麵列出了需要轉義的特殊 JavaScript 字元:

  • ' :\'
  • " :\"
  • \ :\\
  • 走紙換頁: \f
  • 換行:\n
  • 換欄符:\t
  • 回車:\r
  • 回退符:\b

我們通過一個具體例子演示動態變數是如何對 JavaScript 程式進行破壞的。假設我們有一個 JavaScript 數組變數,其元素值通過一個 Java List 對象提供,下麵是完成這一操作的 JSP 代碼片斷:

清單 3. jsTest.jsp:未對 JavaScript 特殊字元進行處理
<%@ page language="java" contentType="text/html; charset=utf-8"%>
<jsp:directive.page import="java.util.*"/>
<%
  List textList = new ArrayList();
  textList.add("\";alert();j=\"");
%>
<script>
  var txtList = new Array();
   <% for ( int i = 0 ; i < textList.size() ; i++) { %>
     txtList[<%=i%>] = "<%=textList.get(i)%>"; 
	 ① 未對可能包含特殊 JavaScript 字元的變數進行處理
   <% } %>
</script>

當客戶端調用這個 JSP 頁面後,將得到以下的 HTML 輸出頁面:

<script>
  var txtList = new Array();
   txtList[0] = "";alert();j=""; ① 本來是希望接受一個字元串,結果被植入了一段JavaScript代碼
</script>

由於包含 JavaScript 特殊字元的 Java 變數直接合併到 JavaScript 代碼中,我們本來期望 ① 處所示部分是一個普通的字元串,但結果變成了一段 JavaScript 代碼,網頁將彈出一個 alert 視窗。想像一下如果粗體部分的字元串是“";while(true)alert();j="”時會產生什麼後果呢?

因此,如果網頁中的 JavaScript 代碼需要通過拼接 Java 變數動態產生時,一般需要對變數的內容進行轉義處理,可以通過 Spring 的 JavaScriptUtils 完成這件工作。下麵,我們使用 JavaScriptUtils 對以上代碼進行改造:

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<jsp:directive.page import="java.util.*"/>
<jsp:directive.page import="org.springframework.web.util.JavaScriptUtils"/>
<%
  List textList = new ArrayList();
  textList.add("\";alert();j=\"");
%>
<script>
   var txtList = new Array();
   <% for ( int i = 0 ; i < textList.size() ; i++) { %>
   ① 在輸出動態內容前事先進行轉義處理
   txtList[<%=i%>] = "<%=JavaScriptUtils.javaScriptEscape(""+textList.get(i))%>";
   <% } %>
</script>

通過轉義處理後,這個 JSP 頁面輸出的結果網頁的 JavaScript 代碼就不會產生問題了:

<script>
   var txtList = new Array();
   txtList[0] = "\";alert();j=\"";
   ① 粗體部分僅是一個普通的字元串,而非一段 JavaScript 的語句了
</script>

SQL特殊字元轉義

應該說,您即使沒有處理 HTML 或 JavaScript 的特殊字元,也不會帶來災難性的後果,但是如果不在動態構造 SQL 語句時對變數中特殊字元進行處理,將可能導致程式漏洞、數據盜取、數據破壞等嚴重的安全問題。網路中有大量講解 SQL 註入的文章,感興趣的讀者可以搜索相關的資料深入研究。

雖然 SQL 註入的後果很嚴重,但是只要對動態構造的 SQL 語句的變數進行特殊字元轉義處理,就可以避免這一問題的發生了。來看一個存在安全漏洞的經典例子:

SELECT COUNT(userId) 
FROM t_user 
WHERE userName='"+userName+"' AND password ='"+password+"';

以上 SQL 語句根據返回的結果數判斷用戶提供的登錄信息是否正確,如果 userName 變數不經過特殊字元轉義處理就直接合併到 SQL 語句中,黑客就可以通過將 userName 設置為 “1' or '1'='1”繞過用戶名/密碼的檢查直接進入系統了。

所以除非必要,一般建議通過 PreparedStatement 參數綁定的方式構造動態 SQL 語句,因為這種方式可以避免 SQL 註入的潛在安全問題。但是往往很難在應用中完全避免通過拼接字元串構造動態 SQL 語句的方式。為了防止他人使用特殊 SQL 字元破壞 SQL 的語句結構或植入惡意操作,必須在變數拼接到 SQL 語句之前對其中的特殊字元進行轉義處理。Spring 並沒有提供相應的工具類,您可以通過 jakarta commons lang 通用類包中(spring/lib/jakarta-commons/commons-lang.jar)的 StringEscapeUtils 完成這一工作:

清單 4. SqlEscapeExample
package com.baobaotao.escape;
import org.apache.commons.lang.StringEscapeUtils;
public class SqlEscapeExample {
    public static void main(String[] args) {
        String userName = "1' or '1'='1";
        String password = "123456";
        userName = StringEscapeUtils.escapeSql(userName);
        password = StringEscapeUtils.escapeSql(password);
        String sql = "SELECT COUNT(userId) FROM t_user WHERE userName='"
            + userName + "' AND password ='" + password + "'";
        System.out.println(sql);
    }
}

事實上,StringEscapeUtils 不但提供了 SQL 特殊字元轉義處理的功能,還提供了 HTML、XML、JavaScript、Java 特殊字元的轉義和還原的方法。如果您不介意引入 jakarta commons lang 類包,我們更推薦您使用 StringEscapeUtils 工具類完成特殊字元轉義處理的工作。

 

方法入參檢測工具類

Web 應用在接受表單提交的數據後都需要對其進行合法性檢查,如果表單數據不合法,請求將被駁回。類似的,當我們在編寫類的方法時,也常常需要對方法入參進行合法性檢查,如果入參不符合要求,方法將通過拋出異常的方式拒絕後續處理。舉一個例子:有一個根據文件名獲取輸入流的方法:InputStream getData(String file),為了使方法能夠成功執行,必須保證 file 入參不能為 null 或空白字元,否則根本無須進行後繼的處理。這時方法的編寫者通常會在方法體的最前面編寫一段對入參進行檢測的代碼,如下所示:

public InputStream getData(String file) {
    if (file == null || file.length() == 0|| file.replaceAll("\\s", "").length() == 0) {
        throw new IllegalArgumentException("file入參不是有效的文件地址");
    }
…
}

類似以上檢測方法入參的代碼是非常常見,但是在每個方法中都使用手工編寫檢測邏輯的方式並不是一個好主意。閱讀 Spring 源碼,您會發現 Spring 採用一個 org.springframework.util.Assert 通用類完成這一任務。

Assert 翻譯為中文為“斷言”,使用過 JUnit 的讀者都熟知這個概念,它斷定某一個實際的運行值和預期想一樣,否則就拋出異常。Spring 對方法入參的檢測借用了這個概念,其提供的 Assert 類擁有眾多按規則對方法入參進行斷言的方法,可以滿足大部分方法入參檢測的要求。這些斷言方法在入參不滿足要求時就會拋出 IllegalArgumentException。下麵,我們來認識一下 Assert 類中的常用斷言方法:

斷言方法說明
notNull(Object object) 當 object 不為 null 時拋出異常,notNull(Object object, String message) 方法允許您通過 message 定製異常信息。和 notNull() 方法斷言規則相反的方法是 isNull(Object object)/isNull(Object object, String message),它要求入參一定是 null;
isTrue(boolean expression) / isTrue(boolean expression, String message) 當 expression 不為 true 拋出異常;
notEmpty(Collection collection) / notEmpty(Collection collection, String message) 當集合未包含元素時拋出異常。notEmpty(Map map) / notEmpty(Map map, String message) 和 notEmpty(Object[] array, String message) / notEmpty(Object[] array, String message) 分別對 Map 和 Object[] 類型的入參進行判斷;
hasLength(String text) / hasLength(String text, String message) 當 text 為 null 或長度為 0 時拋出異常;
hasText(String text) / hasText(String text, String message) text 不能為 null 且必須至少包含一個非空格的字元,否則拋出異常;
isInstanceOf(Class clazz, Object obj) / isInstanceOf(Class type, Object obj, String message) 如果 obj 不能被正確造型為 clazz 指定的類將拋出異常;
isAssignable(Class superType, Class subType) / isAssignable(Class superType, Class subType, String message) subType 必須可以按類型匹配於 superType,否則將拋出異常;

使用 Assert 斷言類可以簡化方法入參檢測的代碼,如 InputStream getData(String file) 在應用 Assert 斷言類後,其代碼可以簡化為以下的形式:

public InputStream getData(String file){
    Assert.hasText(file,"file入參不是有效的文件地址"); 
    ① 使用 Spring 斷言類進行方法入參檢測
…
}

可見使用 Spring 的 Assert 替代自編碼實現的入參檢測邏輯後,方法的簡潔性得到了不少的提高。Assert 不依賴於 Spring 容器,您可以大膽地在自己的應用中使用這個工具類。

 

小結

本文介紹了一些常用的 Spring 工具類,其中大部分 Spring 工具類不但可以在基於 Spring 的應用中使用,還可以在其它的應用中使用。

對於 Web 應用來說,由於有很多關聯的腳本代碼,如果這些代碼通過拼接字元串的方式動態產生,就需要對動態內容中特殊的字元進行轉義處理,否則就有可能產生意想不到的後果。Spring 為此提供了 HtmlUtils 和 JavaScriptUtils 工具類,只要將動態內容在拼接之前使用工具類進行轉義處理,就可以避免類似問題的發生了。如果您不介意引入一個第三方類包,那麼 jakarta commons lang 通用類包中的 StringEscapeUtils 工具類可能更加適合,因為它提供了更加全面的轉義功能。

最後我們還介紹了 Spring 的 Assert 工具類,Assert 工具類是通用性很強的工具類,它使用面向對象的方式解決方法入參檢測的問題,您可以在自己的應用中使用 Assert 對方法入參進行檢查。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Socket通道 上文講述了通道、文件通道,這篇文章來講述一下Socket通道,Socket通道與文件通道有著不一樣的特征,分三點說: 1、NIO的Socket通道類可以運行於非阻塞模式並且是可選擇的,這兩個性能可以激活大程式(如網路伺服器和中間件組件)巨大的可伸縮性和靈活性,因此,再也沒有為每個S
  • 集合: 1、作用: 保存多條數據。 嘮叨:同樣是保存數據,其保存的內容不限,長度不限。 2、集合間的相互關係: Collection--Set —HashSet --List—ArrayList —LinkedList Map—HashMap 集合在底層實現時:依然使用數組,但是性能優於數組。 一、
  • 1 #include<stdio.h> 2 #include<stdlib.h> 3 4 //typedef 80 MAXSIZE; 5 #define MAXSIZE 20 6 7 typedef struct Node{ 8 int data; 9 int cursor; 10 }Node,St
  • 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<malloc.h> 4 5 typedef int Elemtype; 6 7 typedef struct Node{ 8 int data; 9 struct Node* next; 10 }
  • 1 #include<stdio.h> 2 #include<stdlib.h> 3 4 typedef int Elemtype; 5 #define MAXSIZE 20 6 7 typedef struct List{ 8 Elemtype data[MAXSIZE]; 9 int lengt
  • 估計他們上完課就再也不玩了,自己那段時間上完課。也就基本上很少來了,主要是 沒有什麼記錄的習慣,奇怪的是,每每到了心情不好的時候,總會想要寫點什麼。 不管怎麼樣還是出去花錢學習了一下,這段經歷。嗯,很難評價,事實上如果不留下一筆,那麼的確沒有什麼學習的意義。 所以,李飛要一點兒一點兒扳過來。 出去學
  • 本文是一個jdk.locks系列主題的頭篇,總體介紹JDK中Lock底層框架以及JDK中藉助該框架實現的各種同步手段。瞭解JDK基本的併發與同步的實現,對java併發編程更得心應手!
  • 在AOP中有幾個概念: — 方面(Aspect):一個關註點的模塊化,這個關註點實現可能另外橫切多個對象。事務管理是J2EE應用中一個很好的橫切關註點例子。方面用Spring的Advisor或攔截器實現。 — 連接點(Joinpoint):程式執行過程中明確的點,如方法的調用或特定的異常被拋出。 —
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...