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.
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.
Basically our code allows people to pass in a custom implementation of our IRepository interface if they want to define their own repository for the files we generate. I keep getting the following error:
ClassCastException: S3RepositoryPlugin.S3Repository cannot be cast to package_name.IRepository
This is how I define the S3Repository:
public class S3Repository implements IRepository
And this is how im trying to insatiate it:
URL[] urls = { new URL("jar:file:" + assembly +"!/") };
URLClassLoader cl = URLClassLoader.newInstance(urls);
Class classToLoad = Class.forName(className, true, cl);
IRepository externalRepo = (IRepository) classToLoad.newInstance();
Where assembly is the full path to the jar, and className is "S3RepositoryPlugin.S3Repository".
Any idea as to why this is happening? From the stuff Im seeing online it seems casting to interfaces works differently than casting to classes but im not sure if the code im using is necessarily affected by that.
NOTE:
Using Tomcat9 and java8
I had to add the following code to the URLClassLoader to make it work:
URLClassLoader cl = URLClassLoader.newInstance(urls, getClass.getClassLoader());
I have a few groovy classes in the same package ( com.company.config ) in a multi-modular java-8 project.
All used to inherit a java interface ( MyInterface ), but some refactoring was needed so I created a groovy abstract class which resides in the same package as the other scripts; it implements MyInterface and is inherited by the other scripts.
Ever since this change, I can't seem to execute the scripts from java code anymore.
In particular, GroovyClassLoader::parseClass(File) throws:
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed:<|C:\my_workspace\a_project_name\modules\module-setup\src\com\company\config\MyScript1Impl.groovy:
7: unable to resolve class com.company.config.AbstractGroovyClass
# line 7, column 1.
import com.company.config.AbstractGroovyClass
^
At line 7, you can indeed find the import declaration
import com.company.config.AbstractGroovyClass
which I added (despite the class being in the same package) after the first time the same error was thrown and after I read this.
The Exception is triggered in the following line in the java code:
public Object getInstance(File sourceFile) {
try {
GroovyClassLoader gcl = new GroovyClassLoader();
Class clazz = gcl.parseClass(sourceFile); // << Here
Object inst = clazz.newInstance();
// ....
}
// ...
}
whereas I call this function with the following parameters
getInstance(
new File("./modules/module-setup/src/com/company/config/"
className + ".groovy" // className = "MyScript1Impl" in this case
)
);
As already stated, before the introduction of the abstract class, everything was working fine.
Why can't the groovy class find its superclass in the same package, even with the import declaration?
This is the stack-trace of the internal calls:
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed ...
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:310)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:946)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:593)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:542)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:254)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:195)
The rest stack trace is relative to the application calls so it's unhelpful.
Edit I
I noticed indeed that the GroovyClassLoader does not know anything about other groovy classes and where they are located so I just added "./modules/module-setup/src/com/company/config/" in
GroovyClassLoader::addClassPath(String)
but I'm getting the same result as before.
The path is surely correct as the File instance is created with it and can be opened by the class loader.
I resolved it temporarily by loading the superclass via GroovyClassLoader::parseClass right before loading the actual inherited class.
final GroovyClassLoader gcl = new GroovyClassLoader();
// ...
// load superclass first
Class<?> abstractClass = gcl.parseClass(new File(classPath, "AbstractGroovyClass.groovy"));
// load the actual script
Class<?> clazz = gcl.parseClass(sourceFile);
It's definitely a bad answer as, if I had more groovy classes on which I depend on, I would have to manually parse them one by one. But it works...
Hoping someone can give a better answer.
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 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.