I'd like to be able to load a class(es) from a known directory whenever a compiled .class file appears in that particular directory. However the I'd like the .class to be loaded regardless of what the package decleration is in its .java file. For example I have this class which I wish to load:
package com.javaloading.test;
public class SomeClassInPackage {
private String name = "The name of this Class is SomeClass.";
public String getName(){
return name;
}
}
And it is in the package com.javaloading.test. I then want to load it using this class:
public class GetPackage {
public static void main(String[] args){
new GetPackage().loadMyClass();
}
public void loadMyClass(){
// Get the current class loader
ClassLoader cl = getClass().getClassLoader();
try {
Object o = cl.loadClass("SomeClassInPackage");
System.out.println("Class loaded!");
} catch (ClassNotFoundException ex){
System.out.println("Could not load class");
}
}
}
If I put the .class files of both the above Classes into the same directory and run GetPackage it results in the error
Exception in thread "main" java.lang.NoClassDefFoundError:
SomeClassInPackage (wrong name:
com/javaloading/test/SomeClassInPackage
I need to be able to load a class (from a file) regardless of it's declared package and without having to actually know its package. I would then examine the loaded class for its package information. Is this possible using the System ClassLoader or a custom ClassLoader or is it impossible without having knowledge of the package structure? If it's possible any advice is appreciated.
It is impossible to load the class without its respective package structure, means if you want to load the class then it must be placed in the folder that is correspond to its packages name or that class is in a jar file but in same folder structure.
But lets say you want to load the classes which is external means not in the class path where this program gets executed from and you want to load it in current class loader during execution. Refer to this link How to load the classes at runtime. This will also gives answer to your next question where you want to load the classes which is selected by the program based on its name or package.
Related
I have a main method that creates custom classloader and instantiates a class, called Test, with it.
public class App {
public static void main(String[] args) throws Exception {
try {
Class.forName("com.mycompany.app2.Test2"); // We ensure that Test2 is not part of current classpath
System.err.println("Should have thrown ClassNotFound");
System.exit(1);
} catch (ClassNotFoundException e) {
// ignore
}
String jar = "C:\\experiments\\classloader-test2\\target\\classloader-test2-1.0-SNAPSHOT.jar"; // Contains Test2
URL[] classPaths = new URL[] { new File(jar).toURI().toURL() };
ClassLoader classLoader = new URLClassLoader(classPaths, App.class.getClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
Class.forName("com.mycompany.app2.Test2", true, classLoader); // Check that custom class loader can find the wanted class
Test test = (Test) Class.forName("com.mycompany.app.Test", true, classLoader).getDeclaredConstructor().newInstance();
test.ex(); // This throws ClassNotFound for Test2
}
}
This class then itself instantiates another class that is not part of the original classpath, but is part of the custom one.
public class Test {
public void ex() {
new Test2().test();
}
}
In my understanding of classloader, since Test was created with the custom classloader any class loadings within should be done with the same loader. But this does not seem to be the case.
Exception in thread "main" java.lang.NoClassDefFoundError: com/mycompany/app2/Test2
at com.mycompany.app.Test.ex(Test.java:7)
at com.mycompany.app.App.main(App.java:28)
What do I need to do in the main method to make Test#ex work, without changing Test?
I'm using Java 17.
You create the URLClassLoader using App.class.getClassLoader() as the parent class loader. Hence, the request to load Test through the custom class loader is resolved through the parent loader, ending up at exactly the same class you’d get with Test.class in your main method.
You could pass a different parent loader, e.g. null to denote the bootstrap loader, to forbid resolving the Test class though the parent loader but this would result in either of two unhelpful scenarios
If the custom class loader has no com.mycompany.app.Test class on its own, the loading attempt would simply fail.
If the custom class loader has a com.mycompany.app.Test class, i.e. inside classloader-test2-1.0-SNAPSHOT.jar, it would be a different class than the Test class referenced in your main method, loaded by the application class loader. In this case, the type cast (Test) would fail.
In other words, the Test class referenced by you main method can not be affected by another, unrelated class loader at all.
There is an entirely different approach which may work in some scenarios. Do not create a new class loader, when all you want to do, is to inject a new class.
byte[] code;
try(var is = new URL("jar:file:C:\\experiments\\classloader-test2\\target\\" +
"classloader-test2-1.0-SNAPSHOT.jar!/com/mycompany/app2/Test2.class").openStream())
{
code = is.readAllBytes();
}
MethodHandles.lookup().defineClass(code);
Test test = new Test();
test.ex();
This adds the class to the current class loading context, so subsequent linking will succeed. With the following catches:
No previous attempt to link this class must have been made so far
It only works for a classes without dependencies to other absent classes (as there’s no class loader resolving those to the jar file outside the class path).
In some cases, when the dependencies are non-circular or resolved lazily, you could add all the classes with multiple define calls, if you know which you need and in which order.
The class must be in the same package, otherwise, you’d have to move the lookup context to the right package, with the documented restrictions
An entirely different approach to add the classes to the existing environment, would be via Java Agents, as they can add jar files to the class path.
System.out.println(javaClass.getSuperclassName());
JavaClass javaClass1 = javaClass.getSuperClass();
the first line output the name of the class: RestController
The second line throws Exception:
java.lang.ClassNotFoundException: Exception while looking for class example.RestController: java.io.IOException: Couldn't find: example/RestController.class
So, you're using The Byte Code Engineering Library (Apache Commons BCEL™) to load classes from a Jar file (by filename) and want to print the entire call graph with the following github project.
This works just fine, until you want to ask for the superclass of a class that was found in your jar file.
So bcel will load a .class file, and store all it can read from the class file in a JavaClass model. This is for example the name, some flags, the super class name, the declared methods etc.
For example inspect the following java code;
JavaClass stringClass = Repository.lookupClass("java.lang.String");
System.out.println(stringClass);
with output:
public final class java.lang.String extends java.lang.Object
implements java.io.Serializable, java.lang.Comparable, java.lang.CharSequence
file name java.lang.String
compiled from String.java
compiler version 52.0
access flags 49
constant pool 540 entries
ACC_SUPER flag true
Attribute(s):
SourceFile: String.java
etc...
So bcel knows that the superclass is java.lang.Object, but it has not loaded any of the classes at this point! For JRE classes this is of course moot, but for the classes from your Jar file this is a problem.
Because org.apache.bcel.classfile.JavaClass#getSuperclassName will just return the String value that it found as the super class in the .class file. Again this class was not loaded, so the Repository doesn't know about it.
When you then ask for the org.apache.bcel.classfile.JavaClass#getSuperClass, it will try to find it like so:
public JavaClass getSuperClass() throws ClassNotFoundException {
return "java.lang.Object".equals(this.getClassName()) ? null : this.repository.loadClass(this.getSuperclassName());
}
Bcel will try to load it from its Respository, and if the class is unknown, it will delegate the loading to the current ClassPath. Since you're just inputting a File pointing to a Jar, this will fail with the ClassNotFoundException.
There are two ways to you can solve this:
Put the jar file(s) on your classpath; for example via Including all the jars in a directory within the Java classpath
First load all the jar files into the Repository of bcel, so that it knows these classes exist. If we stay in the JCallGraph example from github, that would look something like this:
// ... JCallGraph code from Github above this point
try (JarFile jar = new JarFile(f)) {
// extra stream over jar entries
Stream<JarEntry> jarEntryStream = enumerationAsStream(jar.entries());
jarEntryStream.filter(jarEntry -> jarEntry.getName().endsWith(".class"))
.forEach(jarEntry -> {
ClassParser cp = new ClassParser(jarFileName, jarEntry.getName());
try {
// here we tell BCEL this class exists
Repository.addClass(cp.parse());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
});
// ... back to the JCallGraph code from Github
Stream<JarEntry> entries = enumerationAsStream(jar.entries());
Note that if you have multiple jar files, or super classes coming from external dependencies, these all need to be on the classpath (1) or loaded in bcel first (2) before you can load the superclass.
I have wrote some code to compile a Java source code. It then produces the .class file. The problem is how do I run it?
For example, I am ok with the name of the program and class being set, I've used prog p = new prog(), in this case, however, the class file does not yet exist until I compile it. Not really sure what to do. Can someone give me an advice?
btw, the class looks like this:
public void compile{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult = compiler.run(null, null, null, fileToCompile);
}
public void run(){
Prog prog = new Prog();
prog.run();
}
If you just want to run it, you could launch a java process using
Runtime.exec or ProcessBuilder. These will create a seperate java process to run your java program. This is more likely what you want. You can essentially do the equivelant of:
>java someClass
from within your application. This link may help.
If you want to actually load the classfile and use it in your current application, I think something along the lines of this, or dynamically loading Java Classes ought to help. Basically (directly from the link, slightly modified):
public class MainClass {
public static void main(String[] args){
ClassLoader classLoader = MainClass.class.getClassLoader();
try {
Class aClass = classLoader.loadClass("MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
Once you loaded the class, you have a Class object, and you can create an instance of the class represented by aClass by calling aClass.newInstance(), which is like
MyClass newObj = new MyClass()
Or you can use any of the other methods the Class object exposes.
As pointed out by davmac, the code sample above presumes that the code you're loading is on your applications classpath. If the class files you want to run are not in your classpath, you might want to look into URLClassLoader
Load it by URLClassLoader.
File root = new File("/java"); // The package root.
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { root.toURI().toURL() });
Class<?> cls = Class.forName("test.Test", true, classLoader); // Assuming package test and class Test.
Object instance = cls.newInstance();
// ...
See also:
How do I instantiate a class dynamically in Java?
You need to create a classloader (a URLClassLoader will probably be fine) which will load the just-compiled class file. (So for a URLClassLoader, the compilation output path should be one of the URLs).
Then, load the compiled class using the classloader, and execute it using reflection.
Class c = cl.loadClass("ClassName");
... etc.
In Java all classes are loaded into the JVM dynamically, upon the first use of a class.
Does it mean if i have class in a my source file and I do not make any reference to it then its Class object is not created (i.e. .class file is not created)?
In the sample code below iam not making a refernce to test3 class but still its class object gets created.
class test1 {
static {
System.out.println("static block of test1");
}
}
class test2{
static {
System.out.println("static block of test2");
}
}
class test3 {}
class MyExample1 {
public static void main(String ...strings ) {
new test1();
new test2();
}
}
Why test3.class file gets created?
.class file was created at compilation time. But, it will be loaded from .class file by first usage (probably).
From where it should be loaded without .class file?)
You have to distinguish between the file test3.class (which is created by the compiler) and the class object test3.class of class test3, which is created on runtime when the class is loaded by the classloader.
The class file is always created if you compile a .java source file (compilation unit) with the class in it (most often class3.java, but it can also be named anything else, if the class is not public) - or implicitely if the class is used by another compiled class.
The class object is created by the classloader when the class is first loaded - this occurs whenever it is needed, or earlier. The normal URLClassLoader loads your class from a file with the same name, but in principle the data could also be generated on the fly, loaded from a database or similar.
It (the class) then is initialized by invoking the static blocks. (The initialization is a second step, not necessarily at the same time, but both are before your first use of the class.)
How can I load a *.java class file into my java app and create an object based on that class file?
You can do it by using classes inside javax.tools. You will have a ToolProvider class from which you can obtain a compiler instance and compile code at runtime. Later you will load .class files just compiled separately with a ClassLoader unless you obtain directly a binary code for the class and you are able to istantiate it directly.
Take a look here
Try Janino's SimpleCompiler. Simple example, assuming you're compiling a class with a public no-arg constructor.
import org.codehaus.janino.SimpleCompiler;
public class JaninoSimpleTest
{
public static void main(String[] args) throws Throwable
{
String filename = args[0];
String className = args[1];
SimpleCompiler compiler = new SimpleCompiler(filename);
ClassLoader loader = compiler.getClassLoader();
Class compClass = loader.loadClass(className);
Object instance = compClass.newInstance();
}
}
i Think this will help : Package Summary
Or Simple Comipler .
You cannot. The .java file needs to be compiled into a .class so that you can use it.