How to invoke a Java class of another Jar file? - java

I would like to receive the results dynamically invoke a class in another jar.
For example,
'A' directory in a file named A.jar.
'B' directory in a file named B.jar.
I want to dynamically invoke a class of A.jar file to B.jar file.
This is the main class of A.jar file.
Socket and RMI are not considered because the message exchange technology.
Main Class (B.jar)
public class main {
public static void main(String[] args) {
//It dynamically creates an object of a Message Class in A.jar.
//And it invoke the getMessage function.
//And Save the return value.
}}
Message Class (A.jar)
public class message{
public String getMessage(){
return "Hello!";
}
}

First, you need to dynamically create an instance of the given class:
Class<?> clazz = Class.forName("message");
Object messageObj = clazz.newInstance();
This assumes that the .jar file which contains the message class is on the classpath so that the Java runtime is able to find it. Otherwise, you need to manually load the jar file through a class loader, e.g. like in How should I load Jars dynamically at runtime?. Assumed that your A.jar file which contains the message class is located at c:\temp\A.jar, you can use something like
URLClassLoader child = new URLClassLoader (
new URL[] {new URL("file:///c:/temp/A.jar")}, main.class.getClassLoader());
Class<?> clazz = Class.forName("message", true, child);
to load the jar file and load the message class from it.
In both cases, you can then retrieve the method from the class and invoke it on the created instance:
Method getMessage = clazz.getMethod("getMessage");
String result = (String) getMessage.invoke(messageObj);

Related

Can a invoked java class/method access the main classpath?

After some research, I have managed to invoke a method from a class in a jar file like so:
URLClassLoader child = new URLClassLoader (new URL[] {(new File("myJar.jar")).toURL()}, Main.class.getClassLoader());
Class<?> classToLoad = Class.forName ("com.myClass.TEST", true, child);
Method method = classToLoad.getDeclaredMethod ("onEnable");
Object instance = classToLoad.newInstance ();
Object result = method.invoke (instance);
TEST class:
public void onEnable() {
System.out.println(TempClass.TestString);
}
Where TestString is just "Hello world" and TempClass is in the "main" jar,
and TEST class is in myJar.jar
While invoking the onEnable() method, it throws a NoClassDefError for TempClass
Is there a way the "Client" jar can access the "Main" jar's classpath at runtime?
The goal is to invoke "plugins" much like how Craftbukkit/Spigot works with Plugins,for those who have played with that.

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.

how to update jar class files through programming

I have a jar file which consisting of multiple class files.
Main class file's java file is like
class Main
{
public void setUserType()
{
String utype="user";
}
}
now i want to update the Main class file with usertype "user" to "admin" and recreate the jar file
can you suggest me how can i do this so that the new jar file should have usertype "admin"
These changes should be done through programming not through editor like netbeans or eclipse
Instead of hardcoding things, you should define your code to accept properties either via property file or via environment/System property or command line argument when you start your application like below:
class Main {
public void setUserType(String value) {
String utype=value;//or use System.getProperty("value");if you used -Dvalue=admin in command line for example.
}
public static void main(String args[]) {//see main accepts command line argument
if (args.length == 0) {
throw new IllegalArgumentException("Required parameters are missing");
}
Main main = new Main();
main(args[0]);//just pass whatever you pass as system parameter when you start your application
//if you dont want to pass string across multiple methods and classes just use System.setProperty("value", args[0]); and use it like System.getProperty("value") to access it from anywhere without actually passing this string across.
}
}
And run it with user/admin as you like:
java Main admin
java Main user

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...

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