轉:https://blog.csdn.net/seu_calvin/article/details/52315125
先介紹自定義類加載器的應用場景:
(1)加密:Java代碼可以輕易的被反編譯,如果你需要把自己的代碼進行加密以防止反編譯,可以先將編譯後的代碼用某種加密算法加密,類加密後就不能再用Java的ClassLoader去加載類了,這時就需要自定義ClassLoader在加載類的時候先解密類,然後再加載。
(2)從非標準的來源加載代碼:如果你的字節碼是放在數據庫、甚至是在雲端,就可以自定義類加載器,從指定的來源加載類。
(3)以上兩種情況在實際中的綜合運用:比如你的應用需要通過網絡來傳輸 Java 類的字節碼,爲了安全性,這些字節碼經過了加密處理。這個時候你就需要自定義類加載器來從某個網絡地址上讀取加密後的字節代碼,接着進行解密和驗證,最後定義出在Java虛擬機中運行的類。
1、雙親委派模型
雙親委派對於程序的穩定運行很重要,比如這種機制可以防止用戶自定義一個Object類來自己加載,然後就會影響程序運行。
雙親委派的實現非常簡單,主要集中在java.lang.ClassLoader中的loadClass()方法之中,從下面的代碼可以看出:
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) {
//如果沒有加載則交給父加載器的loadClas()方法去加載 父類加載器同樣會執行同樣的操作 查找 然後交給父類加載器
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;
}
}
首先檢查這個類是否被加載過,若沒有被加載則調用父加載器的loadClass()的方法,若父加載器爲空則默認使用啓動類加載器作爲父加載器。如果父類加載失敗,拋出異常後,C=null,則調用自己的findClass()方法進行加載。
雙親委派的的具體邏輯實現在在loadClass()方法中,所以不提倡用戶覆蓋loadClass()方法,而應當把自己的類加載邏輯寫到findClass()中,這樣先執行loadClass()方法類加載失敗後,就會調用自己的findClass()方法來完成加載,這樣就可以保證寫出來的類加載器符合雙親委派規格。(當然也可以直接使用自己的類加載器來加載,需要把方法暴露出來)
package com.jvm.myclassloader;
public class Animail {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.jvm.myclassloader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
private String path;
private String fileName;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public MyClassLoader() {
}
public MyClassLoader(ClassLoader parent) {
super(parent);
// TODO Auto-generated constructor stub
}
public Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
byte [] b=this.getClassBytes();
Class<?> clazz=this.defineClass(null, b, 0, b.length);
return clazz;
}
public byte[] getClassBytes() {
if(path.equals("")||fileName.equals("")){
return null;
}
System.out.println(path+fileName);
File file=new File(path+fileName);
if( file.exists()){
System.out.println("exits");
}
try(FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream bos=new ByteArrayOutputStream();) {
byte[] b=new byte[1024*2];
int n;
while((n=fis.read(b))!=-1){
bos.write(b, 0, n);
}
return bos.toByteArray();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
MyClassLoader mcl=new MyClassLoader();
//mcl.setPath("E:\\UniWorkspace\\JVM\\bin\\com\\jvm\\classloaderpratice\\");
mcl.setPath("D:/test/");
mcl.setFileName("Animail.class");
Class c=mcl.findClass("Animail");//參數沒有用,因爲裏面沒有用
Object obj=c.newInstance();
System.out.println(obj.getClass().toString());
System.out.println(obj.getClass().getClassLoader());
}
}
運行結果:
D:/test/Animail.class
exits
class com.jvm.myclassloader.Animail
com.jvm.myclassloader.MyClassLoader@37f2ae62
我這裏是先在myeclipse中寫好文件Animail.class放到“D:/test/”目錄下,然後執行的結果,如果在findClass()函數中defineClass(null, b, 0, b.length);第一個參數設置爲null,否則會報錯,因爲在myeclipse中寫的Animail.class文件是包含包名的,放到“D:/test/”下,這樣目錄就不對了,會一直提示name不對,它是根據文件中包名定位。從這裏可以看出,加載的類已經加載了,並且類加載器就是自定義的類加載器。
如果想使用name那就需要改一下:
//包名去掉
public class Animail {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
//去掉包名
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader{
private String path;
private String fileName;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public MyClassLoader() {
}
public MyClassLoader(ClassLoader parent) {
super(parent);
// TODO Auto-generated constructor stub
}
public Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
byte [] b=this.getClassBytes();
Class<?> clazz=this.defineClass(name, b, 0, b.length); /////////////使用name
return clazz;
}
public byte[] getClassBytes() {
if(path.equals("")||fileName.equals("")){
return null;
}
System.out.println(path+fileName);
File file=new File(path+fileName);
if( file.exists()){
System.out.println("exits");
}
try(FileInputStream fis = new FileInputStream(file);ByteArrayOutputStream bos=new ByteArrayOutputStream();) {
byte[] b=new byte[1024*2];
int n;
while((n=fis.read(b))!=-1){
bos.write(b, 0, n);
}
return bos.toByteArray();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
MyClassLoader mcl=new MyClassLoader();
mcl.setPath("D:/");
mcl.setFileName("Animail.class");
Class c=mcl.findClass("Animail");
Object obj=c.newInstance();
System.out.println("類名:"+obj.getClass().toString());
System.out.println("類的加載器:"+obj.getClass().getClassLoader());
}
}
運行結果:
這裏需要說明一下使用命令窗口來執行:
首先使用javac Animail.java命令行來編譯在目錄“D:\”下java文件,然後進入javatest目錄下編譯javac MyClassLoader.java文件,然後在該目錄下執行java MyClassLoader 就會輸出如上結果。這裏的findClass(name)中的name屬性就使用到了。
MyClassLoader mcl=new MyClassLoader();
mcl.setPath("E:\\UniWorkspace\\JVM\\bin\\com\\jvm\\myclassloader\\");
mcl.setFileName("Animail.class");
Class c=mcl.findClass("com.jvm.myclassloader.Animail");
Object obj=c.newInstance();
System.out.println(obj.getClass().toString());
System.out.println(obj.getClass().getClassLoader());
如果是在myEclipse中寫且包名已經寫了,則就這樣寫name:“com.jvm.myclassloader.Animail” 是可以運行的,這裏name報錯主要是因爲路徑問題,這裏的name、包名是相對於工程根目錄,而在別的路徑下的文件則不能這樣寫了。
mcl.setPath("D:/test/");
mcl.setFileName("Animail.class");
Class c=mcl.findClass("Animail");
Object obj=c.newInstance();
System.out.println(obj.getClass().toString());
System.out.println(obj.getClass().getClassLoader());
如果把Animail.java文件放在src下default包中(這裏是沒有包名的),編譯後,然後放在D:/test/下也是可以的,從這裏可以看出findClass(name)中的name是Animail.java開頭的報名+文件名 比如:com.jvm.myclassloader.Animail,由於這裏的Animail.java文件中開頭沒有包名,所以就直接是類名Animail。
比如:
Animail文件中加入包名:package com.jvm.myclassloader;
雖然把Animail.class文件放在D:/test2/目錄下 和位置無關
則name就應該是如下寫
MyClassLoader mcl=new MyClassLoader();
mcl.setPath("D:/test2/");
mcl.setFileName("Animail.class");
Class c=mcl.findClass("com.jvm.myclassloader.Animail");
Object obj=c.newInstance();
System.out.println(obj.getClass().toString());
System.out.println(obj.getClass().getClassLoader());