Skip to main content

How to load classes from JAR or ZIP?

Posted by malenkov on July 25, 2008 at 6:08 AM PDT

I needed to load the classes from the dt.jar archive on the fly. The path to the archive was generated automatically based on the "java.home" system property. The original idea was to use the URLClassLoader, but it could not find classes. I had to write a custom class loader which read an archive and loaded classes on demand. At that instant I realized why the URLClassLoader did not work: I had incorrectly generated the path to the archive and the URLClassLoader for a wonder provided no warning that the archive was not found.

Below is the code of the custom class loader for work with jar- and zip-archives:

public final class ZipClassLoader extends ClassLoader {
   
private final ZipFile file;

    public ZipClassLoader(String filename) throws IOException {
       
this.file = new ZipFile(filename);
    }

   
@Override
   
protected Class findClass(String name) throws ClassNotFoundException {
        ZipEntry entry =
this.file.getEntry(name.replace('.', '/') + ".class");
       
if (entry == null) {
           
throw new ClassNotFoundException(name);
        }
       
try {
           
byte[] array = new byte[1024];
            InputStream in =
this.file.getInputStream(entry);
            ByteArrayOutputStream out =
new ByteArrayOutputStream(array.length);
           
int length = in.read(array);
           
while (length > 0) {
                out.write(array,
0, length);
                length = in.read(array);
            }
           
return defineClass(name, out.toByteArray(), 0, out.size());
        }
       
catch (IOException exception) {
           
throw new ClassNotFoundException(name, exception);
        }
    }
}

Note that it is not necessary to cache classes here, because the ClassLoader caches loaded classes automatically. Also you can override the following methods to search resources in the archive:

    @Override
   
protected URL findResource(String name) {
        ZipEntry entry =
this.file.getEntry(name);
       
if (entry == null) {
           
return null;
        }
       
try {
           
return new URL("jar:file:" + this.file.getName() + "!/" + entry.getName());
        }
       
catch (MalformedURLException exception) {
           
return null;
        }
    }

   
@Override
   
protected Enumeration findResources(final String name) {
       
return new Enumeration() {
           
private URL element = findResource(name);

           
public boolean hasMoreElements() {
               
return this.element != null;
            }

           
public URL nextElement() {
               
if (this.element != null) {
                    URL element =
this.element;
                    this.element = null;
                   
return element;
                }
               
throw new NoSuchElementException();
            }
        };
    }

Perhaps someone may need it because it slightly faster. But I recommend to use the URLClassLoader which is able to work with remote archives and supports security:

URL[] urls = { new URL("jar:file:" + path + "!/") };
return URLClassLoader.newInstance(urls);

If the classes cannot be loaded, double check the correctness of the path.

Related Topics >>

Comments

Thanks. Fixed...

URLClassLoader.newInstance is preferable to the constructor. It actually gives you a subclass of URLClassLoader, and doesn't require the call context to have class loader permissions.