很多時候人們會使用一些自定義的ClassLoader ,而不是使用系統(tǒng)的Class Loader。大多數(shù)時候人們這樣做的原因是,他們在編譯時無法預(yù)知運行時會需要那些Class。非凡是在那些appserver中,比如tomcat,Avalon-phonix,Jboss中。或是程序提供一些plug-in的功能,用戶可以在程序編譯好之后再添加自己的功能,比如ant, jxta-shell等。定制一個ClassLoader很簡單,一般只需要理解很少的幾個方法就可以完成。
一個最簡單的自定義的ClassLoader從ClassLoader類繼續(xù)而來。這里我們要做一個可以在運行時指定路徑,加載這個路徑下的class的ClassLoader。
通常我們使用ClassLoader.loadClass(String):Class方法,通過給出一個類名,就會得到一個相應(yīng)的Class實例。因此只要小小的改動這個方法,就可以實現(xiàn)我們的愿望了。
源碼:
PRotected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); }else{ c = findBootstrapClass0(name); } }catch(ClassNotFoundException e){ // If still not found, then call findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c;}
Source from ClassLoader.java
First,check JavaAPI doc:上面指出了缺省的loadClass方法所做的幾個步驟。
1. 調(diào)用findLoadedClass(String):Class 檢查一下這個class是否已經(jīng)被加載過了,由于JVM 規(guī)范規(guī)定ClassLoader可以cache它所加載的Class,因此假如一個class已經(jīng)被加載過的話,直接從cache中獲取即可。
2. 調(diào)用它的parent 的loadClass()方法,假如parent為空,這使用JVM內(nèi)部的class loader(即聞名的bootstrap classloader)。
3. 假如上面兩步都沒有找到,調(diào)用findClass(String)方法來查找并加載這個class。
后面還有一句話,在Java 1.2版本以后,鼓勵用戶通過繼續(xù)findClass(String)方法實現(xiàn)自己的class loader而不是繼續(xù)loadClass(String)方法。
既然如此,那么我們就先這么做:)
public class AnotherClassLoader extends ClassLoader { private String baseDir;private static final Logger LOG = Logger.getLogger(AnotherClassLoader.class); public AnotherClassLoader (ClassLoader parent, String baseDir) { super(parent); this.baseDir = baseDir; } protected Class findClass(String name) throws ClassNotFoundException { LOG.debug("findClass " + name); byte[] bytes = loadClassBytes(name); Class theClass = defineClass(name, bytes, 0, bytes.length);//A if (theClass == null) throw new ClassFormatError(); return theClass; } private byte[] loadClassBytes(String className) throws ClassNotFoundException { try { String classFile = getClassFile(className); FileInputStream fis = new FileInputStream(classFile); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { int i = fileC.read(buffer); if (i == 0 i == -1) { break; } buffer.flip(); outC.write(buffer); buffer.clear(); } fis.close(); return baos.toByteArray(); } catch (IOException fnfe) { throw new ClassNotFoundException(className); } } private String getClassFile(String name) { StringBuffer sb = new StringBuffer(baseDir); name = name.replace('.', File.separatorChar) + ".class"; sb.append(File.separator + name); return sb.toString(); }}
[i]Ps:這里使用了一些JDK1.4的nio的代碼:)[/i]
很簡單的代碼,要害的地方就在A處,我們使用了defineClass方法,目的在于把從class文件中得到的二進制數(shù)組轉(zhuǎn)換為相應(yīng)的Class實例。defineClass是一個native的方法,它替我們識別class文件格式,分析讀取相應(yīng)的數(shù)據(jù)結(jié)構(gòu),并生成一個class實例。
還沒完呢,我們只是找到了發(fā)布在某個目錄下的class,還有資源呢。我們有時會用Class.getResource():URL來獲取相應(yīng)的資源文件。假如僅僅使用上面的ClassLoader是找不到這個資源的,相應(yīng)的返回值為null。
同樣我們看一下原來的ClassLoader內(nèi)部的結(jié)構(gòu)。
public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0();//這里 if (cl==null) { // A system class. return ClassLoader.getSystemResource(name); } return cl.getResource(name);}
原來是使用加載這個class的那個classLoader獲取得資源。
public URL getResource(String name) { URL url; if (parent != null) { url = parent.getResource(name); } else { url = getBootstrapResource(name); } if (url == null) { url = findResource(name);//這里 } return url;}
這樣看來只要繼續(xù)findResource(String)方法就可以了。修改以下我們的代碼:
//新增的一個findResource方法protected URL findResource(String name) { LOG.debug("findResource " + name); try { URL url = super.findResource(name); if (url != null) return url; url = new URL("file:///" + converName(name)); //簡化處理,所有資源從文件系統(tǒng)中獲取 return url; } catch (MalformedURLException mue) { LOG.error("findResource", mue); return null; }}private String converName(String name) { StringBuffer sb = new StringBuffer(baseDir); name = name.replace('.', File.separatorChar); sb.append(File.separator + name); return sb.toString();}
好了,到這里一個簡單的自定義的ClassLoader就做好了,你可以添加其他的調(diào)料(比如安全檢查,修改class文件等),以滿足你自己的口味:)
新聞熱點
疑難解答