posted @ 2010-09-21 00:16 梧桐夜雨 閱讀(573) | 評論 (0) | 編輯 收藏
2010年1月3日
I threw the full weight of my three bug votes behind bug 6931553 ;)
The fix for bug 6911785 does seem to prevent the endless "hang" on launch (with an active TCP/IP connection but no internet access), but there are still lots of problems:
- launching without internet access can take 20s - 2 minutes!
- launching offline results in the non-sensical "this application has requested to go online" dialog
- the JNLPClassLoader ends up doing DNS lookups which occasionally cause long delays during execution
It's really ugly. We have unhappy customers. I have no idea how to get these bugs fixed. Any suggestions?
posted @ 2010-08-12 23:49 梧桐夜雨 閱讀(210) | 評論 (0) | 編輯 收藏
1, 什么是 ClassLoader?
Java 程序并不是一個可執(zhí)行文件,是需要的時候,才把裝載到 JVM中。ClassLoader 做的工作就是 JVM 中將類裝入內(nèi)存。 而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著您可以創(chuàng)建自己的 ClassLoader
ClassLoader 的基本目標是對類的請求提供服務(wù)。當(dāng) JVM 需要使用類時,它根據(jù)名稱向 ClassLoader 請求這個類,然后 ClassLoader 試圖返回一個表示這個類的 Class 對象。 通過覆蓋對應(yīng)于這個過程不同階段的方法,可以創(chuàng)建定制的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點。該方法的定義如下:
Class loadClass( String name, boolean resolve );
name JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
resolve 參數(shù)告訴方法是否需要解析類。在準備執(zhí)行類之前,應(yīng)考慮類解析。并不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。

defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節(jié)組成的數(shù)組并把它轉(zhuǎn)換成 Class 對象。原始數(shù)組包含如從文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。defineClass 管理 JVM 的許多復(fù)雜、神秘和倚賴于實現(xiàn)的方面 -- 它把字節(jié)碼分析成運行時數(shù)據(jù)結(jié)構(gòu)、校驗有效性等等。不必擔(dān)心,您無需親自編寫它。事實上,即使您想要這么做也不能覆蓋它,因為它已被標記成final的。
C) 方法 findSystemClass
findSystemClass 方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用 defineClass 將原始字節(jié)轉(zhuǎn)換成 Class 對象,以將該文件轉(zhuǎn)換成類。當(dāng)運行 Java 應(yīng)用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關(guān)于 Java 版本 1.2 這個過程變動的詳細信息。) 對于定制的 ClassLoader,只有在嘗試其它方法裝入類之后,再使用 findSystemClass。原因很簡單:ClassLoader 是負責(zé)執(zhí)行裝入類的特殊步驟,不是負責(zé)所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關(guān)心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統(tǒng)。這就是 findSystemClass 的用途。
D) 方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當(dāng)編寫我們自己的 loadClass 時,可以調(diào)用 resolveClass,這取決于 loadClass 的 resolve 參數(shù)的值。
E) 方法 findLoadedClass
findLoadedClass 充當(dāng)一個緩存:當(dāng)請求 loadClass 裝入類時,它調(diào)用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應(yīng)首先調(diào)用該方法。
3, 怎么組裝這些方法
1) 調(diào)用 findLoadedClass 來查看是否存在已裝入的類。
2) 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節(jié)。
3) 如果已有原始字節(jié),調(diào)用 defineClass 將它們轉(zhuǎn)換成 Class 對象。
4) 如果沒有原始字節(jié),然后調(diào)用 findSystemClass 查看是否從本地文件系統(tǒng)獲取類。
5) 如果 resolve 參數(shù)是 true,那么調(diào)用 resolveClass 解析 Class 對象。
6) 如果還沒有類,返回 ClassNotFoundException。
4,Java 2 中 ClassLoader 的變動
1)loadClass 的缺省實現(xiàn)
定制編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發(fā)現(xiàn)一次次地在相同的、很復(fù)雜的方法上編寫變量。 在 Java 1.2 中 loadClass 的實現(xiàn)嵌入了大多數(shù)查找類的一般方法,并使您通過覆蓋 findClass 方法來定制它,在適當(dāng)?shù)臅r候 findClass 會調(diào)用 loadClass。 這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。
2)新方法:findClass
loadClass 的缺省實現(xiàn)調(diào)用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要復(fù)制其它代碼(例如,當(dāng)專門的方法失敗時,調(diào)用系統(tǒng) ClassLoader)。
3) 新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統(tǒng) ClassLoader(而不是固定的從 findSystemClass 調(diào)用它)。
4) 新方法:getParent
為了將類請求委托給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當(dāng)使用特殊方法,定制的 ClassLoader 不能找到類時,可以使用這種方法。
父代 ClassLoader 被定義成創(chuàng)建該 ClassLoader 所包含代碼的對象的 ClassLoader。
運用加密技術(shù)保護Java源代碼
一、為什么要加密?
對 于傳統(tǒng)的C或C++之類的語言來說,要在Web上保護源代碼是很容易的,只要不發(fā)布它就可以。遺憾的是,Java程序的源代碼很容易被別人偷看。只要有一 個反編譯器,任何人都可以分析別人的代碼。Java的靈活性使得源代碼很容易被竊取,但與此同時,它也使通過加密保護代碼變得相對容易,我們唯一需要了解 的就是Java的ClassLoader對象。當(dāng)然,在加密過程中,有關(guān)Java Cryptography Extension(JCE)的知識也是必不可少的。
有幾種技術(shù)可以“模糊”Java類文件,使得反編譯器處理類文件的效果大打折扣。然而,修改反編譯器使之能夠處理這些經(jīng)過模糊處理的類文件并不是什么難事,所以不能簡單地依賴模糊技術(shù)來保證源代碼的安全。
我們可以用流行的加密工具加密應(yīng)用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。這時,最終用戶在運行應(yīng)用之前必須先進行解密。但解密之后,最終用戶就有了一份不加密的類文件,這和事先不進行加密沒有什么差別。
Java 運行時裝入字節(jié)碼的機制隱含地意味著可以對字節(jié)碼進行修改。JVM每次裝入類文件時都需要一個稱為ClassLoader的對象,這個對象負責(zé)把新的類裝 入正在運行的JVM。JVM給ClassLoader一個包含了待裝入類(比如java.lang.Object)名字的字符串,然后由 ClassLoader負責(zé)找到類文件,裝入原始數(shù)據(jù),并把它轉(zhuǎn)換成一個Class對象。
我們可以通過定制ClassLoader,在類文件執(zhí)行之前修改它。這種技術(shù)的應(yīng)用非常廣泛——在這里,它的用途是在類文件裝入之時進行解密,因此可以看成是一種即時解密器。由于解密后的字節(jié)碼文件永遠不會保存到文件系統(tǒng),所以竊密者很難得到解密后的代碼。
由于把原始字節(jié)碼轉(zhuǎn)換成Class對象的過程完全由系統(tǒng)負責(zé),所以創(chuàng)建定制ClassLoader對象其實并不困難,只需先獲得原始數(shù)據(jù),接著就可以進行包含解密在內(nèi)的任何轉(zhuǎn)換。
Java 2在一定程度上簡化了定制ClassLoader的構(gòu)建。在Java 2中,loadClass的缺省實現(xiàn)仍舊負責(zé)處理所有必需的步驟,但為了顧及各種定制的類裝入過程,它還調(diào)用一個新的findClass方法。
這為我們編寫定制的ClassLoader提供了一條捷徑,減少了麻煩:只需覆蓋findClass,而不是覆蓋loadClass。這種方法避免了重復(fù)所有裝入器必需執(zhí)行的公共步驟,因為這一切由loadClass負責(zé)。
不 過,本文的定制ClassLoader并不使用這種方法。原因很簡單。如果由默認的ClassLoader先尋找經(jīng)過加密的類文件,它可以找到;但由于類 文件已經(jīng)加密,所以它不會認可這個類文件,裝入過程將失敗。因此,我們必須自己實現(xiàn)loadClass,稍微增加了一些工作量。
二、定制類裝入器
每一個運行著的JVM已經(jīng)擁有一個ClassLoader。這個默認的ClassLoader根據(jù)CLASSPATH環(huán)境變量的值,在本地文件系統(tǒng)中尋找合適的字節(jié)碼文件。
應(yīng) 用定制ClassLoader要求對這個過程有較為深入的認識。我們首先必須創(chuàng)建一個定制ClassLoader類的實例,然后顯式地要求它裝入另外一個 類。這就強制JVM把該類以及所有它所需要的類關(guān)聯(lián)到定制的ClassLoader。Listing 1顯示了如何用定制ClassLoader裝入類文件。
【Listing 1:利用定制的ClassLoader裝入類文件】
// 首先創(chuàng)建一個ClassLoader對象
ClassLoader myClassLoader = new myClassLoader();
// 利用定制ClassLoader對象裝入類文件
// 并把它轉(zhuǎn)換成Class對象
Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );
// 最后,創(chuàng)建該類的一個實例
Object newInstance = myClass.newInstance();
// 注意,MyClass所需要的所有其他類,都將通過
// 定制的ClassLoader自動裝入
如前所述,定制ClassLoader只需先獲取類文件的數(shù)據(jù),然后把字節(jié)碼傳遞給運行時系統(tǒng),由后者完成余下的任務(wù)。
ClassLoader 有幾個重要的方法。創(chuàng)建定制的ClassLoader時,我們只需覆蓋其中的一個,即loadClass,提供獲取原始類文件數(shù)據(jù)的代碼。這個方法有兩個 參數(shù):類的名字,以及一個表示JVM是否要求解析類名字的標記(即是否同時裝入有依賴關(guān)系的類)。如果這個標記是true,我們只需在返回JVM之前調(diào)用 resolveClass。
【Listing 2:ClassLoader.loadClass()的一個簡單實現(xiàn)】
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中,
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
byte classData[] = /* 通過某種方法獲取字節(jié)碼數(shù)據(jù) */;
if (classData != null) {
// 成功讀取字節(jié)碼數(shù)據(jù),現(xiàn)在把它轉(zhuǎn)換成一個Class對象
clasz = defineClass( name, classData, 0, classData.length );
}
// 必需的步驟2:如果上面沒有成功,
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString() );
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString() );
}
}
Listing 2顯示了一個簡單的loadClass實現(xiàn)。代碼中的大部分對所有ClassLoader對象來說都一樣,但有一小部分(已通過注釋標記)是特有的。在處理過程中,ClassLoader對象要用到其他幾個輔助方法:
findLoadedClass:用來進行檢查,以便確認被請求的類當(dāng)前還不存在。loadClass方法應(yīng)該首先調(diào)用它。
defineClass:獲得原始類文件字節(jié)碼數(shù)據(jù)之后,調(diào)用defineClass把它轉(zhuǎn)換成一個Class對象。任何loadClass實現(xiàn)都必須調(diào)用這個方法。
findSystemClass:提供默認ClassLoader的支持。如果用來尋找類的定制方法不能找到指定的類(或者有意地不用定制方法),則可以調(diào)用該方法嘗試默認的裝入方式。這是很有用的,特別是從普通的JAR文件裝入標準Java類時。
resolveClass:當(dāng)JVM想要裝入的不僅包括指定的類,而且還包括該類引用的所有其他類時,它會把loadClass的resolve參數(shù)設(shè)置成true。這時,我們必須在返回剛剛裝入的Class對象給調(diào)用者之前調(diào)用resolveClass。
三、加密、解密
Java加密擴展即Java Cryptography Extension,簡稱JCE。它是Sun的加密服務(wù)軟件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一種擴展。
JCE 沒有規(guī)定具體的加密算法,但提供了一個框架,加密算法的具體實現(xiàn)可以作為服務(wù)提供者加入。除了JCE框架之外,JCE軟件包還包含了SunJCE服務(wù)提供 者,其中包括許多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。
為簡單計,在本文中我們將用DES算法加密和解密字節(jié)碼。下面是用JCE加密和解密數(shù)據(jù)必須遵循的基本步驟:
步驟1:生成一個安全密匙。在加密或解密任何數(shù)據(jù)之前需要有一個密匙。密匙是隨同被加密的應(yīng)用一起發(fā)布的一小段數(shù)據(jù),Listing 3顯示了如何生成一個密匙。 【Listing 3:生成一個密匙】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
// 為我們選擇的DES算法生成一個KeyGenerator對象
KeyGenerator kg = KeyGenerator.getInstance( "DES" );
kg.init( sr );
// 生成密匙
SecretKey key = kg.generateKey();
// 獲取密匙數(shù)據(jù)
byte rawKeyData[] = key.getEncoded();
/* 接下來就可以用密匙進行加密或解密,或者把它保存
為文件供以后使用 */
doSomething( rawKeyData );
步驟2:加密數(shù)據(jù)。得到密匙之后,接下來就可以用它加密數(shù)據(jù)。除了解密的ClassLoader之外,一般還要有一個加密待發(fā)布應(yīng)用的獨立程序(見Listing 4)。 【Listing 4:用密匙加密原始數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲得密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成加密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.ENCRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并加密
byte data[] = /* 用某種方法獲取數(shù)據(jù) */
// 正式執(zhí)行加密操作
byte encryptedData[] = cipher.doFinal( data );
// 進一步處理加密后的數(shù)據(jù)
doSomething( encryptedData );
步驟3:解密數(shù)據(jù)。運行經(jīng)過加密的應(yīng)用時,ClassLoader分析并解密類文件。操作步驟如Listing 5所示。 【Listing 5:用密匙解密數(shù)據(jù)】
// DES算法要求有一個可信任的隨機數(shù)源
SecureRandom sr = new SecureRandom();
byte rawKeyData[] = /* 用某種方法獲取原始密匙數(shù)據(jù) */;
// 從原始密匙數(shù)據(jù)創(chuàng)建一個DESKeySpec對象
DESKeySpec dks = new DESKeySpec( rawKeyData );
// 創(chuàng)建一個密匙工廠,然后用它把DESKeySpec對象轉(zhuǎn)換成
// 一個SecretKey對象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// Cipher對象實際完成解密操作
Cipher cipher = Cipher.getInstance( "DES" );
// 用密匙初始化Cipher對象
cipher.init( Cipher.DECRYPT_MODE, key, sr );
// 現(xiàn)在,獲取數(shù)據(jù)并解密
byte encryptedData[] = /* 獲得經(jīng)過加密的數(shù)據(jù) */
// 正式執(zhí)行解密操作
byte decryptedData[] = cipher.doFinal( encryptedData );
// 進一步處理解密后的數(shù)據(jù)
doSomething( decryptedData );
四、應(yīng)用實例
前面介紹了如何加密和解密數(shù)據(jù)。要部署一個經(jīng)過加密的應(yīng)用,步驟如下:
步驟1:創(chuàng)建應(yīng)用。我們的例子包含一個App主類,兩個輔助類(分別稱為Foo和Bar)。這個應(yīng)用沒有什么實際功用,但只要我們能夠加密這個應(yīng)用,加密其他應(yīng)用也就不在話下。
步驟2:生成一個安全密匙。在命令行,利用GenerateKey工具(參見GenerateKey.java)把密匙寫入一個文件: % java GenerateKey key.data
步驟3:加密應(yīng)用。在命令行,利用EncryptClasses工具(參見EncryptClasses.java)加密應(yīng)用的類: % java EncryptClasses key.data App.class Foo.class Bar.class
該命令把每一個.class文件替換成它們各自的加密版本。
步驟4:運行經(jīng)過加密的應(yīng)用。用戶通過一個DecryptStart程序運行經(jīng)過加密的應(yīng)用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,啟動被加密應(yīng)用的程序】
import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DecryptStart extends ClassLoader
{
// 這些對象在構(gòu)造函數(shù)中設(shè)置,
// 以后loadClass()方法將利用它們解密類
private SecretKey key;
private Cipher cipher;
// 構(gòu)造函數(shù):設(shè)置解密所需要的對象
public DecryptStart( SecretKey key ) throws GeneralSecurityException,
IOException {
this.key = key;
String algorithm = "DES";
SecureRandom sr = new SecureRandom();
System.err.println( "[DecryptStart: creating cipher]" );
cipher = Cipher.getInstance( algorithm );
cipher.init( Cipher.DECRYPT_MODE, key, sr );
}
// main過程:我們要在這里讀入密匙,創(chuàng)建DecryptStart的
// 實例,它就是我們的定制ClassLoader。
// 設(shè)置好ClassLoader以后,我們用它裝入應(yīng)用實例,
// 最后,我們通過Java Reflection API調(diào)用應(yīng)用實例的main方法
static public void main( String args[] ) throws Exception {
String keyFilename = args[0];
String appName = args[1];
// 這些是傳遞給應(yīng)用本身的參數(shù)
String realArgs[] = new String[args.length-2];
System.arraycopy( args, 2, realArgs, 0, args.length-2 );
// 讀取密匙
System.err.println( "[DecryptStart: reading key]" );
byte rawKey[] = Util.readFile( keyFilename );
DESKeySpec dks = new DESKeySpec( rawKey );
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
SecretKey key = keyFactory.generateSecret( dks );
// 創(chuàng)建解密的ClassLoader
DecryptStart dr = new DecryptStart( key );
// 創(chuàng)建應(yīng)用主類的一個實例
// 通過ClassLoader裝入它
System.err.println( "[DecryptStart: loading "+appName+"]" );
Class clasz = dr.loadClass( appName );
// 最后,通過Reflection API調(diào)用應(yīng)用實例
// 的main()方法
// 獲取一個對main()的引用
String proto[] = new String[1];
Class mainArgs[] = { (new String[1]).getClass() };
Method main = clasz.getMethod( "main", mainArgs );
// 創(chuàng)建一個包含main()方法參數(shù)的數(shù)組
Object argsArray[] = { realArgs };
System.err.println( "[DecryptStart: running "+appName+".main()]" );
// 調(diào)用main()
main.invoke( null, argsArray );
}
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
try {
// 我們要創(chuàng)建的Class對象
Class clasz = null;
// 必需的步驟1:如果類已經(jīng)在系統(tǒng)緩沖之中
// 我們不必再次裝入它
clasz = findLoadedClass( name );
if (clasz != null)
return clasz;
// 下面是定制部分
try {
// 讀取經(jīng)過加密的類文件
byte classData[] = Util.readFile( name+".class" );
if (classData != null) {
// 解密...
byte decryptedClassData[] = cipher.doFinal( classData );
// ... 再把它轉(zhuǎn)換成一個類
clasz = defineClass( name, decryptedClassData,
0, decryptedClassData.length );
System.err.println( "[DecryptStart: decrypting class "+name+"]" );
}
} catch( FileNotFoundException fnfe ) {
}
// 必需的步驟2:如果上面沒有成功
// 我們嘗試用默認的ClassLoader裝入它
if (clasz == null)
clasz = findSystemClass( name );
// 必需的步驟3:如有必要,則裝入相關(guān)的類
if (resolve && clasz != null)
resolveClass( clasz );
// 把類返回給調(diào)用者
return clasz;
} catch( IOException ie ) {
throw new ClassNotFoundException( ie.toString()
);
} catch( GeneralSecurityException gse ) {
throw new ClassNotFoundException( gse.toString()
);
}
}
}
對于未經(jīng)加密的應(yīng)用,正常執(zhí)行方式如下: % java App arg0 arg1 arg2
對于經(jīng)過加密的應(yīng)用,則相應(yīng)的運行方式為: % java DecryptStart key.data App arg0 arg1 arg2
DecryptStart 有兩個目的。一個DecryptStart的實例就是一個實施即時解密操作的定制ClassLoader;同時,DecryptStart還包含一個 main過程,它創(chuàng)建解密器實例并用它裝入和運行應(yīng)用。示例應(yīng)用App的代碼包含在App.java、Foo.java和Bar.java內(nèi)。 Util.java是一個文件I/O工具,本文示例多處用到了它。完整的代碼請從本文最后下載。
五、注意事項
我們看到,要在不修改源代碼的情況下加密一個Java應(yīng)用是很容易的。不過,世上沒有完全安全的系統(tǒng)。本文的加密方式提供了一定程度的源代碼保護,但對某些攻擊來說它是脆弱的。
雖 然應(yīng)用本身經(jīng)過了加密,但啟動程序DecryptStart沒有加密。攻擊者可以反編譯啟動程序并修改它,把解密后的類文件保存到磁盤。降低這種風(fēng)險的辦 法之一是對啟動程序進行高質(zhì)量的模糊處理。或者,啟動程序也可以采用直接編譯成機器語言的代碼,使得啟動程序具有傳統(tǒng)執(zhí)行文件格式的安全性。
另外還要記住的是,大多數(shù)JVM本身并不安全。狡猾的黑客可能會修改JVM,從ClassLoader之外獲取解密后的代碼并保存到磁盤,從而繞過本文的加密技術(shù)。Java沒有為此提供真正有效的補救措施。
不過應(yīng)該指出的是,所有這些可能的攻擊都有一個前提,這就是攻擊者可以得到密匙。如果沒有密匙,應(yīng)用的安全性就完全取決于加密算法的安全性。雖然這種保護代碼的方法稱不上十全十美,但它仍不失為一種保護知識產(chǎn)權(quán)和敏感用戶數(shù)據(jù)的有效方案。
參考資源
在運行時刻更新功能模塊。介紹了一個利用類庫加載器ClassLoader 實現(xiàn)在運行時刻更新部分功能模塊的Java程序,并將其與C/C++中實現(xiàn)同樣功能的動態(tài)鏈接庫方案進行了比較。
Java 技巧 105:利用 JWhich 掌握類路徑。展示一個簡單的工具,它可以清楚地確定類裝載器從類路徑中載入了什么 Java 類。
要了解更多的 Java 安全信息,請閱讀 java.sun.com的 Java Security API 頁。
如何封鎖您的(或打開別人的) Java 代碼。Java 代碼反編譯和模糊處理的指南。
使您的軟件運行起來:擺弄數(shù)字。真正安全的軟件需要精確的隨機數(shù)生成器。
下載本文代碼:EncryptedJavaClass_code.zip
關(guān)于作者
俞良松,軟件工程師,獨立顧問和自由撰稿人。最初從事PB和Oracle開發(fā),現(xiàn)主要興趣在于Internet開發(fā)。您可以通過 javaman@163.net 和我聯(lián)系。
posted @ 2010-04-01 22:52 梧桐夜雨 閱讀(1019) | 評論 (0) | 編輯 收藏
1. 代理模式
代理模式的作用是:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個客戶不想或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。
代理模式一般涉及到的角色有:
抽象角色:聲明真實對象和代理對象的共同接口;
代理角色:代理對象角色內(nèi)部含有對真實對象的引用,從而可以操作真實對象,同時代理對象提供與真實對象相同的接口以便在任何時刻都能代替真實對象。同時,代理對象可以在執(zhí)行真實對象操作時,附加其他的操作,相當(dāng)于對真實對象進行封裝。
真實角色:代理角色所代表的真實對象,是我們最終要引用的對象。(參見文獻1)
以下以《Java與模式》中的示例為例:
抽象角色:
abstract public class Subject
{
abstract public void request();
}
真實角色:實現(xiàn)了Subject的request()方法。
public class RealSubject extends Subject
{
public RealSubject()
{
}
public void request()
{
System.out.println("From real subject.");
}
}
代理角色:
public class ProxySubject extends Subject
{
private RealSubject realSubject; //以真實角色作為代理角色的屬性
public ProxySubject()
{
}
public void request() //該方法封裝了真實對象的request方法
{
preRequest();
if( realSubject == null )
{
realSubject = new RealSubject();
}
realSubject.request(); //此處執(zhí)行真實對象的request方法
postRequest();
}
private void preRequest()
{
//something you want to do before requesting
}
private void postRequest()
{
//something you want to do after requesting
}
}
客戶端調(diào)用:
Subject sub=new ProxySubject();
Sub.request();
由以上代碼可以看出,客戶實際需要調(diào)用的是RealSubject類的request()方法,現(xiàn)在用ProxySubject來代理RealSubject類,同樣達到目的,同時還封裝了其他方法(preRequest(),postRequest()),可以處理一些其他問題。
另外,如果要按照上述的方法使用代理模式,那么真實角色必須是事先已經(jīng)存在的,并將其作為代理對象的內(nèi)部屬性。但是實際使用時,一個真實角色必須對應(yīng)一個代理角色,如果大量使用會導(dǎo)致類的急劇膨脹;此外,如果事先并不知道真實角色,該如何使用代理呢?這個問題可以通過Java的動態(tài)代理類來解決。
2.動態(tài)代理類
Java動態(tài)代理類位于Java.lang.reflect包下,一般主要涉及到以下兩個類:
(1). Interface InvocationHandler:該接口中僅定義了一個方法Object:invoke(Object obj,Method method, Object[] args)。在實際使用時,第一個參數(shù)obj一般是指代理類,method是被代理的方法,如上例中的request(),args為該方法的參數(shù)數(shù)組。這個抽象方法在代理類中動態(tài)實現(xiàn)。
(2).Proxy:該類即為動態(tài)代理類,作用類似于上例中的ProxySubject,其中主要包含以下內(nèi)容:
Protected Proxy(InvocationHandler h):構(gòu)造函數(shù),估計用于給內(nèi)部的h賦值。
Static Class getProxyClass (ClassLoader loader, Class[] interfaces):獲得一個代理類,其中loader是類裝載器,interfaces是真實類所擁有的全部接口的數(shù)組。
Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理類的一個實例,返回后的代理類可以當(dāng)作被代理類使用(可使用被代理類的在Subject接口中聲明過的方法)。
所謂Dynamic Proxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然后該class就宣稱它實現(xiàn)了這些
interface。你當(dāng)然可以把該class的實例當(dāng)作這些interface中的任何一個來用。當(dāng)然啦,這個Dynamic Proxy其實就是一個Proxy,它不會替你作實質(zhì)性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作。(參見文獻3)
在使用動態(tài)代理類時,我們必須實現(xiàn)InvocationHandler接口,以第一節(jié)中的示例為例:
抽象角色(之前是抽象類,此處應(yīng)改為接口):
public interface Subject
{
abstract public void request();
}
具體角色RealSubject:同上;
代理角色:
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
public class DynamicSubject implements InvocationHandler {
private Object sub;
public DynamicSubject() {
}
public DynamicSubject(Object obj) {
sub = obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before calling " + method);
method.invoke(sub,args);
System.out.println("after calling " + method);
return null;
}
}
該代理類的內(nèi)部屬性為Object類,實際使用時通過該類的構(gòu)造函數(shù)DynamicSubject(Object obj)對其賦值;此外,在該類還實現(xiàn)了invoke方法,該方法中的
method.invoke(sub,args);
其實就是調(diào)用被代理對象的將要被執(zhí)行的方法,方法參數(shù)sub是實際的被代理對象,args為執(zhí)行被代理對象相應(yīng)操作所需的參數(shù)。通過動態(tài)代理類,我們可以在調(diào)用之前或之后執(zhí)行一些相關(guān)操作。
客戶端:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Client
{
static public void main(String[] args) throws Throwable
{
RealSubject rs = new RealSubject(); //在這里指定被代理類
InvocationHandler ds = new DynamicSubject(rs); //初始化代理類
Class cls = rs.getClass();
//以下是分解步驟
/*
Class c = Proxy.getProxyClass(cls.getClassLoader(),cls.getInterfaces()) ;
Constructor ct=c.getConstructor(new Class[]{InvocationHandler.class});
Subject subject =(Subject) ct.newInstance(new Object[]{ds});
*/
//以下是一次性生成
Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(),ds );
subject.request();
}
通過這種方式,被代理的對象(RealSubject)可以在運行時動態(tài)改變,需要控制的接口(Subject接口)可以在運行時改變,控制的方式(DynamicSubject類)也可以動態(tài)改變,從而實現(xiàn)了非常靈活的動態(tài)代理關(guān)系(參見文獻2)。
參考文獻:
1. 閻宏,《Java 與模式》
2. 透明,《動態(tài)代理的前世今生》
3. Forest Hou,《Dynamic Proxy 在 Java RMI 中的應(yīng)用》
posted @ 2010-01-03 22:16 梧桐夜雨 閱讀(288) | 評論 (0) | 編輯 收藏
在Java SE1.5中, 增加了一個新的特性:泛型(日本語中的總稱型)。何謂泛型呢?通俗的說,就是泛泛的指定對象所操作的類型,而不像常規(guī)方式一樣使用某種固定的類型去指定。
泛型的本質(zhì)就是將所操作的數(shù)據(jù)類型參數(shù)化,也就是說,該數(shù)據(jù)類型被指定為一個參數(shù)。這種參數(shù)類型可以使用在類、接口以及方法定義中。
一、為什么使用泛型呢?
在以往的J2SE中,沒有泛型的情況下,通常是使用Object類型來進行多種類型數(shù)據(jù)的操作。這個時候操作最多的就是針對該Object進行數(shù)據(jù)的強制轉(zhuǎn)換,而這種轉(zhuǎn)換是基于開發(fā)者對該數(shù)據(jù)類型明確的情況下進行的(比如將Object型轉(zhuǎn)換為String型)。倘若類型不一致,編譯器在編譯過程中不會報錯,但在運行時會出錯。
使用泛型的好處在于,它在編譯的時候進行類型安全檢查,并且在運行時所有的轉(zhuǎn)換都是強制的,隱式的,大大提高了代碼的重用率。
二、
泛型的簡單例子:
首先,我們來看看下面兩個普通的class定義
public class
getString {
private String myStr;
public String getStr() {
return myStr;
}
public void setStr(str) {
myStr = str;
}
}
public class
getDouble {
private Double myDou;
public Double getDou() {
return myDou;
}
public void setDou(dou) {
myDou = dou;
}
}
這兩個class除了所操作的數(shù)據(jù)類型不一致,其他機能都是相同的。現(xiàn)在,我們可以使用泛型來將上面兩個class合并為一個,從而提高代碼利用率,減少代碼量。
public class getObj<T> {
private T myObj ;
public T getObj() {
return myObj;
}
public void setObj<T obj> {
myObj = obj;
}
}
那么,使用了泛型后,如何生成這個class的實例來進行操作呢?請看下面的代碼:
getObj<String> strObj = new getObj<String>();
strObj.setObj(“Hello Nissay”);
System.out.println(strObj.getObj());
getObj<Double> douObj = new getObj<Double>();
douObj.setObj(new Double(“116023”));
System.out.println(douObj.getObj());
三、例子分析
現(xiàn)在我們來分析上面那段藍色字體的代碼:
1、<T>是泛型的標記,當(dāng)然可以使用別的名字,比如。使用<T>聲明一個泛型的引用,從而可以在class、方法及接口中使用它進行數(shù)據(jù)定義,參數(shù)傳遞。
2、<T>在聲明的時候相當(dāng)于一個有意義的數(shù)據(jù)類型,編譯過程中不會發(fā)生錯誤;在實例化時,將其用一個具體的數(shù)據(jù)類型進行替代,從而就可以滿足不用需求。
四、泛型的規(guī)則和限制
通過上述的例子,我們簡單理解了泛型的含義。在使用泛型時,請注意其使用規(guī)則和限制,如下:
1、泛型的參數(shù)類型只能是類(class)類型,而不能是簡單類型。
比如,<int>是不可使用的。
2、可以聲明多個泛型參數(shù)類型,比如<T, P,Q…>,同時還可以嵌套泛型,例如:<List<String>>
3、泛型的參數(shù)類型可以使用extends語句,例如<T extends superclass>。
4、泛型的參數(shù)類型可以使用super語句,例如< T super childclass>。
5、泛型還可以使用通配符,例如<? extends
ArrayList>
五、
擴展
1、extends語句
使用extends語句將限制泛型參數(shù)的適用范圍。例如:
<T extends
collection> ,則表示該泛型參數(shù)的使用范圍是所有實現(xiàn)了collection接口的calss。如果傳入一個<String>則程序編譯出錯。
2、super語句
super語句的作用與extends一樣,都是限制泛型參數(shù)的適用范圍。區(qū)別在于,super是限制泛型參數(shù)只能是指定該class的上層父類。
例如<T super
List>,表示該泛型參數(shù)只能是List和List的上層父類。
3、通配符
使用通配符的目的是為了解決泛型參數(shù)被限制死了不能動態(tài)根據(jù)實例來確定的缺點。
舉個例子:public
class SampleClass < T extends S> {…}
假如A,B,C,…Z這26個class都實現(xiàn)了S接口。我們使用時需要使用到這26個class類型的泛型參數(shù)。那實例化的時候怎么辦呢?依次寫下
SampleClass<A>
a = new SampleClass();
SampleClass<B>
a = new SampleClass();
…
SampleClass<Z>
a = new SampleClass();
這顯然很冗余,還不如使用Object而不使用泛型,呵呵,是吧?
別著急,咱們使用通配符,就OK了。
SampleClass<?
Extends S> sc = new SampleClass();
posted @ 2010-01-03 22:10 梧桐夜雨 閱讀(311) | 評論 (0) | 編輯 收藏