如何在 Java 中使用外部庫
外部庫填補了 Java 核心庫中的一些功能空白。
Java 自帶有一組核心庫,其中包含了定義常用數(shù)據(jù)類型和相關(guān)行為的庫(例如 String 和 Date)、與主機操作系統(tǒng)交互的實用程序(例如 System 和 File),以及一些用來管理安全性、處理網(wǎng)絡(luò)通信、創(chuàng)建或解析 XML的有用的子系統(tǒng)。鑒于核心庫的豐富性,程序員通常很容易在其中找到有用的組件,以減少需要編寫的代碼量。
即便如此,核心庫仍有一些功能上的不足,因此發(fā)現(xiàn)這些不足的程序員們還額外創(chuàng)建了很多有趣的 Java 庫。例如,Apache Commons“是一個專注于可重用 Java 組件所有方面的 Apache 項目”,提供了大約 43 個開源庫的集合(截至撰寫本文時),涵蓋了 Java 核心庫之外的一系列功能 (例如 geometry 或 statistics),并增強或替換了 Java 核心庫中的原有功能(例如 math 或 numbers)。
另一種常見的 Java 庫類型是系統(tǒng)組件的接口(例如數(shù)據(jù)庫系統(tǒng)接口),本文會著眼于使用此類接口連接到 PostgreSQL 數(shù)據(jù)庫,并得到一些有趣的信息。首先,我們來回顧一下庫的重要部分。
什么是庫?
庫library里自然包含的是一些有用的代碼。但為了發(fā)揮用處,代碼需要以特定方式進(jìn)行組織,特定的方式使 Java 程序員可以訪問其中組件來解決手頭問題。
可以說,一個庫最重要的部分是它的應(yīng)用程序編程接口(API)文檔。這種文檔很多人都熟悉,通常是由 Javadoc 生成的。Javadoc 讀取代碼中的結(jié)構(gòu)化注釋并以 HTML 格式輸出文檔,通常 API 的 包package 在頁面左上角的面板中顯示,類class 在左下角顯示,同時右側(cè)會有庫、包或類級別的詳細(xì)文檔(具體取決于在主面板中選擇的內(nèi)容)。例如,Apache Commons Math 的頂級 API 文檔 如下所示:
單擊主面板中的包會顯示該包中定義的 Java 類和接口。例如,org.apache.commons.math4.analysis.solvers 顯示了諸如 BisectionSolver 這樣的類,該類用于使用二分算法查找單變量實函數(shù)的零點。單擊 BisectionSolver 鏈接會列出 BisectionSolver 類的所有方法。
這類文檔可用作參考文檔,不適合作為學(xué)習(xí)如何使用庫的教程。比如,如果你知道什么是單變量實函數(shù)并查看包 org.apache.commons.math4.analysis.function,就可以試著使用該包來組合函數(shù)定義,然后使用 org.apache.commons.math4.analysis.solvers 包來查找剛剛創(chuàng)建的函數(shù)的零點。但如果你不知道,就可能需要更多學(xué)習(xí)向的文檔,也許甚至是一個實際例子,來讀懂參考文檔。
這種文檔結(jié)構(gòu)還有助于闡明 包package(相關(guān) Java 類和接口定義的集合)的含義,并顯示特定庫中捆綁了哪些包。
這種庫的代碼通常是在 .jar 文件 中,它基本上是由 Java 的 jar 命令創(chuàng)建的 .zip 文件,其中還包含一些其他有用的信息。.jar 文件通常被創(chuàng)建為構(gòu)建過程的端點,該構(gòu)建過程編譯了所定義包中的所有 .java 文件。
要訪問外部庫提供的功能,有兩個主要步驟:
- 確保通過類路徑(或者命令行中的 -cp 參數(shù)或者 CLASSPATH 環(huán)境變量),庫可用于 Java 編譯步驟(javac)和執(zhí)行步驟(java)。
- 使用恰當(dāng)?shù)?import 語句訪問程序源代碼中的包和類。
其余的步驟就與使用 String 等 Java核心類相同,使用庫提供的類和接口定義來編寫代碼。很簡單對吧?不過也沒那么簡單。首先,你需要了解庫組件的預(yù)期使用模式,然后才能編寫代碼。
示例:連接 PostgreSQL 數(shù)據(jù)庫
在數(shù)據(jù)庫系統(tǒng)中訪問數(shù)據(jù)的典型使用步驟是:
- 訪問正在使用的特定數(shù)據(jù)庫軟件代碼。
- 連接到數(shù)據(jù)庫服務(wù)器。
- 構(gòu)建查詢字符串。
- 執(zhí)行查詢字符串。
- 針對返回的結(jié)果,做需要的處理。
- 斷開與數(shù)據(jù)庫服務(wù)器的連接。
所有這些面向程序員的部分由接口包 java.sql 提供,它獨立于數(shù)據(jù)庫,定義了核心客戶端 Java 數(shù)據(jù)庫連接(JDBC)API。java.sql 包是 Java 核心庫的一部分,因此無需提供 .jar 文件即可編譯。但每個數(shù)據(jù)庫提供者都會創(chuàng)建自己的 java.sql 接口實現(xiàn)(例如 Connection 接口),并且必須在運行步驟中提供這些實現(xiàn)。
接下來我們使用 PostgreSQL,看看這一過程是如何進(jìn)行的。
訪問特定數(shù)據(jù)庫的代碼
以下代碼使用 Java 類加載器(Class.forName() 調(diào)用)將 PostgreSQL 驅(qū)動程序代碼加載到正在執(zhí)行的虛擬機中:
- import java.sql.*;
- public class Test1 {
- public static void main(String args[]) {
- // Load the driver (jar file must be on class path) [1]
- try {
- Class.forName("org.postgresql.Driver");
- System.out.println("driver loaded");
- } catch (Exception e1) {
- System.err.println("couldn't find driver");
- System.err.println(e1);
- System.exit(1);
- }
- // If we get here all is OK
- System.out.println("done.");
- }
- }
因為類加載器可能失敗,失敗時會拋出異常,所以將對 Class.forName() 的調(diào)用放在 try-catch 代碼塊中。
如果你使用 javac 編譯上面的代碼,然后用 java 運行,會報異常:
- me@mymachine:~/Test$ javac Test1.java
- me@mymachine:~/Test$ java Test1
- couldn't find driver
- java.lang.ClassNotFoundException: org.postgresql.Driver
- me@mymachine:~/Test$
類加載器要求類路徑中有包含 PostgreSQL JDBC 驅(qū)動程序?qū)崿F(xiàn)的 .jar 文件:
- me@mymachine:~/Test$ java -cp ~/src/postgresql-42.2.5.jar:. Test1
- driver loaded
- done.
- me@mymachine:~/Test$
連接到數(shù)據(jù)庫服務(wù)器
以下代碼實現(xiàn)了加載 JDBC 驅(qū)動程序和創(chuàng)建到 PostgreSQL 數(shù)據(jù)庫的連接:
- import java.sql.*;
- public class Test2 {
- public static void main(String args[]) {
- // Load the driver (jar file must be on class path) [1]
- try {
- Class.forName("org.postgresql.Driver");
- System.out.println("driver loaded");
- } catch (Exception e1) {
- System.err.println("couldn't find driver");
- System.err.println(e1);
- System.exit(1);
- }
- // Set up connection properties [2]
- java.util.Properties props = new java.util.Properties();
- props.setProperty("user","me");
- props.setProperty("password","mypassword");
- String database = "jdbc:postgresql://myhost.org:5432/test";
- // Open the connection to the database [3]
- try (Connection conn = DriverManager.getConnection(database, props)) {
- System.out.println("connection created");
- } catch (Exception e2) {
- System.err.println("sql operations failed");
- System.err.println(e2);
- System.exit(2);
- }
- System.out.println("connection closed");
- // If we get here all is OK
- System.out.println("done.");
- }
- }
編譯并運行上述代碼:
- me@mymachine:~/Test$ javac Test2.java
- me@mymachine:~/Test$ java -cp ~/src/postgresql-42.2.5.jar:. Test2
- driver loaded
- connection created
- connection closed
- done.
- me@mymachine:~/Test$
關(guān)于上述的一些注意事項:
- 注釋 [2] 后面的代碼使用系統(tǒng)屬性來設(shè)置連接參數(shù)(在本例中參數(shù)為 PostgreSQL 用戶名和密碼)。代碼也可以從 Java 命令行獲取這些參數(shù)并將所有參數(shù)作為參數(shù)包傳遞,同時還有一些其他 Driver.getConnection() 選項可用于單獨傳遞參數(shù)。
- JDBC 需要一個用于定義數(shù)據(jù)庫的 URL,它在上述代碼中被聲明為 String database 并與連接參數(shù)一起傳遞給 Driver.getConnection() 方法。
- 代碼使用 try-with-resources 語句,它會在 try-catch 塊中的代碼完成后自動關(guān)閉連接。Stack Overflow 上對這種方法進(jìn)行了長期的討論。
- try-with-resources 語句提供對 Connection 實例的訪問,并可以在其中執(zhí)行 SQL 語句;所有錯誤都會被同一個 catch 語句捕獲。
用數(shù)據(jù)庫的連接處理一些有趣的事情
日常工作中,我經(jīng)常需要知道為給定的數(shù)據(jù)庫服務(wù)器實例定義了哪些用戶,這里我使用這個 簡便的 SQL 來獲取所有用戶的列表:
- import java.sql.*;
- public class Test3 {
- public static void main(String args[]) {
- // Load the driver (jar file must be on class path) [1]
- try {
- Class.forName("org.postgresql.Driver");
- System.out.println("driver loaded");
- } catch (Exception e1) {
- System.err.println("couldn't find driver");
- System.err.println(e1);
- System.exit(1);
- }
- // Set up connection properties [2]
- java.util.Properties props = new java.util.Properties();
- props.setProperty("user","me");
- props.setProperty("password","mypassword");
- String database = "jdbc:postgresql://myhost.org:5432/test";
- // Open the connection to the database [3]
- try (Connection conn = DriverManager.getConnection(database, props)) {
- System.out.println("connection created");
- // Create the SQL command string [4]
- String qs = "SELECT " +
- " u.usename AS \"User name\", " +
- " u.usesysid AS \"User ID\", " +
- " CASE " +
- " WHEN u.usesuper AND u.usecreatedb THEN " +
- " CAST('superuser, create database' AS pg_catalog.text) " +
- " WHEN u.usesuper THEN " +
- " CAST('superuser' AS pg_catalog.text) " +
- " WHEN u.usecreatedb THEN " +
- " CAST('create database' AS pg_catalog.text) " +
- " ELSE " +
- " CAST('' AS pg_catalog.text) " +
- " END AS \"Attributes\" " +
- "FROM pg_catalog.pg_user u " +
- "ORDER BY 1";
- // Use the connection to create a statement, execute it,
- // analyze the results and close the result set [5]
- Statement stat = conn.createStatement();
- ResultSet rs = stat.executeQuery(qs);
- System.out.println("User name;User ID;Attributes");
- while (rs.next()) {
- System.out.println(rs.getString("User name") + ";" +
- rs.getLong("User ID") + ";" +
- rs.getString("Attributes"));
- }
- rs.close();
- stat.close();
- } catch (Exception e2) {
- System.err.println("connecting failed");
- System.err.println(e2);
- System.exit(1);
- }
- System.out.println("connection closed");
- // If we get here all is OK
- System.out.println("done.");
- }
- }
在上述代碼中,一旦有了 Connection 實例,它就會定義一個查詢字符串(上面的注釋 [4]),創(chuàng)建一個 Statement 實例并用其來執(zhí)行查詢字符串,然后將其結(jié)果放入一個 ResultSet 實例。程序可以遍歷該 ResultSet 實例來分析返回的結(jié)果,并以關(guān)閉 ResultSet 和 Statement 實例結(jié)束(上面的注釋 [5])。
編譯和執(zhí)行程序會產(chǎn)生以下輸出:
- me@mymachine:~/Test$ javac Test3.java
- me@mymachine:~/Test$ java -cp ~/src/postgresql-42.2.5.jar:. Test3
- driver loaded
- connection created
- User name;User ID;Attributes
- fwa;16395;superuser
- vax;197772;
- mbe;290995;
- aca;169248;
- connection closed
- done.
- me@mymachine:~/Test$
這是在一個簡單的 Java 應(yīng)用程序中使用 PostgreSQL JDBC 庫的(非常簡單的)示例。要注意的是,由于 java.sql 庫的設(shè)計方式,它不需要在代碼中使用像 import org.postgresql.jdbc.*; 這樣的 Java 導(dǎo)入語句,而是使用 Java 類加載器在運行時引入 PostgreSQL 代碼的方式,也正因此無需在代碼編譯時指定類路徑。