Usually we can use java reflection to get a class by simply doing Class.forName("fully qualified class name").
Now I have a class file (.class) located in client machine. For example, if the client machine is windows and the the class file myClass.class is located in c:\tmp\myClass.class. I have to "process" this class file in another machine in the server to know the class's methods etc.
Please do not ask why this is needed:-) Any suggestions on how to do it?
I think you should make use of Network class loader here. Check this out.
URLClassLoader loader = new URLClassLoader(new URL[] { new
URL("http://123.321.456.121:8080/test/") });
Class c = loader.loadClass ("Hello");
Object o = c.newInstance();
Related
I have a program where I want the user to be able to choose a .java class file from the file system, and then have that class loaded into the program.
I'm using a JFileChooser to allow the user to select a file. Then, I tried converting that file to a URL, and using a URLClassLoader to load the class (as suggested by these answers).
The problem is that, when I want to use the loadClass() method, I don't know the "full class name" of the class (e.g. java.lang.String). So, I don't know how to make this method work. Is there a way to get this class name? Or is there another way to do this?
Here is a sample of my code:
// Open the file chooser
JFileChooser fileChooser = new JFileChooser();
fileChooser.showOpenDialog(null);
File obtainedFile = fileChooser.getSelectedFile();
// Create the class loader from the file
URL classPath = obtainedFile.toURI().toURL();
URLClassLoader loader = new URLClassLoader(new URL[] {classPath});
// Get the class from the loader
Class<?> theClassIWant = loader.loadClass("the file name"); // What do I put here??
Load a single class file is generally completely useless. Said class file isn't alone; it has more class files that are relevant. Even if you think 'nah, there is just one source file, do not worry about this', note that a single java file can easily generate multiple class files.
Thus, two options:
Don't load class files. Load jar files.
Use the usual mechanisms (META-INF/services or META-INF/MANIFEST.MF) to put some sort of class name in there so you know what to load. Then create a new classloader with the provided jar, load the manifest, figure out the main class, load that, and run it.
Attempt to determine the 'root' for the loaded class file and include that on the classpath.
This is quite difficult - the problem is, to 'load' a class file you need to tell the loader what the fully qualified name is of that class before it is loaded. But how do you know the fully qualified name? You can surmise the class name from the file (not quite always true, but usually), but the package is a more difficult issue.
You can open the class file yourself as a binary stream and write a basic class file format parser to get the fully qualified class name. Easy for an experienced java programmer. Quite tricky for someone new to java (which I gather you are, if you think this is a good idea).
You can also use existing tools to do this, such as bytebuddy or asm.
Finally, you can try a spaghetti-at-the-wall method: Keep travelling up the directory until it works. You know it isn't working if exceptions occur.
For example, to load C:\MyDir\Whatever\com\foo\MyApp.class, You first try creating a new classloader (see the API of URLClassLoader which is part of core java) using as root dir C:\MyDir\Whatever\com\foo, and then you ask it to load class MyApp.
If that works, great (but usually trying to load package-less classes is simply a non-starter, you're not supposed to do that, the CL API probably doesn't support it, intentionally, there is no fixing that).
If it doesn't, instead try C:\MyDir\Whatever\com, and load class foo.MyApp. If that doesn't work, try C:\MyDir\Whatever and load class com.foo.MyApp, and so on.
The considerable advantage is, if there is another class sitting right next to MyApp.class, and MyApp needs it, this will work fine.
You'll need to write a while loop (traversing the path structure using Paths.get and p.getParent()), catch the right exception, manipulate the path into the class name (using .replace and +), and, of course, create a class loader (URLClassLoader), load classes with it (invoke loadClass), and if you intend on running it, something like thatClass.getConstructor().newInstance() and then thatClass.getMethod("someMethod", String.class, /* all the other args here */).invoke(theInstanceYouJustMade, "param1", /*all other params */) to actually 'run' it, more to be found in the java.lang.reflect package.
I am currently loading Java classes using Class.forName() to load it.
clazz = Class.forName("interfaces.MyClass");
But now I want to load classes from different directory, I have tried to set classpath by
clazz = Class.forName("-cp \"C:/dir\" distantinterfaces.DistantClass");
With no success and ClassNotFoundException. Full path to distant class is:
C:/dir/distantinterfaces/DistantClass.class
Use an URLClassLoader for this. The code might be something along the lines of:
File f = new File("C:/dir");
URL[] cp = {f.toURI().toURL()};
URLClassLoader urlcl = new URLClassLoader(cp);
Class clazz = urlcl.loadClass("distantinterfaces.DistantClass");
Either the directory is in the classpath, and you can use Class.forName() (which only accepts fuly qualified name classes, and not -cp command line options), or it's not in the classpath and you should then use a custom class loader.
You're not saying what you really want to do (why are you loading classes dynamically), but your best bet is to have the directory in the classpath.
You have to create an instance of ClassLoader which is aware of the directory with classes. See stackoverflow questions tagged urlclassloader.
The workings of my program:
Connect to server
Get String
Decrypt String
Send it back
I'm decrypting with a class i downloaded from the server. The class changes everytime and should be downloaded everytime i start my program
It HAS! to be in package named etc/etc/client/file.class
It works flawless when i'm testing it INSIDE eclipse cause the package folder is then accesible
But what do i do when i want to export is as runnable .jar ? Then i can't write in the package folder?
The line that's loading the class:
(The class extends Base64 which is already in the folder)
etc.sec.client.Base64 decode = (etc.sec.client.Base64)Class.forName("etc.sec.client." + handlerClass).newInstance();
// Handler class is the name of the class
The folder i'm downloading the class to before loading newInstance():
bin/etc/sec/client/"+filename+".class
Works perfect in eclipse but i do not know how to make it work when exporting to .jar
You will have to load the class using a new class loader.
public void go() throws Exception {
ClassLoader cl = new URLClassLoader(new URL[] { new URL("file:///home/ben/") }, this.getClass().getClassLoader());
Class decoderclass = cl.loadClass("etc.sec.client." + handlerClass);
etc.sec.client.Base64 decode = (etc.sec.client.Base64)decoderclass.newInstance();
System.out.println(decode.toString());
}
If download your class into:
/home/ben/etc/sec/client/
That should instantiate the class fine. Naturally you will have to use the interface available at compile time, etc.sec.client.Base64 must be an interface or your handler class must inherit from it.
Let us have a Groovy/Java application that should use a set of classes, defined in external *.jar-files (suppose they are located near the main executable jar).
So, the main class (let us call it Main) should load plugin.jar file at runtime and call some instance method on the class, defined in that jar (for some convention, suppose the class has the name as its jar - Plugin in our case).
The Main class could not know which plugins it has until it is runned. Let's throw away the CLASSPATH and java -jar run arguments and just do the magic with code only.
So, how this could be done and how the plugin.jar should be created (using Eclipse in my case) in order to be correctly loaded?
PS: yeah, i do compile my groovy sources into jar file. But i need to perform class loading and invoke exactly on-the-fly.
The secret was really simple!
Using URLClassLoader does the trick.
So, Groovy code:
ClassLoader loader = new URLClassLoader((URL[]) [
new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\").toURI().toURL()
])
Class c = loader.loadClass("src.SomeClass1")
c.invokeMethod("main", (String[]) ["Hello", "World"])
And the Java one:
File file = new File("C:\\Users\\errorist\\workspace\\javatest1\\bin\\");
URL[] urls = new URL[] { file.toURI().toURL() };
ClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("src.SomeClass1");
c.invokeMethod("main", new String[] { "Hello", "World!" });
The OSGi framework supports dynamic loading of plug-ins. There are multiple implementations, including Equinox, which underpins Eclipse itself.
My application uses JDBC database drivers. I load these from a jar file, db2jcc.jar in the case of DB2 which I'm currently working with. With this jar in the classpath, everything is fine, but I have a requirement to find the jar from a property in the application's config file instead - for example,
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar
I can load the class via a URLClassLoader ok, but the problem is that I need to treat the object thus created as an explicit DB2XADataSource. For example:
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("com.ibm.db2.jcc.DB2XADataSource");
DB2XADataSource dataSource = (DB2XADataSource) xaClass.newInstance();
dataSource.setCurrentSchema(DATABASE_SCHEMA); // <- dataSource has to be a
dataSource.setDatabaseName(DATABASE_NAME); // DB2XADataSource to do this
(rearranged and renamed somewhat; I actually do the loadClass in the constructor of the class that contains this code, while the newInstance is in one of its methods.)
I guess I'm getting into a classloader tangle because the classloader that loaded my class is trying to find DB2XADataSource in order to do the cast, but the URL classloader is not above it in the tree. The trouble is, it being long after I should have stopped working for the day (here in the UK) I can't think how best to solve it in a vaguely neat and sane manner.
Ideas?
Thanks.
The simplest approach is to just use the java.beans API (or direct reflection if you must) to invoke the setter methods.
Alternatively: Your database code requires to link to the dynamically loaded code. Therefore, dynamically load your database code. How much is up to you. You might load almost everything except the "bootstrap".
Yep - the class can't load its own dependencies. You could do some ClassLoader magic, but I imagine it would get messy very quickly.
One way to reduce the amount of reflection would be to put any code that depends on DB2XADataSource into an implementation that is invoked via an interface available to the calling ClassLoader.
//in mydb2driver.jar
public class MyDb2Driver implements IDriver {
private DB2XADataSource dataSource = new DB2XADataSource();
public void init() {
dataSource.setCurrentSchema(DATABASE_SCHEMA);
}
//etc.
}
This is loaded with your driver:
database.driver=/opt/IBM/db2/V9.5/java/db2jcc.jar:/foo/mydb2driver.jar
Invoking code is in the regular classpath:
public interface IDriver {
public void init();
//etc.
}
...
URLClassLoader dbClassLoader = new URLClassLoader(new URL[]{driverJar});
xaClass = dbClassLoader.loadClass("foo.MyDb2Driver");
IDriver dataSource = (IDriver) xaClass.newInstance();
dataSource.init();