Loading external class files without regard to class-paths or packages - java

I am attempting to load a class object from some compiled class file sitting in my Desktop dir.
I am feeding in two arguments to main in my program which uses URLClassLoader to get an instance of a class from a compiled file TheClassToLoad.class.
I have, in Main of the classLoading program: (args[0] is for something unrelated)
String classFile_FilePath = args[1];
String className = args[2];
URL classUrl = new URL(classFile_FilePath);
URLClassLoader ucl = new URLClassLoader(new URL[]{classUrl});
When running this program from the shell while in the project directory:
Me:ClassLoadingProgramRootDir Me$ java com.company.Main argZero file:///Users/Me/Desktop/ TheClassToLoad.class
I find a raised exception:
Exception in thread "main" java.lang.ClassNotFoundException: TheClassToLoad.class
So, there is a file TheClassToLoad.class in Desktop/ yet URLClassLoader raises an exception without providing the detail I need to debug the situation.
I am new to Java and am aware that class paths like com.company.Class is often needed to refer to a class's true class name based on package directory structure. However, in this case, I am simply requested that URLClassLoader give me an instance of the Class Object for an arbitrary compiled class file sitting somewhere on a machine.

For URLClassLoader, the URL should be of the directory containing the class+package structure, not the class file itself. In your case, it should be file:///Users/Me/Desktop/.
The argument to loadClass should be the name of the class, not the name of the class file. In your case, it should be TheClassToLoad.
If the class is in a package (e.g., my.pkg.TheClassToLoad), then you should use that class name as the argument to loadClass, and the URL for URLClassLoader should still be the root of the package structure (e.g., file:///Users/Me/Desktop if the file is file:///Users/Me/Desktop/my/pkg/TheClassToLoad.class).

Related

How to load classes dynamically from jar file

If i have a jar file that contain many class, how to get classes, and create instances at runtime. In this code i don't understand the Class.forName line, the MyClass is that class what contain the jar file or that class what will create after the jar file load?
URLClassLoader child = new URLClassLoader(
new URL[] {myJar.toURI().toURL()},
this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);
Class.forName loads a class.
The first parameter is the (canonical) name of the class. In this case, you try to load the class com.MyClass.
The second parameter specifies that the class should be initialized at that point(static variables are initialized and static blocks are run.
The third parameter is the ClassLoader the class will be loaded from. In your case, it will try to find the class from myJar but if the class isn't found there, it will try to load the class from the same ClassLoader the calling class has been loaded.

Using Class.forName() in Java Instrumentation Agent

What I understand is that if I use:
Instrumentation#getAllLoadedClasses()
I do get a selection of all loaded classes by the target JVM. But If I do:
Class.forName("my.class.name")
This will not be the same class as the class loaded by VM. Yes, I can add this particular class as a jar in the agent MANIFEST.MF Class-Path - but that does not look the same to me as getAllLoadedClasses().
Could someone please confirm whether this is correct i.e. I would not be able to find a specific class using Class.forName() when instrumenting? My objective was not to iterate over all loaded classes using getAllLoadedClasses() - But if there is no alternative, I guess that's okay for now.
** UPDATE
What I made a mistake in writing is the Boot-Class-Path which I have now corrected in my manifest. Using -verbose:class logging I managed to see that my jars are being loaded as
[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]
But I don't see any corresponding loading information. I tried adding a Class.forName("a.package.in.someother.jar.classname") and got NoClassDefFoundError. As soon as I jump into the agent jar, I cannot use Class.forName() to check if the class is loaded by the target VM. I am getting a NoClassDefFoundError.
FURTHER UPDATE
Okay I have "Fattened" the manifest to look up all classes in my WEB-INF/lib and tomcat's lib directory. What I can see is below:
1) When my custom class MyClass is loaded for the first time. -verbose shows:
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
2) If I try to load the class again, it is correctly showing the above order.
3) My agent jar is manifested with all classes for my tomcat lib and my web-inf/lib directory. And I can also confirm that the loader sees the jars correctly.
4) Now I inject the agent, and call Class.forName("my.pkg.MyClass") from within the agent class. I get the below results.
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
I acknowledge that it's system class loader loding it inside my agent code as #RafaelWinterhalter pointed out in one of his answers. Is there any way I can force a "Delegation" so that the a different classloader loads the agent class and therefore, correctly redefines a class.
Any help is appreciated.
As it is stated in the javadoc:
Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of
the current class.
You can also see from the source code that the method is marked #CallerSensitive which means that you get a different result based on the class loader that invokes the method.
When calling Instrumentation::getAllLoadedClasses, the returned array contains classes of any class loader and not only of the current class loader which is the system class loader when running a Java agent. Therefore:
for (Class<?> type : instrumentation.getAllLoadedClasses()) {
assert type == Class.forName(type.getName());
}
is not generally true.
After a bit of run around, and Thanks to #Holger who reminded me what the problem was - incorrect class loader.
before I inject the agent, I have done the following:
// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();
// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());
// Now inject agent
try {
vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
vm.detach();
// Put back the classLoader linkage
sc.set(null, original);
}
How I have confirmed
When it goes in my Agent Class - Thread.currentThread().getContextClassLoader() becomes my application extn loader. But the system classloader now becomes `ParallelWebappClassLoader".
I am assuming this is how it works, but could be totally worng:
i) When I say Class.forName("my.pkg") it will check the system class loader which is pointing to my loader now. If the class is not found (i.e. not loaded), it will go to parents etc. I believe this is more or less the delegation model is.
ii) In this way, the class is loaded in VM by the same class loader which would also load the class in my webapp under normal circumstances.
iii) I will not instrument anything else apart from my own classes so the classloader will always be the same.
So far I have not seen any LinkageError happening. But I still feel this is too risky and if I break the link I am screwed.
Using Class.forName in a java profiler must be avoided to escape from the NoClassDef error. JVM Loads the class files in the different level of class loaders based on their classpath setting and the class file demand.
Java Cre libraries + Boot path defended libraries will be loaded in bootstrap Level
Java Agent will be loaded in system level and it goes on. Class.forName() will look the class files from the parent loaders, the current loader will not check the child loader (Until unless we implement our own loaders)
Java core classes will be accessible from your application code but our application code will not be accessible by the Java core classes. Its called class loader hierarchy.
You have three options.
Lookup the class files from the Instrumentation.GetLoadedClassFiles()
Through Transformers you can get all the loaders classes and you can track them and look for your class in every loader until you find.
Have the Class.forname implementation in the lowest level of the hierarchy so that it can internally access all the path.
Maintain the hierarchy properly to avoid too many weird errors.
Assuming you are looking for the Class<?> in order to re-transform a class, it seems to me that you could save the ClassLoader passed to your transformer, and later use ClassLoader.loadClass(String). Something like:
class MyTransformer implements ClassFileTransformer {
Map<String, ClassLoader> _name2loader = new ...;
...
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd,
byte[] classfileBuffer) throws ... {
...
_name2loader.put(className.replace("/","."), classLoader);
...
}
...
Class<?> getClass(String name) throws ClassNotFoundException {
ClassLoader cl = _name2loader.get(name);
if (cl == null) {
throw ClassNotFoundException("No loader for class " + name);
}
return cl.loadClass(name);
}
}
Note that the className passed to transform uses slashes, not dots... A better alternative than the String.replace may be to actually read the class name from the classfileBuffer using your bytecode library (such as javaassist or ASM, but if you're transforming bytecode, you're likely already using such a library).
Note: I'm not sure if you'd see the same class being passed for transformation with different ClassLoaders, but it would be good to look out for that (or research it).

Loading of imported classes of loaded classes using Reflection in Java

I am trying to load classes from a jar file. Basically, I want to call a method in a particular class in a package of that jar. The problem I am facing here is that after the class is successfully loaded from the jar and when I try to instantiate I get exception : ClassNotFound for classes imported in my class.
Here is the class which loads the class:
inputs: D:\Myjar.jar , com.vendor.epbroker.VNFLCMCommunicator
public Class<?> loadClass(String libPath, String pkgName) {
LogManager.getLogger().info("Adding Class");
File jarFile = null;
try {
jarFile = new File(libPath);
URL fileURL = jarFile.toURI().toURL();
String jarURL = "jar:" + fileURL + "!/";
URL urls[] = { new URL(jarURL) };
URLClassLoader ucl = new URLClassLoader(urls);
Class<?> beanClass = ucl.loadClass(pkgName);
ucl.close();
return beanClass;
} catch (Exception ex) {
LogManager.getLogger().error("Given Library: " + libPath + " or Class name: " + pkgName + " is not Valid");
LogManager.getLogger().error("Exception occurred : ", ex);
}
LogManager.getLogger().error("Class loading Error: Returning NULL");
return null;
}
The code snippet which receives this Class:
Object instance = classToLoad.newInstance();
// To get the list of methods exist in the Class
Method[] listOfMethods = classToLoad.getMethods();
The following error is encountered:
SEVERE: Servlet.service() for servlet [spring] in context with path [/vnflcm] threw exception [Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/vendor/epbroker/exception/EPBrokerException] with root cause
java.lang.ClassNotFoundException: com.vendor.epbroker.exception.EPBrokerException
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
at java.lang.Class.getConstructor0(Class.java:3075)
Any help would be appreciated?
Have a look at the following lines:
URLClassLoader ucl = new URLClassLoader(urls);
Class<?> beanClass = ucl.loadClass(pkgName);
ucl.close();
and consider the documentation of URLClassLoader.close():
Closes this URLClassLoader, so that it can no longer be used to load new classes or resources that are defined by this loader.
In other words, you should only close a class loader if you are really done using the classes of that loader. Even if all required classes had already been loaded at this point, there was still the possibility that a required resource needs to be accessed. Note that some frameworks have there own reflection library requiring access to the byte code of the classes, which will be accesses like a resource.
In your specific case, it’s even simpler. You have just loaded one class, which only triggered resolving of the required minimum set of classes (e.g. the direct super class), but no other dependencies. Then you close the class loader, preventing subsequent loading of any other class from your jar file, which hits you when resolving the constructors needs resolving more referenced classes.
There are a few considerations you have to take when trying to use Reflection.
Your URLClassLoader must contain the URL of the jar that you want to reflect into.
If the desired jar depends on any other jars, you must load the URLs for those jars as well.
As #VGR pointed out, you cannot simply use the file path for the jar to use as a URL. One thing you can do is :
File myJar = new File("path/to/myJar.jar");
URL myJarUrl = myJar.toURI().toURL();
A simple example to demonstrate the issue:
Let's call your jar myToolProject. And let's say while developing this tool you created a class, call it JsonMaker, that converts a POJO to a JSON and you accomplish this via the gson.jar. When you build your jar, let's call it myjar.jar, you mention that in the manifest that it depends on gson.
When trying to reflect on myjar, you reflect on each class in your jar, until you reach JsonMaker.class. When trying to reflect here we notice that there is com.google.Gson type object here. The URLClassLoader looks through the URLS in its array and tries to find com.google.Gson in some class. If it cannot find any com.google.Gson class, it cannot reflect on that class, and throws a ClassNotFoundException.

Load a class from a jar having dependency on another jar

My project structure is the following (very simplified of course):
So under lib-ext i download on a daily basis from a Jenkins server 2 jar files 'jar1 and jar2' to be checked by my program, i need one file from 'jar1' lets call it: "Class2Bloaded".
The issue is that this file implements an interface that is to be found in 'jar2', lets call this 'Dependency'
What i would like to do is, from my class under src "ClassThatLoads.java", load 'Class2Bloaded.class' and tell the class loader to look into 'jar2' to search for the implementing interface "Dependency.class"
My code so far (omitting exceptions handling):
//Create the URL pointing to Jar1
private URL getJarUrl(JarFile jarFile)
{
return new File(jarFile.getName()).toURI().toURL();
}
URL jar1Url = getJarUrl(jar1);
ClassLoader jar1classLoader = new URLClassLoader(new URL[] { jar1Url });
Class<?> Class2Bloaded = Class.forName(fullClassName, false, jar1classLoader );
So the problem happens within the Class.forName invocation, because the class i want to load implements an interface that is in jar 2.
Exception in thread "main" java.lang.NoClassDefFoundError: com/packagewithinJar2/Dependency
So eventually i have prepared another class loader that points to 'jar2', and i have even got the actual Interface i need:
URL jar2Url = getJarUrl(jar2);
ClassLoader jar2classLoader = new URLClassLoader(new URL[] { jar2Url });
Class<?> Interface2Bloaded = Class.forName(fullClassName, false, jar2classLoader );
Where 'fullClassName' in the second case is the fully qualified name of the interface from which 'Class2Bloaded' depends on.
Is just that i cant find anything in the javadocs of ClassLoader that allows me to 'inject' an additional class loader for the dependencies.
I hope my explanation is clear.
The first thing to do would be to add jar2 to the list of jars your URLClassLoader reads:
ClassLoader jarclassLoader = new URLClassLoader(new URL[] { jar1Url, jar2Url });
BUT the normal thing to do would be to add jar1 and jar2 on your classpath from the beginning.
To do so you would use the -cp parameter of the java executable.
for example, if you compile your classes into the bin directory:
java -cp libext/jar1.jar:libext/jar2.jar:bin ClassThatLoads
That way, you could use the classes seamless in your own java source and get rid of the cumbersome loading part :
public class ClassThatLoads {
public static void main(String[] args) {
Class2Bloaded stuff = new Class2Bloaded();
//use stuff from here...
}
}

How to reflect classes from JAR on windows

Assuming the following loader definition (the example is written in scala but it is almost the same as Java, thus you should easily understand the problem):
jar = new File("path/to/myjar.jar")
url = jar.toURI.toURL
urls = Array[URL](url) // just a single-element array containing the url
loader = new URLClassLoader(urls)
The JAR myjar.jar contains org/pack/Simple.class. The Simple has standard package org.pack and was compiled and packed into the JAR via JavaCompiler. The JAR is correct (executable etc.)
On linux, loader.loadClass("org.pack.Simple") returns the correct Class object.
On windows, it throws a ClassNotFoundException. Of course, I use \ instead of / as a file separator.
What do I do wrong? Or does simply windows suck? I installed OracleJDK8 and added its bin/ folder to the path.
EDIT1:
If I unpack JAR into some directory and let the ClassLoader to read from URL pointing at that directory, everything works. How is this possible?
I struggled long with this problem yesterday, but after a long Googling session I stumbled on the solution.
The URL object you specify must be created as follows:
URLClassLoader.addURL(new File(pathParam).toURI().toURL());
or in the constructor as follows:
new URLClassLoader(new URL[]{new File(pathParam).toURI().toURL()},this.getClass().getClassLoader());
Note that the constructor has the parent class-loader as a parameter.
for the loadClass() method. You need to ensure the path to the class is in binary format (as you are doing already).
For some reason, even if I create a URL object using the URL(String) constructor it won't work.
If you go and look at the java.lang.ClassLoader super-class, you will notice that it throws an exception by default. This is why the classloader is important.
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

Categories

Resources