數據庫 用來存放數據,那么肯定需要存儲空間,所以對磁盤空間的監視自然就很有必要了。
一. 磁盤可用空間
(1) DOS命令: fsutil volume diskfree
C:\windows\system32>fsutil volume diskfree C:
Total # of free bytes : 9789493248
Total # of bytes : 64424505344
Total # of avail free bytes : 9789493248
這里用到了fsutil,一個文件系統管理工具(file system utility),應該還有其他一些命令或者腳本也是可以的。
(2) WMI/WMIC: wmic logicaldisk
WMI是個
Windows 系統的管理接口,在WMIC出現之前,如果要利用WMI管理系統,必須使用一些專門的WMI應用,例如SMS,或者使用WMI的腳本編程API,或者使用象CIM Studio之類的工具。如果不熟悉C++之類的編程語言或VBScript之類的腳本語言,或者不掌握WMI名稱空間的基本知識,要用WMI管理系統是很困難的。WMIC改變了這種情況,它為WMI名稱空間提供了一個強大的、友好的命令行接口。
C:\windows\system32>wmic logicaldisk get caption,freespace,size
Caption FreeSpace Size
C: 9789071360 64424505344
D: 189013438464 255331397632
這里通過wmic的get命令獲取了logicaldisk 的幾個參數列。
(3) 性能監視器
LogicalDisk: %Free Space
LogicalDisk: Free Megabytes
總大小 = LogicalDisk: Free Megabytes/ LogicalDisk: %Free Space
性能監視器雖然用于現場診斷還是挺方便的,但實現自動化監控,并不太好用。
(1) 擴展存儲過程xp_cmdshell (還是在調用操作系統命令)
DECLARE @Drive TINYINT,
@SQL VARCHAR(100)
DECLARE @Drives TABLE
(
Drive CHAR(1),
Info VARCHAR(80)
)
SET @Drive = 97
WHILE @Drive <= 122
BEGIN
SET @SQL = 'EXEC XP_CMDSHELL ''fsutil volume diskfree ' + CHAR(@Drive) + ':'''
INSERT @Drives
(
Info
)
EXEC(@SQL)
UPDATE @Drives
SET Drive = CHAR(@Drive)
WHERE Drive IS NULL
SET @Drive = @Drive + 1
END
SELECT Drive,
SUM(CASE WHEN Info LIKE 'Total # of bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS TotalMBytes,
SUM(CASE WHEN Info LIKE 'Total # of free bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS FreeMBytes,
SUM(CASE WHEN Info LIKE 'Total # of avail free bytes%' THEN CAST(REPLACE(SUBSTRING(Info, 32, 48), CHAR(13), '') AS BIGINT) ELSE CAST(0 AS BIGINT) END)/1024.0/1024/1024 AS AvailFreeMBytes
FROM(
SELECT Drive,
Info
FROM @Drives
WHERE Info LIKE 'Total # of %'
) AS d
GROUP BY Drive
ORDER BY Drive
xp_cmdshell可以執行操作系統命令行,這段腳本用fsutil volume diskfree命令對26個字母的盤符遍歷了一遍,不是很好,改用wmic會方便些,如下:
EXEC xp_cmdshell 'wmic logicaldisk get caption,freespace,size';
(2) 擴展存儲過程xp_fixeddrives
--exec xp_fixeddrives
IF object_id('tempdb..#drivefreespace') IS NOT NULL
DROP TABLE #drivefreespace
CREATE TABLE #drivefreespace(Drive CHAR(1), FreeMb bigint)
INSERT #drivefreespace EXEC ('exec xp_fixeddrives')
SELECT * FROM #drivefreespace
Drive
FreeMb
C
9316
D
180013
總算不依賴操作系統命令了,不過,這個存儲過程只能返回磁盤可用空間,沒有磁盤總空間。
(3) DMV/DMF: sys.dm_os_volume_stats
SELECT DISTINCT
@@SERVERNAME as [server]
,volume_mount_point as drive
,cast(available_bytes/ 1024.0 / 1024.0 / 1024.0 AS INT) as free_gb
,cast(total_bytes / 1024.0 / 1024.0 / 1024.0 AS INT) as total_gb
FROM sys.master_files AS f
CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id)
ORDER BY @@SERVERNAME, volume_mount_point
server
drive
free_gb
total_gb
…
C:\
9
59
…
D:\
175
237
從SQL Server 2008 R2 SP1開始,有了這個很好用的DMF: sys.dm_os_volume_stats,彌補了之前xp_fixeddrives沒有磁盤總空間的不足。
不過,看它的參數就可以知道,沒被任何數據庫使用的磁盤,是查看不了的,所以xp_fixeddrives還有存在的必要。
二. 數據庫可用空間
1. 文件可用空間查看
(1) 文件已用空間,當前大小(已分配空間),最大值,如下:
select @@SERVERNAME as server_name
,DB_NAME() as database_name
,case when data_space_id = 0 then 'LOG'
else FILEGROUP_NAME(data_space_id)
end as file_group
,name as logical_name
,physical_name
,type_desc
,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_Mb
,size/128.0 as allocated_size_mb
,case when max_size = -1 then max_size
else max_size/128.0
end as max_size_Mb
,growth
,is_percent_growth
from sys.database_files
where state_desc = 'ONLINE'
(2) 再算上磁盤的空閑空間,改動如下:
select @@SERVERNAME as server_name
,DB_NAME() as database_name
,case when data_space_id = 0 then 'LOG'
else FILEGROUP_NAME(data_space_id)
end as file_group
,name as logical_name
,physical_name
,type_desc
,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_mb
,size/128.0 as allocated_size_mb
,case when max_size = -1 then max_size
else max_size/128.0
end as max_size_mb
,vs.available_bytes/1024.0/1024 as disk_free_mb
,growth
,CAST(is_percent_growth as int) as is_percent_growth
from sys.database_files df
cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs
where state_desc = 'ONLINE'
如果是SQL Server 2008 SP1以前的版本,可用xp_fixeddrives生成磁盤空閑空間表,再進行關聯。
(3) 結合文件是否自增長,文件最大值,磁盤空間,算出文件可用空間比率,改動如下:
select @@SERVERNAME as server_name
,DB_NAME() as database_name
,case when data_space_id = 0 then 'LOG'
else FILEGROUP_NAME(data_space_id)
end as file_group
,name as logical_name
,physical_name
,type_desc
,FILEPROPERTY(name,'SpaceUsed')/128.0 as used_size_mb
,size/128.0 as allocated_size_mb
,case when max_size = -1 then max_size
else max_size/128.0
end as max_size_mb
,vs.available_bytes/1024.0/1024 as disk_free_mb
,case when growth = 0 then (size - FILEPROPERTY(name,'SpaceUsed'))*1.0/size
when growth > 0 and max_size = -1 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,'SpaceUsed')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024)
when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) >= 0 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,'SpaceUsed')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024)
when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) < 0 then (max_size - FILEPROPERTY(name,'SpaceUsed'))*1.0/max_size
else null
end as free_space_percent
,growth
,CAST(is_percent_growth as int) as is_percent_growth
from sys.database_files df
cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs
where state_desc = 'ONLINE'
(4) 如果有多個數據庫,注意fileproperty()和filegroup_name()函數,都只在當前數據庫下生效,改動如下:
if object_id('tempdb..#tmp_filesize') is not null
drop table #tmp_filesize
GO
create table #tmp_filesize
(
server_name varchar(256),
database_name varchar(256),
file_group varchar(256),
logical_name varchar(256),
physical_name varchar(1024),
type_desc varchar(128),
used_size_mb float,
allocated_size_mb float,
max_size_mb float,
disk_free_mb float,
free_space_percent float,
growth int,
is_percent_growth int
)
GO
exec sp_msforeachdb 'use [?]
insert into #tmp_filesize
select @@SERVERNAME as server_name
,DB_NAME() as database_name
,case when data_space_id = 0 then ''LOG''
else FILEGROUP_NAME(data_space_id)
end as file_group
,name as logical_name
,physical_name
,type_desc
,FILEPROPERTY(name,''SpaceUsed'')/128.0 as used_size_mb
,size/128.0 as allocated_size_mb
,case when max_size = -1 then max_size
else max_size/128.0
end as max_size_mb
,vs.available_bytes/1024.0/1024 as disk_free_mb
,case when growth = 0 then (size - FILEPROPERTY(name,''SpaceUsed''))*1.0/size
when growth > 0 and max_size = -1 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,''SpaceUsed'')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024)
when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) >= 0 then ((size/128.0 + vs.available_bytes/1024.0/1024) - FILEPROPERTY(name,''SpaceUsed'')/128.0)/(size/128.0 + vs.available_bytes/1024.0/1024)
when growth > 0 and max_size <> -1 and (max_size/128.0 - vs.available_bytes/1024.0/1024) < 0 then (max_size - FILEPROPERTY(name,''SpaceUsed''))*1.0/max_size
else null
end as free_space_percent
,growth
,CAST(is_percent_growth as int) as is_percent_growth
from sys.database_files df
cross apply sys.dm_os_volume_stats(DB_ID(),df.file_id) vs
where state_desc = ''ONLINE'''
select * from #tmp_filesize
2. 數據庫可用空間告警
2.1 告警的格式
數據庫可用空間告警,通常不告警某個文件,也不告警整個數據庫,而是某個確切的文件組/表空間,日志文件是沒有文件組的,所有可以把日志文件合并為LOG這個組。
(1) Oracle可以給表空間設置最大尺寸,表空間里的每個文件逐個使用,直到最后一個文件也沒空間時,就會提示空間不足;
(2) SQL Server 無法對文件組設置最大尺寸,只可以給文件組里每個文件指定最大尺寸,所以要先統計:是否當前文件組下所有的文件都已經滿了?
將同一個文件組/LOG下的所有文件都檢查一下,如果所有文件都滿了(以20%為例),那么就滿足告警條件了,如下:
--#tmp_filesize 在上面的腳本里生成了
select server_name,
database_name,
file_group,
MAX(free_space_percent) as max_free_space_percent
from #tmp_filesize
group by server_name,database_name,file_group
having MAX(free_space_percent) <= 0.2 --20%
郵件告警的格式大致為:
郵件標題:主機名\實例名\數據庫名\文件組名,@@servername已經包含了SQL Server實例名;
郵件內容:文件組 ”file group name” 空間不足,已低于20%。
2.2 告警后如何處理?
(1) 告警中的文件組里的文件,所在的磁盤還有空間嗎?
exec xp_fixeddrives
如果當前磁盤沒空間,可以給當前文件組在其他磁盤上添加新的文件,并關閉老的文件自增長或限制最大值;
如果所有磁盤都沒空間,可以考慮刪除磁盤上的其他文件,或者收縮數據庫文件(數據/日志),或者磁盤擴展空間(加磁盤)。
(2) 如果磁盤有空間,文件是否關閉了自動增長?
可能是在創建文件時,給了文件比較大的size,如500G,并關閉了文件自動增長;
ALTER DATABASE test
ADD FILE
(
NAME = test_02,
FILENAME = 'D:\Program Files (x86)\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\test_02.ndf',
SIZE = 500 GB,
FILEGROWTH = 0
)
TO FILEGROUP [PRIMARY];
GO
(3) 如果磁盤有空間,自動增長也開了,是不是限制了文件最大值?
限制最大值和關閉自增長,應該都是不想單個文件變得太大,個人覺得一個文件控制在500G以內比較合理,這兩種情況,都建議擴展一個新文件。
小結
如果沒有監控工具,那么可選擇系統視圖,擴展存儲過程,結合數據庫郵件的方式,作自動檢查,并告警文件組/日志空閑空間不足。大致步驟如下 :
(1) 部署數據庫郵件;
(2) 部署作業:定時檢查文件組/日志空閑空間,發郵件告警。
English »
Afrikaans Albanian Arabic Armenian Azerbaijani Basque Bengali Belarusian Bulgarian Catalan Chinese (Simp) Chinese (Trad) Croatian Czech Danish Dutch English Esperanto Estonian Filipino Finnish French Galician Georgian German Greek Gujarati Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Kannada Korean Lao Latin Latvian Lithuanian Macedonian Malay Maltese Norwegian Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Tamil Telugu Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish
Java在中文環境中亂碼無處不在,而且出現的時間和位置也包涵廣泛,具體的解決方法也是千奇百怪。
但是如果能理清其中的脈絡,理解字符處 理的過程,對于解決問題很有指導意義,不至于解決了問題也不知道為什么。
其實,原因不外乎出在String輸入時和輸出時。
首先,Java中的任何String都是以UNICODE格式存在的。
很多人因為在GBK環境中使用String,會誤以為String是GBK格式,實際上Java的String類中并沒有存儲CharSet信息的字段, 所有String中的字符只會以UNICODE的2字節形式存在。
String在構造時會逐一把字符按指定編碼(默認值為系統編碼GBK),轉換為UNICODE字符,存入一個Char(無符號16位)數組中。
如:
new String(bytes,"gbk");
并不是說,生成一個GBK編碼的字符串,而是按GBK逐一辨認字節數組bytes中的字符轉化為UNICODE。
假設,bytes本是按GB編碼的,構造方法在發現一個最高位為0的byte就作為ascii字符處理,最高位為1就和后面的一個byte合成中文字符, 再轉換編碼。
可以看出,在這個過程中,編碼選擇錯誤就會導致程序按錯誤方法辨認bytes,亂碼就出現了。
在這里產生的亂碼,很多時候還可以通過.getByte()方法修復,還沒有后面的嚴重。
如:
"中".getBytes("iso-8859-1");
因為iso-8859-1中沒有中文,所以"中"的值被替換成63,顯示'?',無法判斷以前是什么值。
所以如下String將被破壞掉:
new String("中文".getBytes("iso-8859-1"),"iso-8859-1");
如果目標編碼方式支持中文,就不會損壞String:
new String("中文".getBytes("utf-8"),"utf-8");
Java在顯示字符時,還需要進行一次轉換,把UNICODE字符轉換成用于顯示的字符編碼形式。
很多時候,這個過程是自動的,會按系統的默認編碼(一般是GBK)轉換String。
如果和頁面編碼不一樣,就會出現亂碼,雖然在Java的程序中只有一種編碼,輸出卻可以有不同的編碼。
有時候,我們需要用 iso-8859-1格式分解String的中文,以便在不支持中文的系統中存儲:
new String("中文".getBytes("GBK"),"iso-8859-1");
先通過GBK等支持中文的編碼方式分解為byte數組,再做為iso-8859-1字符組成字符串,就避免了被替換為Char(63)。
=========================================================================
示例程序
public static void main(String[] args)
{
String str = "中國";
printBytes("中國的UNICODE編碼:", str.getBytes(Charset.forName("unicode")));
printBytes("中國的GBK編碼:", str.getBytes(Charset.forName("GBK")));
printBytes("中國的UTF-8編碼:", str.getBytes(Charset.forName("UTF-8")));
}
public static void printBytes(String title, byte[] data)
{
System.out.println(title);
for (byte b : data)
{
System.out.print("0x" + toHexString(b) + " ");
}
System.out.println();
}
public static String toHexString(byte value)
{
String tmp = Integer.toHexString(value & 0xFF);
if (tmp.length() == 1)
{
tmp = "0" + tmp;
}
return tmp.toUpperCase();
}
上例的輸出結果為:
中國的UNICODE編碼:
0xFE 0xFF 0x4E 0x2D 0x56 0xFD
中國的GBK編碼:
0xD6 0xD0 0xB9 0xFA
中國的UTF-8編碼:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
最近一直在做項目遷移的
工作 ,由傳統的ASP.NET轉到
Windows Azure,這里介紹一下Azure的
配置管理 。在傳統的WinForm或ASP.NET項目下,配置文件為
web .config(app.config),而Cloud Service項目的配置文件是*.cscfg。
一個環境一個配置文件,并且提供可視化編輯。
但這里的配置有一個缺點,目前Azure SDK2.0還不支持多級配置,傳統配置下的appSettings和connectionStrings在這里只有合并了。在保證對現有系統最小影響的改動下,支持Azure的配置只需要引入一個對象CloudConfigurationManager,據MSDN介紹,CloudConfigurationManager可以智能識別當前運行的環境,讀取配置對象,也就說:當你的應用運行在傳統的本地IIS時,他會讀取Web.config;反過來,當你的應用運行在Cloud上,它會讀取cscfg。
既然有了類庫的支持,我們對其封裝一下即可。注意在Azure配置中,appSettings和connectionStrings是同一級的,用CloudConfigurationManager.GetSetting就可以讀到,當然,這時appSettings和connectionStrings的所有配置Key不能有同名的。如果CloudConfigurationManager.GetSetting獲取的Value為空,說明此Key有可能是App(Web).config下面的connectionStrings節點配置。
public static class SettingsManager
{
/// <summary>
/// 獲取Azure或App(Web).config下的配置節點及連接字符串
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static string GetSetting(string key)
{
string value = CloudConfigurationManager.GetSetting(key);
if (string.IsNullOrEmpty(value))
{
if (null != ConfigurationManager.ConnectionStrings[key])
value = ConfigurationManager.ConnectionStrings[key].ConnectionString;
}
return value;
}
}
這樣,一個簡單的配置讀取類就寫好了,將系統中所有讀取配置的方法統一改成SettingsManager.GetSetting(key)即可。本地開發時,可以拋棄Azure的模擬器(硬件要求高),從而選擇我們較為熟悉本地IIS;Azure用于部署QA/生產環境/預部署,一種讀取方式,適應兩種場景。
Azure配置支持在線修改,避免使用遠程桌面手動操作。
Selenium 2 最大的更新就是集成了WebDriver。這兩者是什么關系呢?如果你搜索WebDriver,第一條結果是Selenium。其實WebDriver和Selenium可以說是在實現UI Automation的競爭對手。Selenium是運行在JavaScript的sandbox里面,所以很容易就支持不同的瀏覽器;而WebDriver則是直接操作瀏覽器本身,更接近用戶的真實操作,但正因為如此,所以WebDriver在多瀏覽器/操作系統的支持上就要落后于Selenium。不過從Selenium 2開始,這兩個項目合并了,可以繼續用原來的Selenium,也可以考慮遷移到WebDriver。我個人認為WebDriver應該是以后的大趨勢,還是值得遷移的。至于你信不信,我反正是信了。
作為一個輕量級的UI Automation框架,需要寫一些驅動它的代碼,大部分人會選擇
JUnit ,因為JUnit是
單元測試 的事實標準;但是我會用
TestNG 。這些UI Automation的東西,它們本身不是單元測試,而且也沒有太多單元測試的風格。
從一段簡單的測試開始
public class GoogleTest {
@Test
public void search(ITestContext context) {
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");
WebElement element = driver.findElement(By.name("q"));
element.sendKeys("magus");
element.submit();
Assert.assertTrue(driver.getTitle().contains("magus"), "Something wrong with title");
}
}
TestNG應用了Java的Annotations,只需要在測試方法上面打上@Test就可以標示出search是一個測試方法。用TestNG運行測試還需要一個testng.xml的文件,文件名其實可以隨便起,沒有關系的。
<suite name="Magus demo" verbose="2">
<test name="Search function">
<classes>
<class name="
test .GoogleTest">
<methods>
<include name="search" />
</methods>
</class>
</classes>
</test>
</suite>
我想讓測試更加靈活,1. 可以配置使用任意支持的瀏覽器進行測試;2. 配置所有
Google 的URL;3. 配置搜索的關鍵字。修改后的代碼:
public class GoogleTest {
WebDriver driver;
@Parameters({"browser"})
@BeforeTest
public void setupBrowser(String browser){
if (browser.equals("firefox")){
driver = new FirefoxDriver();
} else {
driver = new ChromeDriver();
}
}
@Parameters({ "url", "keyword" })
@Test
public void search(String url, String keyword, ITestContext context) { driver.get(url);
WebElement element = driver.findElement(By.name("q"));
element.sendKeys(keyword);
element.submit();
Assert.assertTrue(driver.getTitle().contains(keyword), "Something wrong with title"); }
}
testng.xml
<suite name="Magus demo" verbose="2">
<parameter name="browser" value="firefox" />
<parameter name="url" value="http://www.google.com" />
<parameter name="keyword" value="magus" />
<test name="Search function" preserve-order="true">
<classes>
<class name="test.GoogleTest">
<methods>
<include name="setupBrowser" />
<include name="search" />
</methods>
</class>
</classes>
</test>
</suite>
利用TestNG的@Parameters標簽,讓測試方法從testng.xml里面讀取參數,實現參數化。在testng.xml的配置中,test節點需要增加一個屬性的配置: preserve-order=”true”。這個preserve-order默認是false,在節點下面的所有方法的執行順序是無序的。把它設為true以后就能保證在節點下的方法是按照順序執行的。TestNG的這個功能可以方便我們在testng.xml里面拼裝測試。假設我們有很多獨立的測試方法,例如
navigateCategory
addComment
addFriend
login
logout
就可以在testng.xml里面拼出不同的測試,例如
<test name="Add friend" preserve-order="true">
<classes>
<class name="test.GoogleTest">
<methods>
<include name="login" />
<include name="addFriend" />
<include name="logout" />
</methods>
</class>
</classes>
</test>
<test name="Add comment to category" preserve-order="true">
<classes>
<class name="test.GoogleTest">
<methods> <include name="login" />
<include name="navigateCategory" />
<include name="addComment" />
<include name="logout" />
</methods>
</class>
</classes>
</test>
TestNG比JUnit更加適合做一些非單元測試的事情,不是說JUnit不好,而是不能把JUnit當成萬能的錘子,到處釘釘子。WebDriver的API比Selenium的更加簡潔,會是以后的大趨勢。
之前有
IBM Rational Appscan使用詳細說明的一篇
文章 ,主要是針對掃描過程中配置設置等.本文將介紹針對掃描結果的分析,也是一次完整的滲透
測試 必須經歷的環節.
掃描開始的時候,Appscan會詢問是否保存掃描結果,同時下方有進度條顯示掃描的進度.
在掃描過程中,如果遇到任何連接問題或其他任何問題,可以暫停掃描并在稍后繼續進行.如第一篇文章中講的掃描包括兩個階段-探索、測試.Appscan種的Scan Expert和HP WebInspect中的建議選項卡類似,Scan Expert分析掃描的配置,然后針對變化給出配置建議,目的是為了更好的執行一次掃描.可以選擇忽略或者執行這些建議.
Appscan的窗口大概分三個模塊,Application Links(應用鏈接), Security Issues(安全問題), and Analysis(分析),如下圖所示:
Application Links Pane(應用程序結構)
這一塊主要顯示網站的層次結構,基于URL和基于內容形式的文件夾和文件等都會在這里顯示,在旁邊的括號里顯示的數字代表存在的漏洞或者安全問題.通過右鍵單擊文件夾或者URL可以選擇是否忽略掃描此節點.Dashboard窗格會根據漏洞嚴重程序,高中低列出網站存在的問題情況,因此Dashboard將反映一個應用程序的整體實力。
Security Issues Pane(安全問題)
這個窗格主要顯示應用程序中存在的漏洞的詳細信息.針對沒一個漏洞,列出了具體的參數.通過展開樹形結構可以看到一個特定漏洞的具體情況,如下所示:
根據掃描的配置,Appscan會針對各種諸如
SQL 注入的關鍵問題,以及像郵件地址模式發現等低危害的漏洞進行掃描并標識出來.因為掃描策略選擇了默認,Appscan會展示出各種問題的掃描情況.右鍵單擊某個特定的漏洞可以改變漏洞的的嚴重等級為非脆弱,甚至可以刪除.
Analysis Pane(分析)
選擇Security Issues窗格中的一個特定漏洞或者安全問題,會在Analysis窗格中看到針對此漏洞或者安全問題的四個方面:Issue information(問題信息), Advisory(咨詢), Fix Recommendation(修復建議), Request/Response(請求/相應).
Issue information(安全問題信息)
Issue information 標簽下給出了選定的漏洞的詳細信息,顯示具體的URL和與之相關的安全風險。通過這個可以讓安全分析師需要做什么,以及確認它是一個有效的發現。
Advisory(咨詢)
在此選項卡,你可以找到問題的技術說明,受影響的產品,以及參考鏈接。
Fix Recommendation(修復建議)
本節中會提到解決一個特定問題所需要的步驟.
Request/Response(請求/響應)
此標簽顯示發送給應用程序測試相關反應的具體請求的細節.在一個單一的測試過程中,根據安全問題的嚴重性會不止發送一個請求.例如,檢查SQL盲注漏洞,首先AppScan中發送一個正常的請求,并記錄響應。然后發送一個SQL注入參數,然后再記錄響應.同時發送另外一個請求,來判斷條件,根據回顯的不同,判斷是否存在脆弱性漏洞。在此選項卡,有以下一些標簽.如圖:
Show in Browser(在瀏覽器顯示),讓你在瀏覽器看到相關請求的反應,比如在瀏覽器查看跨站腳本漏洞.實際上會出現警從Appscan發出的彈窗信息.
Report False Positive(報告誤報),如果發現誤報,可以通過此標簽發送給Appscan團隊.
Manual
Test (手動測試),單擊此項之后會打開一個新的窗口,允許您修改請求并發送來觀察響應.這個功能類似Burp Suite中的"repeate"選項.
Delete Variant(變量刪除),從結果中刪除選中的變量.
Set as Non-vulnerable(非脆弱性設置),選取的變量將被視為非脆弱性.
Set as Error Page(設置為錯誤頁面), 有時應用程序返回一個定制的錯誤頁面,通過此選項可以設置錯誤頁面,避免Appscan因為掃描響應為200而誤報.
Understanding the Toolbar(了解工具欄)
Scan按鈕,開始掃描探測,繼續掃描探測.
Manual Explore(手動掃描)按鈕可以用于如果只想掃描特定的URL或者網站的一部分,可以記錄輸入鏈接,然后點擊"Continue with Full Scan(繼續全面掃描)",Appscan就只會掃描手動掃描設置下的鏈接.
Scan Configuration(掃描配置) 按鈕會打開配置向導.
通過點擊report按鈕,可以生成一份詳細的掃描分析報告.
Scan Log(掃描日志)記錄AppScan中進行的掃描的每一個動作。因此,使用此功能,您可以跟蹤所有的活動。例如,掃描運行時,你可以查看此時AppScan中正在尋找什么。
Analyze JavaScript(分析Javascript)按鈕執行的JavaScript分析,發現廣泛的客戶端的問題,如基于DOM的跨站腳本.
可以在View Application Data下查看其他各種結果。比如訪問的網址,斷開的鏈接,JavaScript,Cookies等.
以上是對Appscan中的工具功能進行簡單的了解,繼續進行結果分析,可能需要先解決高的嚴重性的漏洞或者安全問題.首先選擇一個脆弱的網址或參數的分析,如下圖所示:
在 分析( analysis)選項卡下會自動獲得相關的強調細節,首先需要判斷是否是一個脆弱性漏洞,或者是一個誤報.這個判斷完全取決與你的技術水平,如果確定是誤報,可以右鍵進行刪除.如果是正確的判斷,可以繼續分析下一個掃描結果,全部分析完成可以生成一個分析報告.
下面的一些提示將有對分析有所幫助:
Tips for Analysing(分析注意事項)
1.分析掃描結果的同時,如果發現不是你的應用程序有關的問題,可以點擊右上角的Vulnerability-->State-->Noise.這個掃描將會完全從列表中刪除此掃描結果.如果想顯示,可以在View-->Show issues Marker as Noise(顯示標記為雜訊的問題),將會顯示帶有刪除線的灰色文本中的問題.
2.如果開發團隊針對一個特定的漏洞進行了修復,不必要再次掃描整個應用程序,來進行重新測試該問題.只需要點擊URL,選擇"Retest the Issues Found",如果有發現新的問題,會自動添加到掃描結果中.
3.CVSS設置可以調整特定漏洞的嚴重性,想改變漏洞嚴重性,右鍵單擊一個漏洞,Severity-->CVSS settings.
4.工具菜單中的"Manual Test(手動測試)"選項可以幫助進行手動發送攻擊請求,而且可以保存當前掃描下的結果,編輯請求,發送后,點擊"Save"保存當前的掃描測試.
5.Appscan掃描過程中會有很多測試,掃描結果中只顯示發現的漏洞的測試,如果需要顯示所有的測試(包括非脆弱的結果),需要選擇Scan Configuration(掃描配置)-->Test 下的"Save Non-vulnerable Test Variant Information"選項.完成掃描之后可以在View-->Non-vulnerable Variants下查看到.
6.如果想掃描一個特定的URL或一個應用程序的特定部分,可以先針對整個應用程序進行探測而不進行測試.選擇掃描配置向導下的"Start with Automatic Explore Only"選項.然后輸入要掃描的網址,進行掃描.
7.當需要掃描一個正在使用的網站,有可能會導致服務癱瘓,需要確保開發團隊有意識到這個后果.
8.Test Malware(惡意軟件測試):這個會分析網站中的惡意的鏈接.可以選擇Scan-->Test For Malware,如果有任何發現,也會被添加掃掃描結果中.
Generating Reports(生成報告)
在分析結束之后可以針對所有確定的結果進行生成報告.其中包括為了解決改問題需要遵循的補救措施的報告.報告是可以根據需求進行定制的,例如可以為不同的開發團隊設置不同的模板.比如針對公司標志,封面頁,報告標題等進行不同的定制。
在上圖中,可以看到所有可選的參數.
Tools(工具)
本節介紹Tools中的Power Tools,該工具是為了更好的對結果進行分析.
Authentication Tester(認證測試)
幫助執行針對應用程序用戶名和密碼進行暴力猜解,結果取決于密碼策略字典強大與否.
Connection Test(連接測試)
可以用來ping一個網站,僅此而已.
Encode/Decode(編碼/解碼)
分析掃描結果的同時,可能會遇到許多的地方需要進行編碼和解碼.
HTTP Request Editor(Http請求編輯器)
可以修改請求的值來測試應用程序針對請求返回的不同響應.
這篇文章是Rational Appscan使用中的一部分內容,重要的是牢記,工具值提供結(在某些情況下甚至都可能不提供你所有的結果),從安全分析師的觀點,提升個人技術技能才是最重要的,從而可以判斷發現的是否是誤報還是真正存在漏洞.
開發環境:
Win XP + eclipse-jee-helios(版本號3.6) + ADT(版本10.0.1) +
Android SDK(版本10);
在Android軟件的開發過程中,可以使用Junit測試框架。在Junit中可以得到組件,可以模擬發送事件和測試程序處理的正確性。
第一步:在新建項目中,創建待測試的業務類,在cn.hao.service包中,代碼如下:
package cn.hao.service;
//業務類,待測試的兩個方法
public class PersonaService {
public void save(String username){
String sub = username.substring(6);
}
public int add(int a,int b){
return a+b;
}
}
說明:對于save()方法,如果參數為null,那么這個方法會發生錯誤;對add()方法,我們測試相加返回的相加結果是否正確。
在AndroidManifest.xml中加入如下代碼:
<uses-library android:name="android.test.runner"/>
<instrumentation android:name="android.test.instrumentationTestRunner"
android:targetPackage="cn.hao.JunitTest" android:label="
Test for My App" />
引入的位置如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.hao.JunitTest"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="cn.hao.test.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation android:name="android.test.instrumentationTestRunner"
android:targetPackage="cn.hao.JunitTest" android:label="App Test"
/>
</manifest>
說明:在項目中使用單元測試,就是檢查程序及處理結果的正確性。
第三步:新建一個類,測試業務類,代碼如下:
package cn.hao.junit;
import junit.framework.Assert;
import cn.hao.service.PersonaService;
import android.test.AndroidTestCase;
public class PersonServiceTest extends AndroidTestCase {
public void testSave() throws Exception {
PersonaService service = new PersonaService();//new出測試對象
service.save(null);
}
public void testAdd() throws Exception {
PersonaService service = new PersonaService();
int actual = service.add(1, 2);
Assert.assertEquals(3, actual);
}
}
注意:該類需繼承單元測試框架父類android.test.AndroidTestCase類,測試方法最好是拋出異常給測試框架。方法Assert.assertEquals(3, actual)中參數3是期望(理論上)返回的果,actual是實際上返回的結果。
第四步:運行測試類
在大綱OutLine視圖中,右擊測試方法->Run As->Android Junit Test,會將項目自動部署到模擬器上,測試的結果會以顏色的形式顯示,綠色表示方法正確,否則該方法不正確,Eclipse會給出詳細的說明,根據幫助文檔可以查看相應的錯誤信息。
如測試上述testSave()方法時,會給出如下提示:
當然,save()從第六位開始取子字符串,但是該方法現在的參數為null,會發生空指針異常。
軟件安全的最大風險是檢驗工具及過程不透明的本質,以及不同的檢驗技術(例如自動化動態
測試 )不能覆蓋假陰性錯誤的潛在可能性。
盡管安全
軟件開發 生命周期(SDLC)有很多相關的最佳實踐,但大多數組織依然有一種傾向,那就是主要依賴測試去構建安全的軟件。當前的測試方法有一個最嚴重的副作用,即組織不太清楚哪些已經被其解決方案測試過,而(甚至更重要的是)還有哪些未被測試過。我們的研究表明,任何單一的自動化保證機制最多可以檢驗44%的安全需求。NIST 靜態分析工具博覽會發現,Tomcat中有26個已知的漏洞,但所有靜態分析工具綜合起來只報告了其中4個警告。因為依賴不透明的檢驗過程是一種普遍存在的習慣,甚至已經成為行業標準,因此許多組織在構建安全的軟件時,滿足于把測試作為最主要的手段。
舉個例子,假設你雇用一家咨詢公司為你的軟件執行滲透測試。許多人將這種測試稱為“黑盒”(基于同名的質保技術),測試人員沒有詳細的系統內部構件知識(比如系統代碼)。執行測試之后,一成不變地生成一份報告,概括你應用中的幾類漏洞。你修復了漏洞,然后提交應用做回歸測試,下一份報告反饋說“已清除”——也就是說沒有任何漏洞了。或者充其量僅僅告知你,你的應用在同一時間范圍內不會被同樣的測試人員以同樣的方式攻破。但另一方面,它不會告訴你:
你的應用中還有哪些潛在的威脅?
你的應用中哪些威脅“其實不易受到攻擊”?
你的應用中有哪些威脅未被測試人員評估?從運行期的角度來看,哪些威脅無法測試?
測試的時間及其他約束如何影響了結果的可靠性?例如,如果測試人員還有5天時間,他們還將執行哪些其他的
安全測試 ?
測試人員的技能水平有多高?你能否從不同的測試人員或者另一家咨詢公司手中取得一組相同的結果?
以我們的經驗來看,組織無法回答以上大多數問題。黑盒是兩面的:一方面,測試人員不清楚應用的內部結構;而另一方面,申請測試的組織對自己軟件的安全狀況也缺乏了解。并不只是我們意識到了這個問題:Haroon Meer在44con上討論了滲透測試的挑戰。這些問題大多數都適用于任何形式的驗證:自動化動態測試、自動化靜態測試、手工滲透測試以及手工代碼審查。實際上, 近期有一篇論文介紹了源代碼審查中類似的挑戰。
關于需求的實例
為了更好地說明這個問題,讓我們看一些常見的高風險軟件的安全需求,以及如何將常見的驗證方法應用到這些需求上。
需求:使用安全的哈希算法(如SHA-2)和唯一的混淆值(salt value)去哈希(Hash)用戶密碼。多次迭代該算法。
在過去的一年里,LinkedIn、Last FM和Twitter發生了眾所周知的密碼泄露事件,對于此類缺陷,本條需求是具體地、合乎時宜的。
如何應用常見的驗證方法:
自動化運行期測試:不可能訪問已存的密碼,因此無法使用此方法驗證本需求
手工運行期測試:只有另一些開發導致已存密碼轉儲時,才能使用此方法驗證本需求。這并不是你所能控制的,因此你不能依靠運行期測試去驗證本需求。
自動化靜態分析:只有滿足以下條件時,才可以用此方法驗證本需求:
工具清楚身份認證是如何
工作 的(例如,使用了標準的組件,就像Java Realms)
工具清楚應用程序使用了哪個特定的哈希算法
如果應用程序為每次哈希使用了唯一的混淆值,工具要清楚混淆算法和混淆值
實際上,認證有很多實現方法,指望靜態分析方法全面地驗證本需求是不切實際的。更為實際的方案是,使用工具簡單地確認認證程序,并指出必須進行安全的哈希和混淆處理。另一個方案是,你來創建自定義規則,用以鑒定算法和哈希值,確認它們是否符合你專屬的策略,盡管,在我們的經驗中這種實踐極為罕見。
手工代碼審查:對于本需求,這是最可靠的常見驗證方法。手工評估人員能夠理解哪一段代碼中發生了認證,驗證哈希和混淆處理符合最佳實踐。
SQL 注入是最具破壞性的應用漏洞之一。近期發現在
Ruby on Rails中有一個缺陷,在其技術棧上搭建的應用系統會受到SQL注入攻擊。
如何應用常見的驗證方法:
自動化運行期測試:雖然,運行期測試通過行為分析也許能夠發現存在的SQL注入,但是,卻不能證明沒有SQL注入。因此,自動化運行期測試不能充分地驗證本需求
手工運行期測試:與自動化運行期測試一樣具有相同的局限性
自動化靜態分析:通常能夠驗證本需求,特別是當你使用標準類庫訪問SQL數據庫時。你是否將用戶輸入動態地拼接為SQL語句,還是使用正確地變量綁定,工具應該都可以分辨得出來。然而,這是有風險的,在以下場景中靜態分析可能會漏掉SQL注入漏洞:
你在
數據庫 上使用存儲過程,并且無法掃描數據庫代碼。在某些情況下,存儲過程也易受到SQL注入
你使用了一種對象關系映射(ORM)類庫,但你的靜態分析工具不支持這種類庫。對象關系映射也易受到注入。
你使用非標準的驅動或類庫去連接數據庫,并且驅動沒有正確地實現常見地安全控制(比如預編譯語句)
手工代碼審查:與靜態分析一樣,手工代碼審查能夠確認沒有SQL注入漏洞。然而,實際上產品應用中可能有幾百或成千上萬條SQL語句。手工審查每一條語句不僅非常耗時,而且容易出錯。
需求:使用授權檢查以確保用戶無法查看其他用戶的數據。
我們每年都能聽到此類 漏洞新的事例。
如何應用常見的驗證方法:
自動化運行期測試:通過訪問兩個不同用戶數據的方式,使用一個用戶的賬號嘗試訪問另一個用戶的數據,自動化工具能夠在一定程度上完成本需求的測試。然而,這些工具不可能清楚一個用戶賬號的哪些數據是敏感的,也不了解把參數“data=account1”修改為“data=account2”表示違反了授權。
手工運行期測試:通常情況下,手工運行期測試是發現這類漏洞最有效的方法,因為人可以擁有必需的領域知識以探明這類攻擊的位置。然而,在有些情況下,運行期測試人員可能無法全面掌握發現這類缺陷所必需的一切信息。例如,如果附加一個類似于“admin=true”的隱藏參數,使你可以不需授權檢查就能訪問其他用戶的數據。
自動化靜態分析:如果沒有規則的定制,自動化工具通常發現不了這種類型的漏洞,因為它需要對領域的理解能力。例如,靜態分析工具不清楚“data”參數表示條件信息,需要授權檢查。
手工代碼審查:手工代碼審查能夠揭露缺失授權的實體(譯者注,比如代碼),這是使用運行期測試難以發現的,比如添加一個“admin=true”的參數的影響。但是,實際上采用這種方式去驗證是否做了授權檢查費時費力。一處授權檢查可能出現在許多不同部分的代碼中,所以手工審查人員可能需要從頭到尾追蹤數條不同的執行路徑,以檢測是否做了授權。
對你的影響
驗證的不透明的本質,意味著有效的軟件安全需求的管理是必要的。對于已列出的需求,測試人員即可以明確他們已經評估一條具體的需求,也可以明確他們所用到的技術。評論家提出,滲透測試不應該遵循一張“類似于審計的檢查表”,因為沒有檢查表可以覆蓋模糊的范圍和特定領域的漏洞。但是,要靈活地找到獨特的問題,就不可避免地要確定已經充分理解了需求。這種情況與標準的軟件質量保證(QA)非常相似:好的質保測試人員即能夠驗證功能需求,也能夠思考盒子的邊界,想辦法去破壞功能。如果只是簡單、盲目地測試,報告一些與功能需求無關的缺陷,就會顯著降低質量保證的效用。那么為什么要接受較低標準的安全測試呢?
在你執行下一次安全驗證活動之前,確保你有軟件安全需求可用于衡量,并且你要明確屬于驗證范圍內的需求。如果你雇傭手工滲透測試人員或源代碼審查人員,他們就可以相對輕松地確定哪些需求是由他們來測試的。如果你使用某種自動化工具或服務,合作的供應商會表明,哪些需求無法用他們的工具或服務可靠地測試。你的測試人員、產品或服務不可能保證完全沒有假陰性錯誤(例如,保證你的應用中不會受到SQL注入攻擊),但同時也要理解,對它們做測試能夠大大有助于增加你的自信心,有信心你的系統代碼中不包含已知的、可預防的安全漏洞。
拿到一個發行版軟件包后,通常要對軟件包進行非對稱加密驗證(MD5)
首先查看公鑰是否正常安裝:
rpm -qa | grep gpg-pubkey 或者 rpm -qa gpg-pubkey
如果未正常安裝,可先手動進行安裝
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
或者使用安裝介質的源,如:rpm --import /media/Rhel6.4/RPM-GPG-KEY-redhat-release
如果安裝中提示錯誤:
[kevin@PandoraX Rhel6.4]$ rpm --import /media/Rhel6.4/RPM-GPG-KEY-redhat-release
error: cannot get exclusive lock on /var/lib/rpm/Packages
error: cannot open Packages index using db3 - Operation not permitted (1)
error: cannot open Packages database in /var/lib/rpm
error: /media/Rhel6.4/RPM-GPG-KEY-redhat-release: key 1 import failed.
error: cannot get exclusive lock on /var/lib/rpm/Packages
error: cannot open Packages database in /var/lib/rpm
error: /media/Rhel6.4/RPM-GPG-KEY-redhat-release: key 2 import failed.
很可能是由于權限問題造成,更新key需要root身份或者sudo身份進行操作
安裝完成后可正常進行驗證:
rpm -K vsftpd-2.2.2-11.el6.x86_64.rpm
vsftpd-2.2.2-11.el6.x86_64.rpm: rsa sha1 (md5) pgp md5 OK
驗證通過
查看公鑰信息rpm -qi gpg-pubkey-2fa658e0-45700c69
查看詳細驗證信息rpm -vK vsftpd-2.2.2-11.el6.x86_64.rpm
rpm -vvK vsftpd-2.2.2-11.el6.x86_64.rpm
yum源中的gpg校驗
[base]
name=Red Hat Enterprise
Linux baseurl=file:///media/Rhel6.4/Server
enabled=1
gpgcheck=0 (0代表不進行校驗,1為每次都進行校驗)
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
編者按:在
數據庫 技術領域,Michael Stonebraker幾乎是無人不知無人不曉的人物。現年70歲的Stonebraker不僅是Ingres和PostgreSQL的創始人,同時在Informix擔任過技術總監。可以說,Stonebraker是關系型數據庫技術從萌芽走向輝煌的見證人。他最新的項目VoltDB被視為是NewSQL數據庫的代表,在他眼中,這種即擁有傳統
SQL 數據庫血統,又能夠適應
云計算 時代分布式擴展的產品,才代表著數據庫未來的發展方向。
在本文中,數據庫老兵Michael Stonebraker闡述了他對SQL、NoSQL以及NewSQL技術的看法,并解讀了為何NewSQL將對傳統數據庫市場帶來最大的沖擊。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
從已有的成功企業吸取經驗,與最新的技術和趨勢完美結合,這是一切初創企業走向成功的秘訣。
而NewSQL正好具備了這樣的條件。NewSQL越來越受到了人們的關注,究其原因是它保留了過去30多年數據庫技術的精華,同時將現代化的技術架構融入了進來。
那么是不是可以說“SQL已死”呢?
事實上,SQL技術非但沒有消失,反而在大數據時代發揮了更重要的作用。當Facebook去年宣布推出Presto(海量數據查詢引擎)時,我想起了關于NoSQL的一個梗:“Hive從什么時候就開始做SQLon Hadoop了?6年前?”沒錯,盡管NoSQL運動進展的火熱,但不要忘記了,即使是最好的NoSQL平臺也在很久以前就開始研究如何實現SQL了。
好的數據庫設計師都明白一個道理,即數據庫最大的商業價值就是讓人與數據之間形成互動,而SQL是非常擅長實現這個目標的。經過了幾十年的研究,調整,改進,生態系統建設,工具開發以及用戶教育,SQL已經成為一個非常豐富且強大的數據庫語言標準,它帶動了價值上百億美元的市場。無論是架構師還是DBA、開發人員都無法忽視它的價值。
但這并不意味著數據庫領域就沒有創新的空間,企業就應該永遠鎖定在遺留系統之上。
NoSQL運動的興起讓我們了解到,一個分布式,高容錯,基于云的集群化數據庫服務并不是天方夜譚。最早吃過NoSQL這個螃蟹的公司都是些不計代價來實現擴展性的公司,他們必須犧牲一定的互動性從而滿足擴展需求。更關鍵的是,他們沒有其他選擇。當然,早期的用戶沒有多少有勇氣做這種犧牲的。數據庫市場需要一股新的力量,來幫助用戶實現這一目標:能夠快速地擴展從而獲得駕馭快數據流的能力,提供實時的分析和實時的決策,具備云計算的能力,支持關鍵業務系統,還能夠在更廉價的硬件設備上對歷史數據分析性能提升100倍。
然而,實現這些目標并不需要我們重新定義已經成熟的SQL語言。NewSQL就是答案:它能夠使用SQL語句來查詢數據,同時具備現代化,分布式,高容錯,基于云的集群架構。NewSQL結合了SQL豐富靈活的數據互動能力,以及針對大數據和快數據的實時擴展能力。
NoSQL廠商從來都不否認他們需要讓自己的產品更成熟,他們也都了解SQL的價值。傳統數據庫廠商也面臨著嚴峻的考驗,盡管他們擁有良好的查詢接口,但他們需要為自己的產品融入更多靈活、高性能的架構,從而滿足客戶在大數據時代的需求。
你好,今天我要和大家分享一些東西,舉例來說這個在JavaScript中用的很多。我要講講回調(callbacks)。你知道什么時候用,怎么用這個嗎?你真的理解了它在
java 環境中的用法了嗎?當我也問我自己這些問題,這也是我開始研究這些的原因。這個背后的思想是控制反轉( PS:維基百科的解釋是控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。)這個范例描述了框架(framework)的
工作 方式,也以“好萊塢原則:不要打電話給我們,我們會打給你("Hollywood principle - Don't call me, we will call you)”為人們所熟知。
簡單的Java里的回調模式來理解它,具體的例子在下面:
1 interface CallBack {
2 void methodToCallBack();
3 }
4
5 class CallBackImpl implements CallBack {
6 public void methodToCallBack() {
7 System.out.println("I've been called back");
8 }
9 }
10
11 class Caller {
12
13 public void register(CallBack callback) {
14 callback.methodToCallBack();
15 }
16
17 public static void main(String[] args) {
18 Caller caller = new Caller();
19 CallBack callBack = new CallBackImpl();
20 caller.register(callBack);
21 }
22 }
你可能要問我,什么時候用這個或者會問直接調用和回調機制有什么不同呢?
答案是:好吧,這個例子僅僅向你展示了怎樣在java環境中構造這樣的回調函數。當然用那種方式使用它毫無意義。讓我們現在更加深入具體地研究它。
在它之中的思想是控制反轉。讓我們用定時器作為現實中的例子。假設你知道,有一個特別的定時器支持每小時回調的功能。準確地說意思是,每小時,定時器會調用你注冊的調用方法。
具體的例子:
我們想要每小時更新一次網站的時間,下面是例子的UML模型:
回調接口:
讓我們首先定義回調接口:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 // For example: Let's assume that this interface is offered from your OS to be implemented
5 interface TimeUpdaterCallBack {
6 void updateTime(long time);
7 }
8
9 // this is your implementation.
10 // for example: You want to update your website time every hour
11 class WebSiteTimeUpdaterCallBack implements TimeUpdaterCallBack {
12
13 @Override
14 public void updateTime(long time) {
15 // print the updated time anywhere in your website's example
16 System.out.println(time);
17 }
18 }
在我們的例子中系統定時器支持回調方法:
1 // This is the SystemTimer implemented by your Operating System (OS)
2 // You don't know how this timer was implemented. This example just
3 // show to you how it could looks like. How you could implement a
4 // callback by yourself if you want to.
5 class SystemTimer {
6
7 List<TimeUpdaterCallBack> callbacks = new ArrayList<TimeUpdaterCallBack>();
8
9 public void registerCallBackForUpdatesEveryHour(TimeUpdaterCallBack timerCallBack) {
10 callbacks.add(timerCallBack);
11 }
12
13 // ... This SystemTimer may have more logic here we don't know ...
14
15 // At some point of the implementaion of this SystemTimer (you don't know)
16 // this method will be called and every registered timerCallBack
17 // will be called. Every registered timerCallBack may have a totally
18 // different implementation of the method updateTime() and my be
19 // used in different ways by different clients.
20 public void oneHourHasBeenExprired() {
21
22 for (TimeUpdaterCallBack timerCallBack : callbacks) {
23 timerCallBack.updateTime(System.currentTimeMillis());
24 }
25 }
26 }
最后是我們虛擬簡單的例子中的網站時間更新器:
1 // This is our client. It will be used in our WebSite example. It shall update
2 // the website's time every hour.
3 class WebSiteTimeUpdater {
4
5 public static void main(String[] args) {
6 SystemTimer SystemTimer = new SystemTimer();
7 TimeUpdaterCallBack webSiteCallBackUpdater = new WebSiteTimeUpdaterCallBack();
8 SystemTimer.registerCallBackForUpdatesEveryHour(webSiteCallBackUpdater);
9 }
10 }