How can I pass parameters to a jar, whilst using a ClassLoader? - java

Here's my code
package serverloader;
import java.net.URL;
import java.net.URLClassLoader;
public class ServerLoader {
public void initiate(String[] arguments) throws Exception {
System.out.println("Initiating...");
URL link = new URL("link");
URLClassLoader classLoader = new URLClassLoader(new URL[]{link});
Object o = classLoader.loadClass("class.MyClass").newInstance();
System.out.println("Loaded, starting...");
}
}
This is the loader to load my actual application, but for some reason it's not starting up and I believe it's because there are parameters that are needed to launch the application that's being loaded here, so how do I pass the parameters, which are here
String[] arguments
to the jar that is being loaded by the ClassLoader?

Kind of, you need to use reflection to get the constructor which meets your needs, something like...
Class[] clazz = classLoader.loadClass("class.MyClass");
Constructor constructor = clazz.getConstructor(String[].class);
Once you have the constructor, you can create a new instance of the class using Constructor#newInstance...
Object o = constructor.newInstance(arguments);
As an example...

Related

How to use Custom ClassLoader to new Object in Java

I want to create a custom ClassLoader to load all jar files in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object, it will search class in all jar files in that path, then search the class path defined by parameter (-cp).
Is it possible?
for Example, there is a jar file in /home/custom/lib/a.jar
in Main Class
public class Main {
public static void main(String[] args) {
// do something here to use custom ClassLoader
// here will search Car in /home/custom/lib/a.jar first then in java class path
Car car = new Car();
}
}
A class loader cannot do exactly what you seem to expect.
Quoting another answer of a relevant Q&A:
Java will always use the classloader that loaded the code that is executing.
So with your example:
public static void main(String[] args) {
// whatever you do here...
Car car = new Car(); // ← this code is already bound to system class loader
}
The closest you can get would be to use a child-first (parent-last) class loader such as this one, instanciate it with your jar, then use reflection to create an instance of Car from that jar.
Car class within a.jar:
package com.acme;
public class Car {
public String honk() {
return "Honk honk!";
}
}
Your main application:
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Object someCar = carClass.newInstance();
Object result = carClass.getMethod("honk").invoke(someCar);
System.out.println(result); // Honk honk!
}
To note: if you also have a com.acme.Car class in your class path, that's not the same class, because a class is identified by its full name and class loader.
To illustrate this, imagine I'd used my local Car class as below with the carClass loaded as above by my custom class loader:
Car someCar = (Car) carClass.newInstance();
// java.lang.ClassCastException: com.acme.Car cannot be cast to com.acme.Car
Might be confusing, but this is because the name alone does not identify the class. That cast is invalid because the 2 classes are different. They might have different members, or they might have same members but different implementations, or they might be byte-for-byte identical: they are not the same class.
Now, that's not a very useful thing to have.
Such things become useful when the custom classes in your jar implement a common API, that the main program knows how to use.
For example, let's say interface Vehicle (which has method String honk()) is in common class path, and your Car is in a.jar and implements Vehicle.
ParentLastURLClassLoader classLoader = new ParentLastURLClassLoader(
Arrays.asList(new File("/home/lib/custom/a.jar").toURI().toURL()));
Class<?> carClass = classLoader.loadClass("com.acme.Car");
Vehicle someCar = (Vehicle) carClass.newInstance(); // Now more useful
String result = someCar.honk(); // can use methods as normal
System.out.println(result); // Honk honk!
That's similar to what servlet containers do:
your application implements the servlet API (e.g. a class that implements javax.servlet.Servlet)
it is packaged into a war file, that the servlet container can load with a custom class loader
the deployment descriptor (web.xml file) tells the servlet container the names of the servlets (classes) that it needs to instanciate (as we did above)
those classes being Servlets, the servlet container can use them as such
In your case, you do not need to write a new ClassLoader as the only thing you wanna do is extend your classpath at runtime.
For that you get your current SystemClassLoader instance and you add the classpath entry to it using URLClassLoader.
working example with JDK 8:
Car class compiled and located in C:\Users\xxxx\Documents\sources\test\target\classes
public class Car {
public String prout() {
return "Test test!";
}
}
Main class
public static void main(String args[]) throws Exception {
addPath("C:\\Users\\xxxx\\Documents\\sources\\test\\target\\classes");
Class clazz = ClassLoader.getSystemClassLoader().loadClass("Car");
Object car = clazz.newInstance();
System.out.println(clazz.getMethod("prout").invoke(car));
}
public static void addPath(String s) throws Exception {
File f=new File(s);
URL u=f.toURI().toURL();
URLClassLoader urlClassLoader=(URLClassLoader)ClassLoader.getSystemClassLoader();
Class urlClass=URLClassLoader.class;
Method method=urlClass.getDeclaredMethod("addURL",new Class[]{URL.class});
method.setAccessible(true);
method.invoke(urlClassLoader,new Object[]{u});
}
note that we need to use reflection because method addURL(URL u) is protected
also note that since we add the classpath entry to the SystemClassloader, you do not need to add the classpath entry everytime you need it, only once is enough and then use ClassLoader.getSystemClassLoader().loadClass(String name) to load the class from previously added classpath entry.
If you do not need that classpath entry for later use, you can instantiate your own URLClassLoader instance and load the classes accordingly, instead of setting the classpath entry on the SystemClassLoader.
i.e:
public static void main(String[] args) {
try {
File file = new File("c:\\other_classes\\");
//convert the file to URL format
URL url = file.toURI().toURL();
URL[] urls = new URL[]{ url };
//load this folder into Class loader
ClassLoader cl = new URLClassLoader(urls);
//load the Address class in 'c:\\other_classes\\'
Class cls = cl.loadClass("com.mkyong.io.Address");
} catch (Exception ex) {
ex.printStackTrace();
}
}
source:
https://www.mkyong.com/java/how-to-load-classes-which-are-not-in-your-classpath/
Question: I want to create a custom ClassLoader to load all jar files
in some path(e.g. /home/custom/lib).
then I expect that every time I use new operator to create a Object,
it will search class in all jar files in that path, then search the
class path defined by parameter (-cp).
Is it possible?
If you want to be able to use new keyword, you need to amend the classpath of the compiler javac -classpath path
otherwise at compile-time it will not know from where to load the class.
The compiler is loading classes for type checking.
(more infos here: http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#searching)
It is not possible to use new keyword for classes loaded by a custom ClassLoader at runtime due to the compiler internal implementation of new keyword.
The compiler and JVM (runtime) have their own ClassLoaders, you cannot customize the javac classloader, the only part that can be customized from the compiler is the annotation processing as far as I know.

Adding IntegerMetaClass to GroovyShell dynamically

I would like to use a custom IntegerMetaClass only in a given GroovyShell context.
The reason why is to not pollute the whole runtime with my potentially 'disturbing' IntegerMetaClass.
It works like a charm when I put my IntegerMetaClass.java implementation in the magic package groovy.runtime.metaclass.java.lang. But, when I try to add it manually to an intermediate GroovyClassLoader, it stops working.
// Pseudo-code without try catch, etc
// Within my eval factory
GroovyClassLoader gcl = new GroovyClassLoader(getClass().getClassLoader());
URL url = getClass().getClassLoader().getResource("groovy/runtime/metaclass/java/lang/IntegerMetaClass.groovy"); // I rename it to .groovy file
GroovyCodeSource gcs = new GroovyCodeSource(url);
Class<?> clazz = gcl.parseClass(gcs);
// clazz is not null here and equals to what I expect:
// Class<groovy.runtime.metaclass.java.lang.IntegerMetaClass>
// Now trying to use it in a groovy shell
GroovyShell gs = new GroovyShell(gcl);
gs.evaluate("10.minutes"); // Where .minutes is part of my IntegerMetaClass
// Fail with an NoSuchProperty exception
Do I miss something to do on GroovyClassLoader more than just 'parsing' the MetaClass ? Anywhere else ?
Update1:
As mentioned above, IntegerMetaClass.minutes lookup is working when I put it directly in my java sources classpath.
package groovy.runtime.metaclass.java.lang;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.MetaClass;
public class IntegerMetaClass extends DelegatingMetaClass {
public IntegerMetaClass(Class<Integer> delegate) {
super(delegate);
}
public IntegerMetaClass(MetaClass delegate) {
super(delegate);
}
#Override
public Object getProperty(Object object, String property) {
if ("minutes".equals(property)) {
Integer q = (Integer) object;
return new Minutes(q);
}
return super.getProperty(object, property);
}
}
Update2:
A possible but not satisfying solution :
Adding the following just after the gcl.parseClass call
Constructor<?> constructor = clazz.getConstructor(Class.class);
DelegatingMetaClass dmc = (DelegatingMetaClass) constructor.newInstance(Integer.class);
dmc.initialize();
InvokerHelper.getMetaRegistry().setMetaClass(Integer.class, dmc);
But this solution has to maintain a sort of 'mapping' between MetaClass sources and original targeted class to support more than Integer ...
You can load the class with the GroovyShell classloader:
GroovyShell gs = new GroovyShell()
gs.loader.loadClass('groovy.runtime.metaclass.java.lang.IntegerMetaClass')
gs.evaluate("10.minutes")
Note: I got a java.lang.IncompatibleClassChangeError when IntegerMetaClass was a groovy file but no issues when it was java. That could just be related to my environment setup

How do I LoadClass() from outside project build?

I'm trying to create an exploit, I want to load a class from outside the netbeans project, of a subclassed cash register, which should have been made final.
I can load the LegitClass fine from within the original package badclassloader, with:
claz = "badclassloader.LegitClass"
loadClass = this.getClass().getClassLoader().loadClass(claz);
(LegitClass)loadClass.newInstance();
Now I want to load the MalClass which lives in another project and package 'Package Mal'
How do I get the Mal.MalClass into the path for the LoadClass() method to find?
I tried the following:
private void loadLibrary() {
if (library != null) {
AccessController.doPrivileged(new PrivilegedAction() {
#Override
public Object run() {
System.loadLibrary("Path/to/Project/mal.jar");
return null;
}
});
}
}
but firstly I got Directory separator should not appear in library name So that clearly isn't it, I'm pretty sure I'm missing something considerable here.
Create a new, custom class loader...
URLClassLoader cl = new URLClassLoader(
new URL[]{new File("Path/to/Project/mal.jar").toURI().toURL()}
);
Load the class from the class loader...
Class clazz = cl.loadClass("Mal.MalClass");
Create a new instance of the class...
Object obj = clazz.newInstance();
Now. You have a problem. Unless the Class you've just loaded implements a interface which is common to both projects, you won't be able to resolve the instance of the Class to an actual type, as the type would be unknown at compile time. Instead, you would be forced to use reflection, which is never pretty...

AbstractMethodError On a Dynamic Loaded Jar

I am loading a plug-in dynamically. Both the plug-in and the software
have been created by us.
I have an Interface lets call it Foo. There is also FooImpl that just
implements that method But FooImpl is in the jar loaded dynamically
public interface Foo {
void write(..someArgument..) throws Exception; }
I have also a PluginLoader class here is the method public
Object loadPlugin(final String jarPath, final Class
pluginInterface) {
try
{
final URI uri = new File(jarPath).toURI();
final URL url = uri.toURL();
final URLClassLoader ucl = new URLClassLoader(new URL[] { url });
try
{
final Class<?> pluginClass = Class.forName("FooImpl", true, ucl);
// Verify if plugin implements plugin interface.
if (pluginClass.getInterfaces()[0].getName().equals(pluginInterface.getName()))
{
// Instantiate plugin.
return pluginClass.newInstance();
} }//[...] </code></pre>
This part is actually working well i think so because after doing some
sysout on the pluginClass i notice: the .getMethods() =
[public void FooImpl.write(..someArgumentType..) throws Exception,
public abstract void some.package.Foo.write(..someArgumentType..)
throws Exception]
the .getGenericInterfaces() = [interface some.package.Foo]
But when i try to call the method write here is what i get
java.lang.AbstractMethodError: FooImpl.write(..SomeArgumentType..;)V
I dont know why there is a ";" and a "V"
So basically i think that it try to call the interface method instead
of the implemented one. So i'm wondering What is going on!
As usual, Thank you for your time and help
An AbstractMethodError suggests that your code is trying to use a different version of a class at runtime compared to the class it was originally built against. You need to ensure that in your execution environment, there isn't a rogue version of your interface implementation on the classpath.

Java ClassLoader: load same class twice

I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.
But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the class.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> compiledClass = cl.loadClass(stringClass);
What am I missing? like a newInstance or something?
A classloader can't replace a class that already has been loaded. loadClass will return the reference of the existing Class instance.
You'll have to instantiate a new classloader and use it to load the new class. And then, if you want to "replace" the class, you'll have to throw this classloader away and create another new one.
In response to your comment(s): do something like
ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);
This classloader will use the "default delegation parent ClassLoader" and you have to take care, the class (identified by it fully qualified classname) has not been loaded and can't be loaded by that parent classloader. So the "pathToClassAsUrl" shouldn't be on the classpath!
You have to load a new ClassLoader each time, or you have to give the class a different name each time and access it via an interface.
e.g.
interface MyWorker {
public void work();
}
class Worker1 implement MyWorker {
public void work() { /* code */ }
}
class Worker2 implement MyWorker {
public void work() { /* different code */ }
}
As it was stated before,
Each class loader remembers (caches) the classes that is has loaded before and won't reload it again - essentially each class loader defines a namespace.
Child class loader delegates class loading to the parent class loader, i.e.
Java 8 and before
Custom Class Loader(s) -> App Class Loader -> Extension Class Loader -> Bootstrap Class Loader
Java 9+
Custom Class Loader(s) -> App Class Loader -> Platform Class Loader -> Bootstrap Class Loader.
From the above we can conclude that each Class object is identified by its fully qualified class name and the loader than defined it (also known as defined loader)
From Javadocs :
Every Class object contains a reference to the ClassLoader that
defined it.
The method defineClass converts an array of bytes into an instance of
class Class. Instances of this newly defined class can be created
using Class.newInstance.
The simple solution to reload class is to either define new (for example UrlClassLoader) or your own custom class loader.
For more complex scenario where you need to substitute class dynamic proxy mechanism can be utilized.
Please see below simple solution I used for a similar problem to reload same class by defining custom class loader.
The essence - override findClass method of the parent class loader and then load the class from bytes read from the filesystem.
MyClassLoader - overrides findClass and executed defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
SimpleClass - experiment subject -
** IMPORTANT : Compile with javac and then remove SimpleClass.java from class path (or just rename it)
Otherwise it will be loaded by System Class Loader due to class loading delegation mechanism.**
from src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
#Override
public long getCount() {
return count;
}
}
SimpleInterface - subject interface : separating interface from implementation to compile and execute output from the subject.
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
Driver - execute to test
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}
I think the problem might be more basic than what the other answers suggest. It is very possible that the class loader is loading a different file than what you think it is. To test out this theory, delete the .class file (DO NOT recompile your .java source) and run your code. You should get an exception.
If you do not get the exception, then obviously the class loader is loading a different .class file than the one you think it is. So search for the location of another .class file with the same name. Delete that .class file and try again. Keep trying until you find the .class file that is actually being loaded. Once you do that, you can recompile your code and manually put the class file in the correct directory.

Categories

Resources