mysql數據遷移到oracle,其中有個表名叫“bil_vip”,有10萬條記錄,遷移后檢查發現oracle數據庫只插入34464條記錄,程序執行過程沒有發現任何錯誤。修改數據庫連接代碼向mysql插入10萬條記錄,結果都插入OK。
批量插入使用spring jdbctemplate.batchUpdate(sql, new BatchPreparedStatementSetter());方法,該方法的核心是PreparedStatement的executeBatch方法。
2. 繼續實驗:
新建一個Test表,只有一個name字段做實驗。
拋棄jdbctemplate,直接使用PreparedStatement做實驗,結果和jdbcTemplate是一樣的,實驗證明總是會丟失65536條記錄。
oracle 驅動的問題?換了最新driver,還是不行。繼續探索....
3. 希望之光:
早上發了個消息向大家征求思路,繼中說了一句分批處理,點亮了我的思路。按照繼中提供思路,每1萬條記錄一批,分多批發送給Oracle。
結果喜出望外,10萬條記錄全部插入成功。但是"65536"這個數字是什么意思呢......
在網上搜索文章發現,這個65536是一個bug。當PreparedStatement批量處理正好65536個記錄時候,程序會掛死。我試了一下真的掛死了,太嚇人了,這坑夠深的。
4. 解決方案:
spring jdbctemplate還是很好用的,而且業務已經被我封裝了,如果使用PreparedStatement意味著多了一個處理分支,以后維護會很麻煩。
我新建一個MyJdbcTemplate類,繼承 jdbctemplate類,并覆蓋了batchUpdate方法。這下舒服了,系統又恢復了整潔。
5. ★ 結論和收獲:
ü 有事多思考,多請教身邊同事。
ü 堅持Open-close原則(Open for extension, Closed for modification)會系統更好的擴展,非常容易維護,關鍵是要堅持這個原則,如果我因為一個特殊分支使用了PreparedStatement,這樣勢必破壞了這個原則,日后的維護必然會很麻煩。
ü 基于oracle數據如果使用jdbc批量,一定要分批發送數據oracle,否則正好發一個65536系統就掛死,大于65536數據就丟失,杯具呀......
6. 題目何以為“血案”:
周二打球回家就想這個問題,打開筆記本調試,不知不覺搞到很晚,影響媳婦睡覺(媳婦早上5:30上班),被痛罵一頓,趕緊上床睡覺,我媳婦氣的不行,手痛砸了一下床板(我們的床撤掉了床墊,只有床架和木板),床一下子塌了,把媳婦嚇壞了,把我腿弄傷了一塊,唉,“血案”呀。
Oracle | mysql | |
對比版本 | Enterprise Oracle10g Release 10.2.0.1.0 | mysql 4.1.21-nt |
默認安裝目錄 | ..\oracle\product\10.2.0 | ..\MySQL\MySQL Server 4.1 |
各種實用程序所在目錄 | ..\oracle\product\10.2.0\db_1\BIN | ..\MySQL\MySQL Server 4.1\bin |
客戶程序 | SQL*Plus | mysql |
安裝后系統默認用戶(庫) | sys system scott |
mysql test |
顯示所有用戶(庫) | SQL >select * from all_users; | mysql> show databases; |
退出命令 | SQL> exit SQL> quit |
mysql> exit mysql> quit |
改變連接用戶(庫) | SQL> conn 用戶名/密碼@主機字符串 | mysql> use 庫名 |
查詢當前所有的表 | SQL> select * from tab; SQL> select * from cat; |
mysql> show tables; F:MySQLbin>mysqlshow 庫名 |
顯示當前連接用戶(庫) | SQL> show user | mysql> connect |
查看幫助 | SQL> ? | mysql> help |
顯示表結構 | SQL> desc 表名 SQL> describe 表名 |
mysql> desc 表名; mysql> describe 表名; mysql> show columns from 表名; F:MySQLbin>mysqlshow 庫名 表名 |
日期函數 | SQL> select sysdate from dual; | mysql> select now(); mysql> select sysdate(); mysql> select curdate(); mysql> select current_date; mysql> select curtime(); mysql> select current_time; |
日期格式化 | SQL> select to_char(sysdate,'yyyy-mm-dd') from dual; SQL> select to_char(sysdate,'hh24-mi-ss') from dual; |
mysql> select date_format(now(),'%Y-%m-%d'); mysql> select time_format(now(),'%H-%i-%S'); |
日期函數 (增加一個月) |
SQL> select to_char(add_months(to_date('20000101','yyyymmdd'),1),'yyyy-mm-dd') from dual; 結果:2000-02-01 SQL> select to_char(add_months(to_date('20000101','yyyymmdd'),5),'yyyy-mm-dd') from dual; 結果:2000-06-01 |
mysql> select date_add('2000-01-01',interval 1 month); 結果:2000-02-01 mysql> select date_add('2000-01-01',interval 5 month); 結果:2000-06-01 |
別名 | SQL> select 1 a from dual; | mysql> select 1 as a; |
字符串截取函數 | SQL> select substr('abcdefg',1,5) from dual; SQL> select substrb('abcdefg',1,5) from dual; 結果:abcde |
mysql> select substring('abcdefg',2,3); 結果:bcd mysql> select mid('abcdefg',2,3); 結果:bcd mysql> select substring('abcdefg',2); 結果:bcdefg mysql> select substring('abcdefg' from 2); 結果:bcdefg 另有SUBSTRING_INDEX(str,delim,count)函數 返回從字符串str的第count個出現的分隔符delim之后的子串。 如果count是正數,返回最后的分隔符到左邊(從左邊數) 的所有字符。 如果count是負數,返回最后的分隔符到右邊的所有字符(從右邊數)。 |
執行外部腳本命令 | SQL >@f:\sql\a.sql | 1:mysql> source f:/sql/a.sql 2:F:MySQLbin>mysql 3:F:MySQLbin>mysql 庫名 |
改表名 | SQL> rename a to b; | mysql> alter table a rename b; |
執行命令 | ;<回車> / r run |
;<回車> go ego |
distinct用法 | SQL> select distinct 列1 from 表1; SQL> select distinct 列1,列2 from 表1; |
mysql> select distinct 列1 from 表1; mysql> select distinct 列1,列2 from 表1; |
注釋 | -- /*與*/ |
# -- /*與*/ |
限制返回記錄條數 | SQL> select * from 表名 where rownum<5; | mysql> select * from 表名 limit 5; |
新建用戶(庫) | SQL> create user 用戶名 identified by 密碼; | mysql> create database 庫名; |
刪用戶(庫) | SQL> drop user 用戶名; | mysql> drop database 庫名; |
外連接 | 使用(+) | 使用left join |
查詢索引 | SQL> select index_name,table_name from user_indexes; | mysql> show index from 表名 [FROM 庫名]; |
通配符 | “%” | “%”和“_” |
SQL語法 | SELECT selection_list 選擇哪些列 FROM table_list 從何處選擇行 WHERE primary_constraint 行必須滿足什么條件 GROUP BY grouping_columns 怎樣對結果分組 HAVING secondary_constraint 行必須滿足的第二條件 ORDER BY sorting_columns 怎樣對結果排序 |
SELECT selection_list 選擇哪些列 FROM table_list 從何處選擇行 WHERE primary_constraint 行必須滿足什么條件 GROUP BY grouping_columns 怎樣對結果分組 HAVING secondary_constraint 行必須滿足的第二條件 ORDER BY sorting_columns 怎樣對結果排序 |
Spring 不但提供了一個功能全面的應用開發框架,本身還擁有眾多可以在程序編寫時直接使用的工具類,您不但可以在 Spring 應用中使用這些工具類,也可以在其它的應用中使用,這些工具類中的大部分是可以在脫離 Spring 框架時使用的。了解 Spring 中有哪些好用的工具類并在程序編寫時適當使用,將有助于提高開發效率、增強代碼質量。
在這個分為兩部分的文章中,我們將從眾多的 Spring 工具類中遴選出那些好用的工具類介紹給大家。第 1 部分將介紹與文件資源操作和 Web 相關的工具類。在 第 2 部分 中將介紹特殊字符轉義和方法入參檢測工具類。
文件資源操作
文件資源的操作是應用程序中常見的功能,如當上傳一個文件后將其保存在特定目錄下,從指定地址加載一個配置文件等等。我們一般使用 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 中聲明該監聽器就可以了。
方法 |
說明 |
Restrictions.eq |
= |
Restrictions.allEq |
利用Map來進行多個等于的限制 |
Restrictions.gt |
> |
Restrictions.ge |
>= |
Restrictions.lt |
< |
Restrictions.le |
<= |
Restrictions.between |
BETWEEN |
Restrictions.like |
LIKE |
Restrictions.in |
in |
Restrictions.and |
and |
Restrictions.or |
or |
Restrictions.sqlRestriction |
用SQL限定查詢 |
QBC常用限定方法
Restrictions.eq --> equal,等于.
Restrictions.allEq --> 參數為Map對象,使用key/value進行多個等于的比對,相當于多個Restrictions.eq 的效果
Restrictions.gt --> great-than > 大于
Restrictions.ge --> great-equal >= 大于等于
Restrictions.lt --> less-than, < 小于
Restrictions.le --> less-equal <= 小于等于
Restrictions.between --> 對應SQL的between子句
Restrictions.like --> 對應SQL的LIKE子句
Restrictions.in --> 對應SQL的in子句
Restrictions.and --> and 關系
Restrictions.or --> or 關系
Restrictions.isNull --> 判斷屬性是否為空,為空則返回true
Restrictions.isNotNull --> 與isNull相反
Restrictions.sqlRestriction --> SQL限定的查詢
Order.asc --> 根據傳入的字段進行升序排序
Order.desc --> 根據傳入的字段進行降序排序
MatchMode.EXACT --> 字符串精確匹配.相當于"like 'value'"
MatchMode.ANYWHERE --> 字符串在中間匹配.相當于"like '%value%'"
MatchMode.START --> 字符串在最前面的位置.相當于"like 'value%'"
MatchMode.END --> 字符串在最后面的位置.相當于"like '%value'"
例子
查詢年齡在20-30歲之間的所有學生對象
List list = session.createCriteria(Student.class)
.add(Restrictions.between("age",new Integer(20),new Integer(30)).list();
查詢學生姓名在AAA,BBB,CCC之間的學生對象
String[] names = {"AAA","BBB","CCC"};
List list = session.createCriteria(Student.class)
.add(Restrictions.in("name",names)).list();
查詢年齡為空的學生對象
List list = session.createCriteria(Student.class)
.add(Restrictions.isNull("age")).list();
查詢年齡等于20或者年齡為空的學生對象
List list = session.createCriteria(Student.class)
.add(Restrictions.or(Restrictions.eq("age",new Integer(20)),
Restrictions.isNull("age")).list();
--------------------------------------------------------------------
使用QBC實現動態查詢
public List findStudents(String name,int age){
Criteria criteria = session.createCriteria(Student.class);
if(name != null){
criteria.add(Restrictions.liek("name",name,MatchMode.ANYWHERE));
}
if(age != 0){
criteria.add(Restrictions.eq("age",new Integer(age)));
}
criteria.addOrder(Order.asc("name"));//根據名字升序排列
return criteria.list();
}
-----------------------------------------------------------------------------------
今天用了寫hibernate高級查詢時用了Restrictions(當然Expr
下面的代碼寫的不易讀.其實核心就是一句
Restrictions.or(Restrictions.like(),Restrictions.or(Restrictions.like,........))
里面的or可以無限加的.還是比較好用
Session session = getHibernateTemplate().getSessionFactory()
.openSession();
Criteria criteria = session.createCriteria(Film.class);
List<Film> list = criteria.add(
Restrictions.or(Restrictions.like("description", key,MatchMode.ANYWHERE),
Restrictions.or(Restrictions.like("name", key,MatchMode.ANYWHERE),
Restrictions.or( Restrictions.like("direct", key,MatchMode.ANYWHERE),
Restrictions.or(Restrictions.like("mainplay",key,MatchMode.ANYWHERE),
Restrictions.like("filearea", key,MatchMode.ANYWHERE)))))).list();
session.close();
return list;
我們都知道,互聯網是不安全的,但其上所使用的大部分應用,如Web、Email等一般都只提供明文傳輸方式(用https、smtps等例外)。所以,當我們需要傳輸重要文件時,應該對當中的信息加密。非對稱密碼系統是其中一種常見的加密手段。而在基于PGP方式加密的中文介紹少之又少,所以萌生了寫一個完整教程的想法,當然本文部分資料是我搜遍網絡整理出來的,并不能保證百分之百的原創
GnuPG 是一個用來進行非對稱加密(PGP)的免費軟件,簡稱GPG(是不是有的童鞋已經被PGP和GPG給搞昏了)。先說說什么是非對稱加密。傳統的加密手段往往是使用同一個密碼進行加密和解密。例如你加密時用的密碼是“abc”, 則解密時也要使用“abc”才行。這樣就存在一個問題,你不能夠把一段加密信息發送給你的朋友。試想,如果采用這種加密方式把信息發送給你的朋友時,你的 朋友必須要知道你的密碼才能把你的信息解密出來。但你如何保證你的朋友是絕對可靠的呢?也就是說,如果你的朋友把你的密碼告訴了別人,你的密碼就不再安全 了。
非對稱加密采用的是另一種思想。它會給你產生兩個密鑰,一個稱為“公鑰”,另一個稱為“私鑰”。公鑰是可以公開的,你盡管把它傳給別 人;私鑰你一定要保管好不讓其他任何人知道。當某人得到你的公鑰后,他就可以給你發送加密信息了。具體來說,他把他要發給你的信息用你的公鑰加密后發給 你,加密的信息只能用你的私鑰去解密。這樣,因為世界上除了你以外沒有別人知道你的私鑰,所以即使別人看到發送給你的加密信息他也無法解密,甚至連發送者 本人也不行。因為他不知道你的私鑰。簡單說來,就是用公鑰去加密;用對應的私鑰去解密。想給誰發送加密信息,首先要得到他的公鑰。
支持非 對稱加密的軟件有多種,最著名的可能是美國的PGP了,不過它是個商業軟件,價格不便宜。對于加密軟件,我反對使用破解軟件,因為如果信息需要加密的話, 肯定是非常重要的信息,破解軟件無法保證加密的安全可靠。因此我建議使用免費開源的GnuPG軟件進行信息的加密和解密。
1.生成密鑰對
要使用GnuPG加密,首先需要創建密鑰對,執行:
# gpg –gen-key
gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
請選擇您要使用的密鑰種類:
(1) DSA 和 ElGamal (默認)
(2) DSA (僅用于簽名)
(5) RSA (僅用于簽名)
您的選擇? 1 ←只有1可以用于加密,其他種類只能用于簽名
DSA 密鑰對會有 1024 位。
ELG-E 密鑰長度應在 1024 位與 4096 位之間。
您想要用多大的密鑰尺寸?(2048) ←選擇密碼的位數,位數越大,越安全,但速度越慢
您所要求的密鑰尺寸是 2048 位
請設定這把密鑰的有效期限。
0 = 密鑰永不過期
<n> = 密鑰在 n 天后過期
<n>w = 密鑰在 n 周后過期
<n>m = 密鑰在 n 月后過期
<n>y = 密鑰在 n 年后過期
密鑰的有效期限是?(0) 0 ←根據實際情況選擇密鑰期限
密鑰永遠不會過期
以上正確嗎?(y/n)y ←確認您需要一個用戶標識來辨識您的密鑰;本軟件會用真實姓名、注釋和電子郵件地址組合
成用戶標識,如下所示:
“Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>”真實姓名:Hyphen Wang ←請填入真實姓名,后面會用到
電子郵件地址:gpgencrypt@linuxfly.org ←郵件作為標記之一,不能重復
注釋:Use for GPG Encrypt ←僅是注釋而已
您選定了這個用戶標識:
“Hyphen Wang (Use for GPG Encrypt) <gpgencrypt@linuxfly.org>”更改姓名(N)、注釋(C)、電子郵件地址(E)或確定(O)/退出(Q)?O ←輸入“O”確認
您需要一個密碼來保護您的私鑰。 ←輸入兩次用于訪問私鑰的密碼,緊記,不能公開或丟失我們需要生成大量的隨機字節。這個時候您可以多做些瑣事(像是敲打鍵盤、移動
鼠標、讀寫硬盤之類的),這會讓隨機數字發生器有更好的機會獲得足夠的熵數。
++++++++++…++++++++++..++++++++隨機字節不夠多。請再做一些其他的瑣事,以使操作系統能搜集到更多的熵數!
(還需要274字節) ←運行一些的程序,以便在內存中獲得更多隨機數
我們需要生成大量的隨機字節。這個時候您可以多做些瑣事(像是敲打鍵盤、移動
鼠標、讀寫硬盤之類的),這會讓隨機數字發生器有更好的機會獲得足夠的熵數。
+++++++++++++++++++++++++.+++++.+++++.++++++++++.+++<+++++..+++++^^^
gpg: 密鑰 A3942296 被標記為絕對信任 ←密鑰ID
公鑰和私鑰已經生成并經簽名。gpg: 正在檢查信任度數據庫
gpg: 需要 3 份勉強信任和 1 份完全信任,PGP 信任模型
gpg: 深度:0 有效性: 2 已簽名: 0 信任度:0-,0q,0n,0m,0f,2u
pub 1024D/A3942296 2008-12-19
密鑰指紋 = E95E 1F77 6C4E 33BD 740C 19AB EEF9 A67E A394 2296
uid Hyphen Wang (Use for GPG Encrypt) <gpgencrypt@linuxfly.org>
sub 2048g/911E677B 2008-12-19
gpg –output revoke.asc –gen-revoke mykeyID
其中mykey 參數是可以表示的密鑰標識,產生的回收證書放在revoke.asc文件里,一旦回收證書被發放,以前的證書就不能再被其他用戶訪問,因此以前的公鑰也就失效了。
PS:如果一旦決定撤銷已經上傳的公鑰,就需要將該密鑰的回收證書上傳至密鑰服務器完成回收工作。
gpg –keyserver Server Address –send-keys mykeyID
3.密鑰的上傳
當上述工作完成以后,為了讓盡可能多的人獲取您的公鑰,您可以將公鑰郵寄出去,或者貼在自己的個人主頁上,當然還有一種更好的方法就是上傳到全球性的密鑰服務器,其他用戶可以通過您提供的公鑰ID來搜索并獲得您的公鑰。
通過如下命令可以將你的key發布到服務器上:
gpg –keyserver Server Address –send-keys mykeyID
gpg -o keyfilename –export mykeyID
如果沒有mykeyID則是備份所有的公鑰,-o表示輸出到文件keyfilename中,如果加上-a的參數則輸出文本格式( ASCII )的信息,否則輸出的是二進制格式信息。
gpg -o keyfilename –export-secret-keys mykeyID
如果沒有mykeyID則是備份所有的私鑰,-o表示輸出到文件keyfilename中,如果加上-a的參數則輸出文本格式的信息,否則輸出的是二進制格式信息。
gpg –import filename
PS:用戶可以使用gpg –list-keys命令查看是否成功導入了密鑰。
5.加密解密和數字簽名
通過上述的密鑰生成以及公鑰分發后,加密和解密數據變得非常容易,用戶可以通過使用該功能來達到安全地在網絡上傳輸自己的隱密數據的目的。
如果用戶patterson要給用戶liyang發送一個加密文件,則他可以使用liyang的公鑰加密這個文件,并且這個文件也只有liyang使用自己的密鑰才可以解密查看。下面給出加解密的步驟:
# gpg -e test
You did not specify a user ID. (you may use “-r”)
Enter the user ID. End with an empty line: liyang
Added 1024g/C50E455A 2006-01-02 “liyang (hello) < liyang@sina.com>”
這樣,就可以將gpg.conf文件加密成test.gpg,一般用戶是無法閱讀的
PS:當然你也可以直接指定使用哪個用戶的公鑰進行加密:
gpg -e -r liyang test (-r 表示指定用戶)
還可以加上參數 -a 來輸出ASCII編碼的文件test.asc(test.gpg是二進制編碼的,不可用文本讀)
gpg -ea -r liyang test
# gpg -d test.gpg
You need a passphrase to unlock the secret key for
user: “liyang (hello) < liyang@sina.com>”
1024-bit ELG-E key, ID C50E455A, created 2006-01-02 (main key ID 378D11AF)
GnuPG提示用戶,需要輸入生成私鑰使用的密碼:
Enter passphrase:
gpg: encrypted with 1024-bit ELG-E key, ID C50E455A, created 2006-01-02
“liyang (hello) < liyang@sina.com>”
PS:無論加密解密,都可以加上-o參數來指定加密和解密后的輸出文件,例如
#gpg -o doc.gpg -er name doc
其中name是選擇誰的公鑰加密,即誰是文件的接收者。
doc為要加密的文件,即原文件
doc.gpg為命令執行后生成的加密的文件,這里要先指定好文件名
1、數字簽名
命令格式:
#gpg -o doc.sig -s doc
其中doc是原文件,doc.sig包含了原文件和簽名,是二進制的。這個命令會要求你輸入你的私鑰的密碼句。
#gpg -o doc.sig -ser name doc
既簽名又加密2、文本簽名
#gpg -o doc.sig –clearsign doc
這樣產生的doc.sig同樣包含原文件和簽名,其中簽名是文本的,而原文件不變。3、分離式簽名
#gpg -o doc.sig -ab doc
doc.sig僅包括簽名,分離式簽名的意思是原文件和簽名是分開的。
b 表示分離式簽名detach-sign4、驗證簽名
#gpg –verify doc.sig [doc]
驗證之前必須導入文件作者的公鑰,對于分離式簽名,最后還要加上原文件,即后面的doc。
盡管在理論上講,具備了公匙和私匙就可以實現安全的信息通訊,但是在實際應用中,還必須對公匙進行有效確認。因為,確實存在偽造公匙信息的可能。
由此,在GPG中引入了一個復雜的信任系統,以幫助我們區分哪些密匙是真的,哪些密匙是假的。這個信任系統是基于密匙的,主要包括密匙簽名。
當收到熟人的公匙并且GPG告知不存在任何實體可信信息附加于這個公匙后,首要的事情就是對這個密匙進行“指紋采樣”(fingerprint)。例如,我們對來自mike的公匙進行了導入操作,并且GPG告知我們不存在這個密匙的附加可信信息,這時候,我們首先要做的工作就是對這個新密匙進行“指紋采樣 ”,相關命令及執行情況如下:
$ gpg –fingerprint mike@hairnet.orgpub 1024D/4F03BD39 2001-01-15 Mike Socks (I’m WIRED) Key fingerprint = B121 5431 8DE4 E3A8 4AA7 737D 20BE 0DB8 4F03 BD39sub 1024g/FDBB477D 2001-01-15$
這樣,就從密匙數據中生成了其指紋信息,并且應該是唯一的。然后,我們打電話給mike,確認兩件事情。首先,他是否發送給我們了密匙;其次,他的公匙的指紋信息是什么。如果Mike確認了這兩件事情,我們就可以確信這個密匙是合法的。接下來,我們對密匙進行簽名操作,以表示這個密匙來自Mike而且我們對密匙的信任,相關命令及執行情況如下:
$ gpg –sign-key mike@hairnet.orgpub 1024D/4F03BD39 created: 2001-01-15 expires: neversub 1024g/FDBB477D created: 2001-01-15 expires: never(1) Mike Socks (I’m WIRED) pub 1024D/4F03BD39 created: 2001-01-15 expires: neverFingerprint = B121 5431 8DE4 E3A8 4AA7 737D 20BE 0DB8 4F03 BD39Mike Socks (I’m WIRED) Are you really sure that you want to sign this keywith your key: Ima User (I’m just ME) Really sign? yYou need a passphrase to unlock the secret key foruser: Ima User (I’m just ME) 1024-bit DSA key, ID D9BAC463, created 2001-01-03Enter passphrase:$
執行到此,使用我們的私匙完成了對Mike的公匙的簽名操作,任何持有我們的公匙的人都可以查證簽名確實屬于我們自己。這個附加到Mike的公匙上的簽名信息將隨它環游Internet世界,我們使用個人信譽,也就是我們自己的私匙,保證了那個密匙確實屬于Mike。這是一個多么感人的充滿誠信的故事啊 現實世界的人們是否應該從這嚴格的技術標準中反思些什么呢?
還是回到這里。獲取附加于一個公匙上的簽名信息列表的命令是:
gpg –check-sigs mike@hairnet.org
簽名列表越長,密匙的可信度越大。其實,正是簽名系統本身提供了密匙查證功能。假設我們接收到一個簽名為Mike的密匙,通過Mike的公匙,我們驗證出簽名確實屬于Mike,那么我們就信任了這個密匙。推而廣之,我們就可以信任Mike簽名的任何密匙。
為了更加穩妥,GPG還引入了另一個附加功能:可信級別(trust level)。使用它,我們可以為我們擁有的任何密匙的所有者指定可信級別。例如,即使我們知道Mike的公匙是可信的,但是事實上我們不能信任Mike在對其他密匙簽名時的判斷;我們會想,Mike也許只對少數密匙進行了簽名,但卻沒有好好地檢查一遍。
設置可信級別的命令及執行情況如下:
$ gpg –edit-key mike@hairnet.orgpub 1024D/4F03BD39 created: 2001-01-15 expires: never trust: -/fsub 1024g/FDBB477D created: 2001-01-15 expires: never(1) Mike Socks (I’m WIRED) Command> trust 1 = Don’t know 2 = I do NOT trust 3 = I trust marginally 4 = I trust fully s = please show me more information m = back to the main menuYour decision? 2Command> quit$
在命令編輯環境中執行trust,然后選擇級別2(I do NOT trust),這樣我們割斷了任何信任鏈,使每個密匙都必須經過Mike的簽名。
6.刪除密鑰
從私鑰鑰匙環里刪除密鑰:
# gpg –delete-secret-keys hyphenwang@redflag-linux.com
gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
sec 1024D/A3942296 2008-12-19 Hyphen Wang (Use for GPG Encrypt) <gpgencrypt@linuxfly.org>
要從鑰匙環里刪除這把密鑰嗎?(y/N)y
這是一把私鑰!――真的要刪除嗎?(y/N)y
必須先刪除私鑰,然后才能刪除公鑰。
從公鑰鑰匙環里刪除密鑰:
# gpg –delete-keys hyphenwang@redflag-linux.com
gpg (GnuPG) 1.4.5; Copyright (C) 2006 Free Software Foundation, Inc.
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.
sec 1024D/A3942296 2008-12-19 Hyphen Wang (Use for GPG Encrypt) <gpgencrypt@linuxfly.org>
要從鑰匙環里刪除這把密鑰嗎?(y/N)y
當然GPG同樣具備普通的對稱加密功能,這時候就不需要密鑰,直接用密碼加密即可(注意,這里的密碼不一定是你私鑰的密碼,您大可以隨意設定)
gpg -o doc.gpg -c doc
---------------------------------------------
語法:gpg [選項] [文件名]
簽字、檢查、加密或解密
默認的操作依輸入數據而定
指令:
-s, –sign [文件名] 生成一份簽字
–clearsign [文件名] 生成一份明文簽字
-b, –detach-sign 生成一份分離的簽字
-e, –encrypt 加密數據
-c, –symmetric 僅使用對稱加密
-d, –decrypt 解密數據(默認)
–verify 驗證簽字
–list-keys 列出密鑰
–list-sigs 列出密鑰和簽字
–check-sigs 列出并檢查密鑰簽字
–fingerprint 列出密鑰和指紋
-K, –list-secret-keys 列出私鑰
–gen-key 生成一副新的密鑰對
–delete-keys 從公鑰鑰匙環里刪除密鑰
–delete-secret-keys 從私鑰鑰匙環里刪除密鑰
–sign-key 為某把密鑰添加簽字
–lsign-key 為某把密鑰添加本地簽字
–edit-key 編輯某把密鑰或為其添加簽字
–gen-revoke 生成一份吊銷證書
–export 導出密鑰
–send-keys 把密鑰導出到某個公鑰服務器上
–recv-keys 從公鑰服務器上導入密鑰
–search-keys 在公鑰服務器上搜尋密鑰
–refresh-keys 從公鑰服務器更新所有的本地密鑰
–import 導入/合并密鑰
–card-status 打印卡狀態
–card-edit 更改卡上的數據
–change-pin 更改卡的 PIN
–update-trustdb 更新信任度數據庫
–print-md 算法 [文件] 使用指定的散列算法打印報文散列值選項:
-a, –armor 輸出經 ASCII 封裝
-r, –recipient 某甲 為收件者“某甲”加密
-u, –local-user 使用這個用戶標識來簽字或解密
-z N 設定壓縮等級為 N (0 表示不壓縮)
–textmode 使用標準的文本模式
-o, –output 指定輸出文件
-v, –verbose 詳細模式
-n, –dry-run 不做任何改變
-i, –interactive 覆蓋前先詢問
–openpgp 行為嚴格遵循 OpenPGP 定義
–pgp2 生成與 PGP 2.x 兼容的報文
(請參考在線說明以獲得所有命令和選項的完整清單)
范例:
-se -r Bob [文件名] 為 Bob 這個收件人簽字及加密
–clearsign [文件名] 做出明文簽字
–detach-sign [文件名] 做出分離式簽字
–list-keys [某甲] 顯示密鑰
–fingerprint [某甲] 顯示指紋
———————————————————————————–
Private和public的鑰匙是gpg加密和解密過程的主要部分,所以第一步就是創建為自己創建一對密匙.
生成私鑰
$gpg --gen-key |
你需要回答一些這個命令提出的問題
私鑰的種類和size,這里缺省的答案已經足夠好了
私鑰的有效期,我通常選擇不會過期,呵呵
你的真實的姓名和e-mail地址,這些是用來從一大堆鑰匙中找到你的鑰匙的
關于你的鑰匙的comment,可以為空,我一般填一個昵稱
鑰匙的密碼. 千萬別忘了,否則所有你加密過的文件都沒用了
為你的私鑰生成一個公鑰(文本文件),這是我的:aubrey.asc.zip
$ gpg --armor --output public.key --export <your email> |
你可以分發這個文件了,給你的朋友,或者貼到你的個人網站上, or whatever.
#gpg --encrypt --recipient 'Your Name' foo.txt
|
#gpg --output foo.txt --decrypt foo.txt.gpg
|
#gpg --import key.asc |
#gpg --output foo.txt --decrypt foo.txt.gpg
|