用戶定制自己的ClassLoader可以實現以下的一些應用:
自定義路徑下查找自定義的class類文件,也許我們需要的class文件並不總是在已經設置好的Classpath下面,那麼我們必須向辦法來找到這個類,在這種清理下我們需要自己實現一個ClassLoader。 確保安全性:Java自己嗎很容易被反編譯,對我們自己的要加載的類做特殊處理,如保證通過網絡傳輸的類的安全性,可以將類經過加密後再傳輸,在加密到JVM之前需要對類的字節碼在解密,這個過程就可以在自定義的ClassLoader中實現。 實現類的熱部署:可以定義類的實現機制,如果我們可以檢查已經加載的class文件是否被修改,如果修改了,可以重新加載這個類。findClass()的功能是找到class文件並把字節碼加載到內存中。自定義的ClassLoader一般覆蓋改方法,以便使用不同的加載路徑,然後調用defineClass()解析字節碼。
defineClass()方法用來將byte字節流解析成JVM能夠識別的Class對象。有了這個方法意味著我們不僅僅可以通過class文件實例化對象,還可以通過其他方式實例化對象,如我們通過網絡接收到一個類的字節碼,拿這個字節碼流直接創建類的Class對象形式實例化對象。
自定義的加載器可以覆蓋方法loadClass()以便定義不同的加載機制。
如果自定義的加載器僅覆蓋了findClass(),而未覆蓋loadClass(即加載規則一樣,但加載路徑不同);則調用getClass().getClassLoader()返回的仍然是AppClassLoader!因為真正的load類,還是AppClassLoader.
下面演示一個方法加載指定路徑下(”D:/workspace_jee/JavaTest/src/”)的類文件。
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class PathClassLoader extends ClassLoader
{
private String classPath;
public PathClassLoader(String classPath)
{
this.classPath = classPath;
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
byte[] classData = getData(name);
if (classData == null)
{
throw new ClassNotFoundException();
}
else
{
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getData(String className)
{
String path = classPath + File.separatorChar+className.replace('.', File.separatorChar)+".class";
try
{
InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while((num = is.read(buffer))!=-1)
{
stream.write(buffer,0,num);
}
return stream.toByteArray();
}
catch(IOException e)
{
e.printStackTrace();
}
return null;
}
public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
ClassLoader pcl = new PathClassLoader("D:/workspace_jee/JavaTest/src/");
Class c = pcl.loadClass("classloader.SingleClass");
System.out.println(c.newInstance());
}
}
輸出結果:[email protected]
如果我們從網路上下載一個class文件的字節碼,但是為了安全性在傳輸之前對這個字節碼進行了簡單的加密處理,然後再通過網絡傳輸。當客戶端接收到這個類的字節碼後需要經過解密才能還原成原始的類格式,然後再通過ClassLoader的defineClass()方法創建這個類的實例,最後完成類的加載工作。
比如上面的代碼中,在獲取到字節碼(byte[] classData = getData(name);)之後再通過一個類似以下的代碼:
private byte[] deCode(byte[] src){
byte[] decode = null;
//do something niubility! 精密解碼過程
return decode;
}
將字節碼解碼成所需要的字節碼即可。
JVM默認不能熱部署類,因為加載類時會去調用findLoadedClass(),如果類已被加載,就不會再次加載。
JVM判斷類是否被加載有兩個條件:完整類名是否一樣,ClasssLoader是否是同一個
所以要實現熱部署的話,只需要使用ClassLoader的不同實例來加載。
如果用同一個ClassLoader實例來加載同一個類,則會拋出LinkageError.
Jsp就是一個熱部署的例子。
如下所示:
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ClassReloader extends ClassLoader
{
private String classPath;
String classname = "classloader.SingleClass";
public ClassReloader(String classpath)
{
this.classPath = classpath;
}
protected Class findClass(String name) throws ClassNotFoundException{
byte [] classData = getData(name);
if(classData == null)
{
throw new ClassNotFoundException();
}
else
{
return defineClass(classname,classData,0,classData.length);
}
}
private byte[] getData(String className)
{
String path = classPath+className;
try
{
InputStream is = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while((num = is.read(buffer))!=-1)
{
stream.write(buffer,0,num);
}
return stream.toByteArray();
}
catch(IOException e)
{
e.printStackTrace();
}
return null;
}
public static void main(String[] args)
{
try
{
String path = "D:/workspace_jee/JavaTest/src/classloader/";
ClassReloader reloader = new ClassReloader(path);
Class r = reloader.findClass("SingleClass.class");
System.out.println(r.newInstance());
// ClassReloader reloader2 = new ClassReloader(path);
Class r2 = reloader.findClass("SingleClass.class");
System.out.println(r2.newInstance());
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}
}
這段代碼的運行結果為:
java.lang.LinkageError: loader (instance of classloader/ClassReloader): attempted duplicate class definition for name: "classloader/SingleClass"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at classloader.ClassReloader.findClass(ClassReloader.java:26)
at classloader.ClassReloader.main(ClassReloader.java:62)
比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載他們的類加載器不同,那這兩個類就必定不相等。