With DynamicJarClassLoader you can do this:
ClassLoader loader = new DynamicJarClassLoader(parent, file); Class clzz1 = loader.loadClass("my.package.MyClass"); // ... // JAR is replaced // ... Class clzz2 = loader.loadClass("my.package.MyClass"); // ... clzz1.newInstance(); // loads old "other.package.OtherClass" clzz2.newInstance(); // loads new "other.package.OtherClass"
Let's say MyClass will also load "other.package.OtherClass" sooner or later. The classloader will keep that data loaded, so that clzz1 and clzz2 have access to their own version of OtherClass.
Fancy stuff!
public class DynamicJarClassLoader extends DynamicClassLoader { private final File jar; private long prevLastModified; private final SetresourceNames; public DynamicJarClassLoader(ClassLoader parent, File jar) { super(parent); this.jar = jar; this.prevLastModified = -1L; this.resourceNames = new HashSet (); this.ensureLatestClassLoader(); } public File getJar() { return this.jar; } public Set getResourceNames() { return Collections.unmodifiableSet(this.resourceNames); } private static final long file_idle_timeout = 3 * 1000; @Override public boolean isUpdated() { long jarLastModified = this.jar.lastModified(); boolean willBeUpdated = jarLastModified != this.prevLastModified; if (willBeUpdated && this.prevLastModified != -1L) { if (this.jar.lastModified() > System.currentTimeMillis() - file_idle_timeout) { Logger.notification("Pending new JAR file: %s", this.jar.getAbsolutePath()); willBeUpdated = false; } } if (willBeUpdated) { Logger.notification("Loading new JAR file: %s", this.jar.getAbsolutePath()); this.prevLastModified = jarLastModified; } return willBeUpdated; } @Override public ClassLoader createClassLoader() { final Map resources; this.resourceNames.clear(); try { resources = this.loadCompleteJarFile(); } catch (IOException exc) { throw new IllegalStateException("Failed to load JAR file: " + this.jar.getAbsolutePath(), exc); } this.resourceNames.addAll(resources.keySet()); ClassLoader loader = new BytesClassLoader(this.getParent()) { @Override public byte[] readBytes(String name) { return resources.get(name); } }; return loader; } private final Map loadCompleteJarFile() throws IOException { Map map = new HashMap (); JarFile jf = new JarFile(this.jar); Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { byte[] buf = null; JarEntry entry = entries.nextElement(); if (!entry.isDirectory()) { buf = new byte[(int) entry.getSize()]; InputStream in = jf.getInputStream(entry); int off = 0; while (off != buf.length) { int justRead = in.read(buf, off, buf.length - off); if (justRead == -1) throw new EOFException("Could not fully read JAR file entry: " + entry.getName()); off += justRead; } in.close(); } map.put(entry.getName(), buf); } jf.close(); return map; } } public abstract class DynamicClassLoader extends ClassLoader { private ClassLoader currentLoader; public DynamicClassLoader(ClassLoader parent) { super(parent); this.currentLoader = null; } // public abstract boolean isUpdated(); public abstract ClassLoader createClassLoader(); // @Override public URL getResource(String name) { this.ensureLatestClassLoader(); URL url = this.getParent().getResource(name); if (url != null) return url; return this.currentLoader.getResource(name); } @Override public Enumeration getResources(String name) throws IOException { this.ensureLatestClassLoader(); Enumeration urls = this.getParent().getResources(name); if (urls != null) return urls; return this.currentLoader.getResources(name); } @Override public InputStream getResourceAsStream(String name) { this.ensureLatestClassLoader(); InputStream in = this.getParent().getResourceAsStream(name); if (in != null) return in; return this.currentLoader.getResourceAsStream(name); } public synchronized Class< ? > loadClass(String name) throws ClassNotFoundException { this.ensureLatestClassLoader(); return this.currentLoader.loadClass(name); } // private long lastChecked; private long minCheckInterval = 0; public void setMinCheckInterval(long minCheckInterval) { this.minCheckInterval = minCheckInterval; } public final boolean checkForUpdate() { long now = System.currentTimeMillis(); long elapsed = now - this.lastChecked; if (elapsed < this.minCheckInterval) { // if we checked less than N ms ago, // just assume the loader is not updated. // otherwise we put a major strain on // the file system (?) for no real gain return false; } this.lastChecked = now; return this.isUpdated(); } // public void ensureLatestClassLoader() { if (this.checkForUpdate()) { this.replaceClassLoader(); } } protected void replaceClassLoader() { this.currentLoader = this.createClassLoader(); // protected, so do stuff, if you wish } } public abstract class BytesClassLoader extends ClassLoader { public BytesClassLoader(ClassLoader parent) { super(parent); } protected abstract byte[] readBytes(String path); public synchronized Class< ? > loadClass(String name) throws ClassNotFoundException { Class< ? > found = this.findLoadedClass(name); if (found != null) { return found; } String path = name.replace('.', '/').concat(".class"); byte[] raw = this.readBytes(path); if (raw == null) { return this.getParent().loadClass(name); } return super.defineClass(name, raw, 0, raw.length); } @Override public InputStream getResourceAsStream(String path) { byte[] raw = this.readBytes(path); if (raw == null) return null; return new ByteArrayInputStream(raw); } @Override public URL getResource(String name) { // who uses this anyway? throw new UnsupportedOperationException(); } @Override public Enumeration getResources(String name) throws IOException { // who uses this anyway? throw new UnsupportedOperationException(); } }
No comments:
Post a Comment