Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I've made a blog post on this subject, in which the problematics is explained a bit further: <a href="http://yannbane.com/2014/01/manipulating-java-class-loading-mechanisms/" rel="nofollow">http://yannbane.com/2014/01/manipulating-java-class-loading-mechanisms/</a>.</p> <p>After some research I think I've found a way to accomplish exactly what I want. I haven't implemented this system yet (will edit the answer once I do), so I'd like some feedback from people who are more experienced in this regard.</p> <p><strong>This is how I understand URLClassLoaders (and classloaders in general) work:</strong></p> <p><code>URLClassLoader.loadClass()</code> gets automatically called if it’s the current classloader (for the method/class executing). The call is first delegated to its parent classloader, and if nothing is found it uses its own custom <code>findClass()</code> to load it off one of the URLs it has. <code>loadClass()</code> here (and the classloading logic) is just inherited from the regular <code>ClassLoader</code>.</p> <p>The default behavior of this method (<code>loadClass()</code>) is to first delegate the search to the parent, and if parent cannot find the class, only then to call its own <code>findClass()</code>. This method (<code>findClass()</code>) is, by default (in <code>ClassLoader</code>), left unimplemented, and you are supposed to implement it yourself, by essentially getting the bytecodes of the class from somewhere (e.g. a file or a network) and calling <code>defineClass()</code> on them.</p> <p>Once you call <code>defineClass()</code> on some class, and only then, are you registered as the classloader of this class. In all other cases classloading is either delegated to your parent (and you are not the classloader of the class you are loading, paradoxically), or you throw a <code>ClassNotFoundException</code>. You cannot change the classloader of some class at runtime, it is set once it is loaded, and constant.</p> <p>All of my test classes will try to <code>getClass().getClassLoader().loadClass()</code> all of the classes that they reference - including my custom test classes (this is the regular behavior of <em>all</em> classes, not just my tests, to be clear). As long as they’re using standard classes, and other classes from my application that are not to-be-tested classes, this classloading method should be delegated further on to the application classloader. However, as soon as they try to load a to-be-tested class, they need to get their own, specifically loaded, custom version of it.</p> <p>The <strong>use case</strong> of my application is that a user submits a JAR with some class, then a test that expects this class to have certain methods is ran on the class from that JAR (using JUnit), and then the results are sent back to the user.</p> <p><strong>The solution is as follows:</strong></p> <ol> <li>Implement basic versions of the to-be-tested classes so that the tests would compile and run without any submitted JARs. This could (more likely, should) be done by leveraging polymorphism, but is not planned at the moment (this means that the user should most likely extend the “basic” version of the class himself, locally, before sending the class to be tested).</li> <li>Extend <code>URLClassLoader</code> and re-work the classloading logic. <ul> <li>Do the necessary checks (is the class already loaded) that exist in the default implementation.</li> <li>Try to findClass yourself, if it’s not in your URLs, throw a <code>ClassNotFoundException</code>.</li> <li>If the class has already been loaded, or if has been loaded just now from some URL, return the class.</li> <li>If the class has neither been loaded before (by this classloader), nor is it in one of the URLs, delegate the search to your parent.</li> <li>If the parent returns the class, return the class, if not, throw <code>ClassNotFoundException</code> (it will actually be thrown by the parent).</li> </ul></li> <li>For each JAR file with a class that is sent: <ul> <li>Instantiate the custom <code>URLClassLoader</code></li> <li>Add the JAR to it and the specific test class as well</li> <li>Ask it to load the test class. At this point my custom classloading logic kicks in, and it loads my test straight from the disk - anew, without delegating to its parent classloader. Why? It calls <code>defineClass()</code> on my test class, which sets this custom <code>URLClassLoader</code> as the parent of the test class</li> <li>Give the test class to JUnit, which then instantiates it and begins testing</li> </ul></li> <li>Once one of my tests are running, each time they reference any class, they will call their own custom classloader’s <code>loadClass()</code>. First they will search the URLs - so in case they’re referencing a to-be-tested class, it will be loaded from there. In case they are referencing some other application class, or a system class, the custom classloading logic will just delegate this call and an already-loaded (presumably) class would be returned.</li> </ol> <p>As I’ve said - I haven’t implemented this yet, and I would really like if you’d point out my mistakes in the comments.</p> <p>Resources from which I've gathered this information:</p> <ol> <li><a href="http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html" rel="nofollow">http://www.onjava.com/pub/a/onjava/2003/11/12/classloader.html</a></li> <li><a href="http://www.devx.com/Java/Article/31614" rel="nofollow">http://www.devx.com/Java/Article/31614</a></li> <li><a href="http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html" rel="nofollow">http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.html</a></li> <li><a href="http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#loadClass%28java.lang.String,%20boolean%29" rel="nofollow">http://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#loadClass%28java.lang.String,%20boolean%29</a></li> </ol>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload