很多时候人们会使用一些自定义的ClassLoader 而不是使用系统的Class Loader大多数时候人们这样做的原因是他们在编译时无法预知运行时会需要那些Class特别是在那些appserver中比如tomcatAvalonphonixJboss中或是程序提供一些plugin的功能用户可以在程序编译好之后再添加自己的功能比如ant jxtashell等定制一个ClassLoader很简单一般只需要理解很少的几个方法就可以完成 一个最简单的自定义的ClassLoader从ClassLoader类继承而来这里我们要做一个可以在运行时指定路径加载这个路径下的class的ClassLoader 通常我们使用ClassLoaderloadClass(String):Class方法通过给出一个类名就会得到一个相应的Class实例因此只要小小的改动这个方法就可以实现我们的愿望了 源码 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 = parentloadClass(name false); }else{ c = findBootstrapClass(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 ClassLoaderjava Firstcheck JavaAPI doc上面指出了缺省的loadClass方法所做的几个步骤 调用findLoadedClass(String):Class 检查一下这个class是否已经被加载过了由于JVM 规范规定ClassLoader可以cache它所加载的Class因此如果一个class已经被加载过的话直接从cache中获取即可 调用它的parent 的loadClass()方法如果parent为空这使用JVM内部的class loader(即着名的bootstrap classloader) 如果上面两步都没有找到调用findClass(String)方法来查找并加载这个class 后面还有一句话在Java 版本以后鼓励用户通过继承findClass(String)方法实现自己的class loader而不是继承loadClass(String)方法 既然如此那么我们就先这么做) public class AnotherClassLoader extends ClassLoader { private String baseDir;private static final Logger LOG = LoggergetLogger(AnotherClassLoaderclass); public AnotherClassLoader (ClassLoader parent String baseDir) { super(parent); thisbaseDir = baseDir; } protected Class findClass(String name) throws ClassNotFoundException { LOGdebug(findClass + name); byte[] bytes = loadClassBytes(name); Class theClass = defineClass(name bytes byteslength);//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 = fisgetChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = ChannelsnewChannel(baos); ByteBuffer buffer = ByteBufferallocateDirect(); while (true) { int i = fileCread(buffer); if (i == || i == ) { break; } bufferflip(); outCwrite(buffer); bufferclear(); } fisclose(); return baostoByteArray(); } catch (IOException fnfe) { throw new ClassNotFoundException(className); } } private String getClassFile(String name) { StringBuffer sb = new StringBuffer(baseDir); name = namereplace( FileseparatorChar) + class; sbappend(Fileseparator + name); return sbtoString(); }} [i]Ps:这里使用了一些JDK的nio的代码)[/i] 很简单的代码关键的地方就在A处我们使用了defineClass方法目的在于把从class文件中得到的二进制数组转换为相应的Class实例defineClass是一个native的方法它替我们识别class文件格式分析读取相应的数据结构并生成一个class实例 还没完呢我们只是找到了发布在某个目录下的class还有资源呢我们有时会用ClassgetResource():URL来获取相应的资源文件如果仅仅使用上面的ClassLoader是找不到这个资源的相应的返回值为null 同样我们看一下原来的ClassLoader内部的结构 public URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader();//这里 if (cl==null) { // A system class return ClassLoadergetSystemResource(name); } return clgetResource(name);} 原来是使用加载这个class的那个classLoader获取得资源 public URL getResource(String name) { URL url; if (parent != null) { url = parentgetResource(name); } else { url = getBootstrapResource(name); } if (url == null) { url = findResource(name);//这里 } return url;} 这样看来只要继承findResource(String)方法就可以了修改以下我们的代码 //新增的一个findResource方法protected URL findResource(String name) { LOGdebug(findResource + name); try { URL url = superfindResource(name); if (url != null) return url; url = new URL(file:/// + converName(name)); //简化处理所有资源从文件系统中获取 return url; } catch (MalformedURLException mue) { LOGerror(findResource mue); return null; }}private String converName(String name) { StringBuffer sb = new StringBuffer(baseDir); name = namereplace( FileseparatorChar); sbappend(Fileseparator + name); return sbtoString();} 好了到这里一个简单的自定义的ClassLoader就做好了你可以添加其他的调料(比如安全检查修改class文件等)以满足你自己的口味) |