摘要:自定義類加載器示例代碼類加載器獲取的字節流字節流解密被加載的類測試代碼以上代碼,展示了自定義類加載器加載類的方法。這就需要自定義類加載器,以便對加載的類庫進行隔離,否則會出現問題對于非的文件,需要轉為類,就需要自定義類加載器。
Java類加載器的作用是尋找類文件,然后加載Class字節碼到JVM內存中,鏈接(驗證、準備、解析)并初始化,最終形成可以被虛擬機直接使用的Java類型。
有兩種類加載器:
1 啟動類加載器(Bootstrap ClassLoader)
由C++語言實現(針對HotSpot VM),負責將存放在
2 其他類加載器(Java語言實現)
1)擴展類加載器(Extension ClassLoader)
負責加載
2)應用程序類加載器(Application ClassLoader),或者叫系統類加載器
負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。
3)自定義類加載器
通過繼承ClassLoader類實現,主要重寫findClass方法。
在JVM虛擬機中,如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索范圍內找不到指定的類時(即ClassNotFoundException),子加載器才會嘗試自己去加載。
也就是說,對于每個類加載器,只有父類(依次遞歸)找不到時,才自己加載 。這就是雙親委派模型。
為什么需要雙親委派模型呢?這可以提高Java的安全性,以及防止程序混亂。
提高安全性方面:
假設我們使用一個第三方Jar包,該Jar包中自定義了一個String類,它的功能和系統String類的功能相同,但是加入了惡意代碼。那么,JVM會加載這個自定義的String類,從而在我們所有用到String類的地方都會執行該惡意代碼。
如果有雙親委派模型,自定義的String類是不會被加載的,因為最頂層的類加載器會首先加載系統的java.lang.String類,而不會加載自定義的String類,防止了惡意代碼的注入。
防止程序混亂
假設用戶編寫了一個java.lang.String的同名類,如果每個類加載器都自己加載的話,那么會出現多個String類,導致混亂。如果本加載器加載了,父加載器則不加載,那么以哪個加載的為準又不能確定了,也增加了復雜度。
我們可以自定義類加載器,只需繼承ClassLoader抽象類,并重寫findClass方法(如果要打破雙親委派模型,需要重寫loadClass方法)。原因可以查看ClassLoader的源碼:
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
這個是ClassLoader中的loadClass方法,大致流程如下:
1)檢查類是否已加載,如果是則不用再重新加載了;
2)如果未加載,則通過父類加載(依次遞歸)或者啟動類加載器(bootstrap)加載;
3)如果還未找到,則調用本加載器的findClass方法;
以上可知,類加載器先通過父類加載,父類未找到時,才有本加載器加載。
因為自定義類加載器是繼承ClassLoader,而我們再看findClass方法:
protected Class> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
可以看出,它直接返回ClassNotFoundException。
因此,自定義類加載器必須重寫findClass方法。
自定義類加載器示例代碼:
類加載器HClassLoader:
class HClassLoader extends ClassLoader { private String classPath; public HClassLoader(String classPath) { this.classPath = classPath; } @Override protected Class> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 獲取.class的字節流 * * @param name * @return * @throws Exception */ private byte[] loadByte(String name) throws Exception { name = name.replaceAll(".", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); // 字節流解密 data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data); return data; } }
被加載的類Car:
public class Car { public Car() { System.out.println("Car:" + getClass().getClassLoader()); System.out.println("Car Parent:" + getClass().getClassLoader().getParent()); } public String print() { System.out.println("Car:print()"); return "carPrint"; } }
測試代碼:
@Test public void testClassLoader() throws Exception { HClassLoader myClassLoader = new HClassLoader("e:/temp/a"); Class clazz = myClassLoader.loadClass("com.ha.Car"); Object o = clazz.newInstance(); Method print = clazz.getDeclaredMethod("print", null); print.invoke(o, null); }
以上代碼,展示了自定義類加載器加載類的方法。
需要注意的是:
執行測試代碼前,必須將Car.class文件移動到e:/temp/a下,并且按包名建立層級目錄(這里為com/ha/)。因為如果不移動Car.class文件,那么Car類會被AppClassLoader加載(自定義類加載器的parent是AppClassLoader)。
上面介紹了Java類加載器的相關知識。對于自定義類加載器,哪里可以用到呢?
主流的Java Web服務器,比如Tomcat,都實現了自定義的類加載器。因為它要解決幾個問題:
1)Tomcat上可以部署多個不同的應用,但是它們可以使用同一份類庫的不同版本。這就需要自定義類加載器,以便對加載的類庫進行隔離,否則會出現問題;
2)對于非.class的文件,需要轉為Java類,就需要自定義類加載器。比如JSP文件。
這里舉一個其它的例子:Java核心代碼的加密。
假設我們項目當中,有一些核心代碼不想讓別人反編譯看到。當前知道有兩種方法,一種是通過代碼混淆(推薦Allatori,商用收費);一種是自己編寫加密算法,對字節碼加密,加大反編譯難度。
代碼混淆如果用Allatori,比較簡便。注意控制自己編寫類的訪問權限即可。接口用public,內部方法用private,其他的用默認的(即不加訪問修飾符)或者protected。代碼混淆這里不過多說明,這里主要介紹一下字節碼加密。
大概的流程可以如下:
.class加密代碼:
@Test public void testEncode() { String classFile = "e:/temp/a/com/ha/Car.class"; FileInputStream fis = null; try { fis = new FileInputStream(classFile); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); data = DESInstance.enCode("1234567890qwertyuiopasdf".getBytes(), data); String outFile = "e:/temp/a/com/ha/EnCar.class"; FileOutputStream fos = new FileOutputStream(outFile); fos.write(data); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
類加載器中解密,查看上文中的:
// 字節流解密 data = DESInstance.deCode("1234567890qwertyuiopasdf".getBytes(), data);
加解密工具類:
public class DESInstance { private static String ALGORITHM = "DESede"; /** * 加密 * * @param key * @param src * @return */ public static byte[] enCode(byte[] key, byte[] src) { byte[] value = null; SecretKey deskey = new SecretKeySpec(key, ALGORITHM); try { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, deskey); value = cipher.doFinal(src); } catch (Exception e) { e.printStackTrace(); } return value; } /** * 解密 * * @param key * @param src * @return */ public static byte[] deCode(byte[] key, byte[] src) { byte[] value = null; SecretKey deskey = new SecretKeySpec(key, ALGORITHM); try { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, deskey); value = cipher.doFinal(src); } catch (Exception e) { e.printStackTrace(); } return value; } }
注意秘鑰是24位,否則會報錯:
java.security.InvalidKeyException: Invalid key length
如果解密密碼錯誤,則是如下錯誤:
javax.crypto.BadPaddingException: Given final block not properly padded
當然,這樣做還是會被反編譯破解,要加大難度,還需要其他處理的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68320.html
摘要:一配置屬性詳解可以在各式各樣不同環境下工作而設計的因此存在著大量的配置參數。以簡便操作,多數配置參數都有默認的配置值也是我們日常使用的必須品。 Hibernate (開放源代碼的對象關系映射框架) Hibernate是一個開放源代碼的對象關系映射框架,它對JDBC進行了非常輕量級的對象封裝, 它將POJO與數據庫表建立映射關系,是一個全自動的orm框架,hibernat...
摘要:執行引擎作用執行字節碼,或者執行本地方法運行時數據區其實就是指在運行期間,其對內存空間的劃分和分配。 雖是讀書筆記,但是如轉載請注明出處https://uestc-dpz.github.io..拒絕伸手復制黨 JVM Java 虛擬機 Java 虛擬機(Java virtual machine,JVM)是運行 Java 程序必不可少的機制。JVM實現了Java語言最重要的特征:即平臺...
摘要:重要以及內部類都是訪問級別,可以注入自定義的。的目的是將包裝成風格以便開發。示例以下示例參考其中的和是自定義的。需要自定義,則實現類,需要自定義,則實現即可總結由于構建過程所用到的是訪問級別的,不能使用自定義的以及是,給了我們擴展的空間。 spring-cloud-openfeign-core-2.1.1.RELEASE.jar 中 HystrixFeign 的詳細構建過程: @Ena...
摘要:中簡單搞定接口訪問,以及簡析掘金最近總結的一些經驗,對于或中使用接口的一些心得。這里,本文將數據結構之學習總結掘金前言前面介紹了的數據結構,今天抽空學習總結一下另一種數據結構。淺析事件傳遞掘金中的事件傳遞主要涉及三個方法和。 Android 系統中,那些能大幅提高工作效率的 API 匯總(持續更新中...) - 掘金前言 條條大路通羅馬。工作中,實現某個需求的方式往往不是唯一的,這些不...
閱讀 2954·2021-11-17 09:33
閱讀 3118·2021-11-16 11:52
閱讀 482·2021-09-26 09:55
閱讀 2975·2019-08-30 15:52
閱讀 1313·2019-08-30 15:44
閱讀 1257·2019-08-30 13:59
閱讀 796·2019-08-30 13:08
閱讀 1157·2019-08-30 10:50