ClasscastException in servlet - java

I try to implement a plugin system in a servlet. I've written a class to load plugin that use URLClassLoader to load the jar files and Class.forname to load the class.
Here is my code:
This part create the url class Loader:
public PluginLoader(ServletContext context, String[] pluginName, String[] classToLoad) throws PluginLoaderException{
this.context = context;
urls= new URL[pluginName.length];
nameToURL(pluginName);
//create class loader
loader = new URLClassLoader(urls);
//loading the plug-in
loadPlugin(classToLoad);
}
This one initialize the url:
private void nameToURL(String[] pluginName) throws PluginLoaderException{
try{
for(int i=0;i&ltpluginName.length;i++){
urls[i] = context.getResource(pluginName[i]);
}
}
Finally this one create the object:
private void loadPlugin(String[] classToLoad) throws PluginLoaderException{
try{
iTest = (ITest) Class.forName(classToLoad[0],true,loader).newInstance();
}
catch(Exception e){
throw new PluginLoaderException(e.toString());
}
}
I have managed to create the object because I can manipulate it and retrieve the interface it implements but I can't cast it in ITest to manipulate it in the application. I have a ClassCastException tplugin.toto.Toto cannot be cast to fr.test.inter.ITest .
It's strange because Toto implements ITest.
Does anyone has an idea ?
Thanks

You've created a classoader issue -- when you test with instanceof ITest, you are using the copy of ITest loaded by the default classloader, but you are testing an instance loaded by the URLClassloader. That classloader has loaded its own copy of ITest, which, as far as the JVM is concerned, is a completely different type.

Related

How classloader leak is occured

I am sorry to ask you this basic question but I am not able to understand the concept.
I read many SO post but I could not understand. Could you please give me code example to understand.
As said in this post
Static variables cannot be elected for garbage collection while the class is loaded. They can be collected when the respective class loader (that was responsible for loading this class) is itself collected for garbage.
I understand as per theory that Classloader cannot be collected if it has a reference but I do not understand how it is possible practically.
Could you please kindly explain with a code example?
Many thanks for your help!
Lets see this code to understand how classloader leaks possible
Main.java
public class Main {
public static void main(String...args) throws Exception {
List<Object> list = new ArrayList<>();
loadClass(list);
while (true) {
System.gc();
Thread.sleep(1000);
}
}
private static void loadClass(List list) throws Exception {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
MyCustomClassLoader cl = new MyCustomClassLoader(url);
Class<?> clazz = cl.loadClass("com.test.Foo");
list.add(clazz.newInstance());
cl = null;
}
}
class MyCustomClassLoader extends URLClassLoader {
public MyCustomClassLoader(URL... urls) {
super(urls, null);
}
#Override
protected void finalize() {
System.out.println("*** CustomClassLoader finalized!");
}
}
Foo.java
public class Foo {
public Foo() {
System.out.println("Test ClassLoader: " + this.getClass().getClassLoader());
}
#Override
protected void finalize() {
System.out.println( this + " finalized!");
}
}
The output of this as follows:
Test ClassLoader: com.test.MyCustomClassLoader#71dac704
So, here we can see "*** CustomClassLoader finalized!" is not called and this is because MyCustomClassLoader is holding a reference of object list as the instances loaded by classloader are kept in it.
Now, lets change the code a bit, so here we will set list to null
public static void main(String...args) throws Exception {
List<Object> list = new ArrayList<>();
loadClass(list);
while (true) {
System.gc();
Thread.sleep(1000);
list = null;
}
}
And now see the output
Test ClassLoader: com.test.MyCustomClassLoader#71dac704
com.test.Foo#650de12 finalized!
*** CustomClassLoader finalized!
I am posting my understanding hope it helps,
Background understanding:
Simple way to understand this is to take an example of a Tomcat or any such application. Which is java based.
Tomcat can run multiple webapps. Even if you deploy same application with different name they will be treated differently. Here these both applications will have same classes but still they are treated differently. So here comes the class loaders.
So you can think in a way like Tomcat is creating a class loader for each application and loading them under it.
Reclaiming of loaders: above if Tomcat is holding reference to the loader object then the loader object will not be reclaimed. And unless loader gets garbage collected the classes loaded by it stays.
So if you shutdown an application, Tomcat will ultimately drefrence it's respective loader so that gc can reclaim it an clean it including the classed loaded by it.
Quick links that may help:
https://stackoverflow.com/questions/2433261/when-and-how-are-classes-garbage-collected-in-java#:~:text=A%20class%20in%20Java%20can,that%20class%20are%20still%20reachable.
https://www.dynatrace.com/resources/ebooks/javabook/class-loader-issues/#:~:text=Classloader%20Cannot%20Be%20Garbage-Collected,hold%20references%20to%20their%20classes.

Error while executing dynamically created JUnit Tests

I am trying to invoke a dynamically created Junit test class using the code below
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
URL classUrl = javaClass.getParent().toFile().toURI().toURL();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { classUrl });
Class<?> clazz = Class.forName(fileName, true, classLoader);
Object obj = clazz.newInstance();
context.getLogger().log("Test Class Loader==>"+obj.getClass().getClassLoader()+"\n");
JUnitCore junit = new JUnitCore();
context.getLogger().log("JUnitCore Class Loader==>"+junit.getClass().getClassLoader()+"\n");
junit.addListener(new TextListener(new PrintStream(outputStream)));
Result result = junit.run(clazz);
return outputStream.toString();
Dynamically created test file
public class SampleJavaFileTest {
String EXPECTED_OUTPUT_STRING="r3plac3";
#Test
public void testReplaceString() {
SampleJavaFile sample = new SampleJavaFile();
String outputString = sample.replaceString("replace","e","3");
Assert.assertEquals(EXPECTED_OUTPUT_STRING, outputString);
}
}
But I get the error as
There was 1 failure:
1) initializationError(JUnitTest)
org.junit.runners.model.InvalidTestClassError: Invalid test class 'JUnitTest':
1. No runnable methods
at org.junit.runners.ParentRunner.validate(ParentRunner.java:511)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:101)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:84)
at org.junit.runners.JUnit4.<init>(JUnit4.java:23)
at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder
.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at org.junit.runner.Computer.getRunner(Computer.java:50)
at org.junit.runner.Computer$1.runnerForClass(Computer.java:31)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:125)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:111)
at org.junit.runners.Suite.<init>(Suite.java:81)
at org.junit.runner.Computer$2.<init>(Computer.java:33)
I tried printing the class loaders of the dynamically created class and the JUnitCore class the results are
Test Class Loader==>java.net.FactoryURLClassLoader#86be70a
JUnitCore Class Loader==>java.net.URLClassLoader#49c2faae
Upon going through various posts the answers provided are to use custom class loaders to resolve this issue. Can you assist on how to create a custom class loader to resolve this issue?
If not custom class loaders , how else can this be resolved?
Thanks!
This might be class loader related issue, try to create URLClassLoader by using constructor and pass class loader of other test class (or just junit class) as is parent class loader, to ensure that JUnit classes are always loaded by the same class loader. And double check that this is right annotation, with right package.
public URLClassLoader(URL[] urls, ClassLoader parent)
so
new URLClassLoader(urlOfToClass, SomeTestOrJUnitClass.class.getClassLoader())
https://docs.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html#URLClassLoader(java.net.URL[],%20java.lang.ClassLoader)
this exception is generated in this junit block of code
List<Method> methods = testClass.getAnnotatedMethods(Test.class);
if (methods.size() == 0) {
errors.add(new Exception("No runnable methods"));
}
So, I'd recommend to double check, that you definitely have annotation #Test for method, and it is really org.junit.Test and this annotation available in runtime.
You can check this by taking klass.getDeclaredMethod("testReplaceString") and printing all annotations from it.
If it doesn't help, then, you can debug Junit library, put breakpoint to exception (please note, this exceptions generated not in the same place, where throwed) and check conditions
BTW, what version of junit do you use?
EDITED:
I've checked your code, I've created a new project in IDEA, added junit 4.13-rc-1 to dependencies and created two files.
public class SampleJavaFileTest {
String EXPECTED_OUTPUT_STRING="r3plac3";
#Test
public void testReplaceString() {
Assert.assertEquals(EXPECTED_OUTPUT_STRING, "r3plac3");
}
}
and another class
public class ClassLoadDynamically {
public static void main(String[] args) throws Exception {
final File fileForClass = new File(SampleJavaFileTest.class.getProtectionDomain().getCodeSource().getLocation().getPath());
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { fileForClass.toURI().toURL() });
Class<?> clazz = Class.forName("SampleJavaFileTest", true, classLoader);
Method testReplaceString = clazz.getDeclaredMethod("testReplaceString");
System.out.println("Get declared methods==>"+ testReplaceString);
System.out.println("Get annotation => "+ testReplaceString.getAnnotation(org.junit.Test.class));
JUnitCore junit = new JUnitCore();
Result result = junit.run(clazz);
System.out.println(result.wasSuccessful());
}
}
I've checked your code, it works in this way. Please check, how do you generate your code dynamically, looks like the the issue in wrong bytecode generation, double check, how do you set annotations to the method.
However, I'm not sure, about your environment configuration on lambda. Anyway, I'd recommend to make your code run locally

Java 9 - add jar dynamically at runtime

I've got a classloader problem with Java 9.
This code worked with previous Java versions:
private static void addNewURL(URL u) throws IOException {
final Class[] newParameters = new Class[]{URL.class};
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class newClass = URLClassLoader.class;
try {
Method method = newClass.getDeclaredMethod("addNewURL", newParameters );
method.setAccessible(true);
method.invoke(urlClassLoader, new Object[]{u});
} catch (Throwable t) {
throw new IOException("Error, could not add URL to system classloader");
}
}
From this thread I learned that this has to be replaced by something like this:
Class.forName(classpath, true, loader);
loader = URLClassLoader.newInstance(
new URL[]{u},
MyClass.class.getClassLoader()
MyClass is the class I'm trying to implement the Class.forName() method in.
u = file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar
String classpath = URLClassLoader.getSystemResource("plugins/myNodes/myOwn-nodes-1.6.jar").toString();
For some reason - I really can't figure out, why - I get a ClassNotFoundException when running Class.forName(classpath, true, loader);
Does someone know what I'm doing wrong?
From the documentation of the Class.forName(String name, boolean initialize, ClassLoader loader) :-
throws ClassNotFoundException - if the class cannot be located by the specified class loader
Also, note the arguments used for the API includes the name of the class using which the classloader returns the object of the class.
Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface.
In your sample code, this can be redressed to something like :
// Constructing a URL form the path to JAR
URL u = new URL("file:/C:/Users/SomeUser/Projects/MyTool/plugins/myNodes/myOwn-nodes-1.6.jar");
// Creating an instance of URLClassloader using the above URL and parent classloader
ClassLoader loader = URLClassLoader.newInstance(new URL[]{u}, MyClass.class.getClassLoader());
// Returns the class object
Class<?> yourMainClass = Class.forName("MainClassOfJar", true, loader);
where MainClassOfJar in the above code shall be replaced by the main class of the JAR myOwn-nodes-1.6.jar.

Java 9, compatability issue with ClassLoader.getSystemClassLoader

The following code adds jar file to the build path, it works fine with Java 8. However, it throws exception with Java 9, the exception is related to the cast to URLClassLoader. Any ideas how this can be solved? an optimal solution will edit it to work with both Java 8 & 9.
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
You've run into the fact that the system class loader is no longer a URLClassLoader. As indicated by ClassLoader::getSystemClassLoader's return type, this was an implementation detail, albeit one that a non-negligible amount of code relied upon.
Judging by the comments, you are looking for a way to dynamically load classes at run time. As Alan Bateman points out, this can not be done in Java 9 by appending to the class path.
You should instead consider creating a new class loader for that. This has the added advantage that you'll be able to get rid of the new classes as they are not loaded into the application class loader. If you're compiling against Java 9, you should read up on layers - they give you a clean abstraction for loading an entirely new module graph.
I have stumbled over this issue a while ago. As many, I had used a method similar to that in the question
private static int AddtoBuildPath(File f)
to dynamically add paths to the classpath at runtime. The code in the question is probably bad style in multiple aspects: 1) assuming that ClassLoader.getSystemClassLoader() returns an URLClassLoader is an undocumented implementation detail and 2) using reflection to make addURL public is maybe another one.
Cleaner way to dynamically add classpaths
In case that you need to use the additional classpath URLs for class loading through „Class.forName“, a clean, elegant and compatible (Java 8 to 10) solution is the following:
1) Write your own class loader by extending URL classloader, having a public addURL method
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2) Declare a (singleton/app wide) object of your classloader
private final MyClassloader classLoader;
and instanciate it via
classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
Note: The system class loader is the parent. Classes loaded though classLoader know those who can be loaded through this.getClass().getClassLoader() but not the other way around.
3) Add additional classpaths whenever needed (dynamically):
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4) Instanciate objects or your app though your singleton classloader via
cls = Class.forName(name, true, classLoader);
Note: Since class loaders try a delegation to the parent class loader prior loading a class (and the parent to its parent), you have to make sure that the class to load is not visible to the parent class loader to make sure that it is loaded through the given class loader. To make this clearer: if you have ClassPathB on your system class path and later add ClassPathB and some ClassPathA to your custom classLoader, then classes under ClassPathB will be loaded through the system classloader and classes under ClassPathA are not known to them. However, if you remove ClassPathB from you system class path, such classes will be loaded through your custom classLoader, and then classes under ClassPathA are known to those under ClassPathB.
5) You may consider passing your class loader to a thread via
setContextClassLoader(classLoader)
in case that thread uses getContextClassLoader.
If you're just looking to read the current classpath, for example because you want to spin up another JVM with the same classpath as the current one, you can do the following:
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
By default the final fields in the $AppClassLoader class cannot be accesed via reflection, an extra flag needs to be passed to the JVM:
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
I was given a spring boot application that runs in Java 8. I had the task to upgrade it to Java 11 version.
Issue faced:
Caused by: java.lang.ClassCastException: jdk.internal.loader.ClassLoaders$AppClassLoader (in module: java.base) cannot be cast to java.net.URLClassLoader (in module: java.base)
Way around used:
Create a class:
import java.net.URL;
/**
* This class has been created to make the code compatible after migration to Java 11
* From the JDK 9 release notes: "The application class loader is no longer an instance of
* java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
* Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
* need to be updated. Note that Java SE and the JDK do not provide an API for applications or
* libraries to dynamically augment the class path at run-time."
*/
public class ClassLoaderConfig {
private final MockClassLoader classLoader;
ClassLoaderConfig() {
this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
}
public MockClassLoader getClassLoader() {
return this.classLoader;
}
}
Create Another class:
import java.net.URL;
import java.net.URLClassLoader;
public class MockClassLoader extends URLClassLoader {
public MockClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
Now set it in the current thread from your main class (Right at the beginning of your application)
Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());
Hope this solution works for your!!!
Shadov pointed to a thread at the oracle community. There is the correct answer:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
The caveats mentioned there are also important:
Caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly using Class.forName("drivername", true, new URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Referring to Edi's Solution this worked for me:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* #return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
Running within Eclipse, you need to set VM Arguments to JUnit Launch/Debug Configuration.
Running with maven via command line you have two options:
Option 1
Add following lines to pom.xml :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
Option 2
run mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"
There's also this guys article that helped me.
I could not find the article but... here: https://github.com/CGJennings/jar-loader
Here's a part of guide inside there there's a jar at release you could read his guide & setup it up.
I just tried it myself download the jar file which include the class file
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public final class classname{
public static void premain(String agentArgs, Instrumentation instrumentation) {
loadedViaPreMain = true;
agentmain(agentArgs,instrumentation);
}
public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
if (instrumentation == null){throw new NullPointerException("instrumentation");}
if (inst == null) {inst = instrumentation;}
}
private static Instrumentation inst;
private static boolean loadedViaPreMain = false;
}
I just try it out myself package these code as a package then start the application class with -javaagent:plugin......jar option then call this function.It doesn't change my classpath.I am probably missing some details here.
Hope you can make it work though.
i found this, and worked for me.
String pathSeparator = Syste .getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path") .split(pathSeparator);
from the web site https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader

Parent-Child Classloader Class Resolution

The tasks and initial investigation
I try to set up two Oracle Coherence near cache instances at one java swing application. The idea a solution could be found here. My case is a bit more complicated and this is where the game starts.
Short description
In my case there is an account service. It can have two endpoints: SIT and UAT. In order to create two such services, I need to load two 'instances' of the Coherence in order to override the endpoints with system variables (tangosol.coherence.cacheconfig).
I have:
the main code of the app is located in the mainapp.jar;
the AccountService interface that is located in the account-interfaces.jar;
the AccountServiceImpl class that is located in the account-impl.jar and implements the AccountService interface;
my main application has the following structure
bin: startup.bat, startup.sh
conf: app.properties
lib: mainapp.jar, account-interfaces.jar, account-impl.jar, coherence.jar
Approach tried
I created a dedicated child-first classLoader - InverseClassLoader and made the AppLaunchClassLoader (the default Thread.currentThread().GetContextClassLoader() classLoader) it's parent. With the InverseClassLoader I load the AccountServiceImpl class:
Class<AccountServiceImpl> acImplClass = contextClassLoader.selfLoad(AccountServiceImpl.class).loadClass(AccountServiceImpl.class);
Constructor<AccountServiceImpl> acConstructor =
acImplClass .getConstructor(String.class);
AccountService acService = acConstructor .newInstance(serviceURL);
Issues and questions
I get the 'AccountServiceImpl cannot be cast to AccountService' exceptions, which means that those two classes loaded by different classloaders. But those classloaders are in the parent-child relationship. So am I right that even if a class is loaded by a parent (interface - 'abstract' type) it can't be used with a class (concrete impl) loaded by a child classloader? Why then we need this parent-child relation?
I specified the AccountService interface in a code and it got loaded by a default classloader. I tried wrap the code above is a thread and set the InverseClassLoader it's context classloader. Nothing changed. So am I right that I can't use such interface-implementation coding (as usual coding) and need to use reflection all the time to invoke concrete methods all the time? (Hope there is a solution) ;
Say, I listed both the AccountService and AccountServiceImpl classes for being loaded by the InverseClassLoader. What if I need other classes, that are accessible by those two, to be also loaded by the InverseClassLoader? It there a way to say that all 'related' classes must be loaded by the same classloader?
Update
Here is the InverseClassLoader:
public class InvertedClassLoader extends URLClassLoader {
private final Set<String> classesToNotDelegate = new HashSet<>();
public InvertedClassLoader(URL... urls) {
super(urls, Thread.currentThread().getContextClassLoader());
}
public InvertedClassLoader selfLoad(Class<?> classToNotDelegate) {
classesToNotDelegate.add(classToNotDelegate.getName());
return this;
}
#Override
public Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
if (shouldNotDelegate(className)) {
System.out.println("CHILD LOADER: " + className);
Class<?> clazz = findClass(className);
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
else {
System.out.println("PARENT LOADER: " + className);
return super.loadClass(className, resolve);
}
}
public <T> Class<T> loadClass(Class<? extends T> classToLoad) throws ClassNotFoundException {
final Class<?> clazz = loadClass(classToLoad.getName());
#SuppressWarnings("unchecked")
final Class<T> castedClass = (Class<T>) clazz;
return castedClass;
}
private boolean shouldNotDelegate(String className) {
if (classesToNotDelegate.contains(className) || className.contains("tangosol") ) {
return true;
}
return false;
}
Issue 1, part one I cannot reproduce (see below). As for part 2:
the hierarchy of class-loaders is to prevent the "X cannot be cast to X" exceptions.
But if you break the parent-first rule, you can get into trouble.
About issue 2: setting a thread's context classloader does not do anything in itself, see also this article (javaworld.com)
for some more background. Also, in relation to issue 1, part 2, a quote from the article
that describes what can happen if there is no parent-child relation between the current classloader
and the thread's context classloader:
Remember that the classloader that loads and defines a class is part of the internal JVM's ID for that class.
If the current classloader loads a class X that subsequently executes, say, a JNDI lookup for some data of type Y,
the context loader could load and define Y.
This Y definition will differ from the one by the same name but seen by the current loader.
Enter obscure class cast and loader constraint violation exceptions.
Below is a simple demo-program to show that a cast to an interface from another classloader can work
(note I'm using a simple Java project with classes in a bin-folder and the InvertedClassLoader from your question in the same (test) package):
import java.io.File;
public class ChildFirstClassLoading {
public static void main(String[] args) {
InvertedClassLoader cl = null;
try {
File classesDir = new File(new File("./bin").getCanonicalPath());
System.out.println("Classes dir: " + classesDir);
cl = new InvertedClassLoader(classesDir.toURI().toURL());
cl.selfLoad(CTest.class);
System.out.println("InvertedClassLoader configured.");
new CTest("Test 1").test();
ITest t2 = cl.loadClass(CTest.class)
.getConstructor(String.class)
.newInstance("Test 2");
t2.test();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cl != null) {
try { cl.close(); } catch (Exception ignored) {}
}
}
}
public interface ITest {
void test();
}
public static class CTest implements ITest {
static {
System.out.println("CTest initialized.");
}
private String s;
public CTest(String s) {
this.s = s;
}
public void test() {
System.out.println(s);
}
}
}
If you change ITest t2 = to CTest t2 = you will get the "CTest cannot be cast to CTest" exception,
but using the interface prevents that exception.
Since this little demo works fine, I'm guessing there is more going on in your application which somehow breaks the class-loading.
I suggest you work from a situation where the class-loading works and keep adding code until it breaks the class-loading.
The InvertedClassLoader looks a lot like the "child first classloader", see this question
for some good answers discussing this manner of class-loading.
The child first classloader can be used to load "related classes" (from your third issue) separately.
You could also update the InvertedClassLoader to always "self-load" classes in certain packages.
And remember that "once a class is loaded by a classloader it uses that classloader to load every other class it needs"
(quote from this blog article).

Categories