AbstractMethodError On a Dynamic Loaded Jar - java

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.

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.

Writing Junit Testcase for a default method in an Interface

I am using Java 1.8 and using default method in Interface.
As we are targeting to write JUnit for all the methods we have I have a situation that need to write Junit for default method. Can somebody help me please to know whether we can achieve it.
My Interface is below.
public interface DataFilter {
default public TableInclusions parseYaml(String path) throws IOException {
Resource resource = new ClassPathResource(path);
Yaml yaml = new Yaml();
yaml.setBeanAccess(BeanAccess.FIELD);
return yaml.loadAs(resource.getInputStream(), TableInclusions.class);
}
public DBMSEvent filter(DBMSEvent jsonString) throws FilterException;
public void setYaml(String path);
}
Should I need to write Junit for the method of the class which implements this Interface and uses this default method to test it?
In your test you could do:
DataFilter dataFilter = new DataFilter() {
//Add no-op implementation for other methods
};
datafilter.parseYaml(testPath);
But looking add your interface I don't think you should have this default method at all. I think it would be better to inject a TableInstructorParser with this method in it. That would be better for your separation of concerns.

Inject Data with Guice into JavaFX ViewController

Today I added Guice to my Java FX Application. The main goal was to replace the singletons I had with Injection and break up dependencies.
So far everything worked fine, this is the code I have to start a new Scene:
public class App extends Application{
public static void main(String[] args){
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final String LANGUAGE_BUNDLE = "myBundlePath";
final String FXML = "myFXMLPath";
try {
ResourceBundle resourceBundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE, Locale.GERMAN, this.getClass().getClassLoader());
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(FXML), resourceBundle, new JavaFXBuilderFactory(), getGuiceControllerFactory());
primaryStage.setScene(new Scene(fxmlLoader.load()));
primaryStage.show();
}catch (IOException ex) {
ex.printStackTrace();
}
}
private Callback<Class<?>, Object> getGuiceControllerFactory(){
Injector injector = Guice.createInjector(new GuiceModule());
return new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> clazz) {
return injector.getInstance(clazz);
}
};
}
}
My Guice Module looks like this:
public class GuiceModule extends AbstractModule {
#Override
protected void configure() {
bind(ITester.class).to(Tester.class);
bind(ISecondTest.class).to(SecondTest.class);
}
}
Please note that i substituted the paths for the ressource bundle and the fxml file as they would have revealed my identity. But loading and everything works, so this shouldn't be a problem ;)
Now the problem is, that I want to instantiate a new view with a button click in a different view. The second view should display a more detailed version of the data in view 1.
Everything that I need to pass to the second view is an Integer (or int), but I have absolutely no clue on how to do this.
I have the standard FX setup with:
View.fxml (with a reference to the ViewController)
ViewController.java
Model.java
ViewModel.java
The ViewController then binds to properties offered by the ViewModel.
I need the int to choose the correct model.
Everything I could find was about the Annotation #Named but as far as I can see, this wouldn't be usable to inject dynamic data.
Could you please give me a hint what this what I want to do is called?
Or long story short: How can I inject a variable, chosen by a different view, in a second ViewController, and leaving the rest in the standard FX-way?
Any help appreciated and thanks in advance!
Regards, Christian
After trying around a bit more, it seems like I found a solution by myself!
However, it "feels" ugly what I'm doing, so I'd like to have some confirmation ;)
First the theory: Guice supports "AssistedInject". This is, when a class can not be constructed by a default constructor. In order to be able to use AssistedInject, you have to download the extension (I downloaded the jar from maven repository).
What AssistedInject does for you is that it allows you to specify a factory which builds the variable for you. So here is what I have done:
First, create an interface for the class which you want to use later, in my case:
public interface IViewController {
}
Second, create an interface for the factory. Important: you do not have to implement the factory
public interface IControllerFactory {
ViewController create(#Assisted int myInt);
}
Third, add the constructor with the corresponding parameters to your class which you want to instantiate later, and let it implement the interface you created like so:
public class ViewController implements IViewController{
#AssistedInject
public ViewController(#Assisted int i){
final String LANGUAGE_BUNDLE = "languageBundle";
final String FXML = "View.fxml";
try{
ResourceBundle resourceBundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE, Locale.GERMAN, this.getClass().getClassLoader());
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(FXML), resourceBundle, new JavaFXBuilderFactory());
fxmlLoader.setController(this);
Stage second = new Stage();
second.setScene(new Scene(fxmlLoader.load()));
second.show();
}catch (IOException e){
e.printStackTrace();
}
System.out.println("ViewController constructor called with: " + i);
}
Here are a few things to note:
The annotation "#AssistedInject" for the method
The annotation "#Assisted" for the parameter which we want to supply externally
we set the controller for the loader manually (with fxmlLoader.setController(this);)
I had to remove the controller configuration in the fxml file, so no "fx:controller" in the fxml!
Next we need to add a variable into the class from where we want to instantiate the other class:
#Inject
IControllerFactory controllerFactory;
We can use it in the class like so:
controllerFactory.create(3)
Note: we call the method "create" which we never implemented in the ViewController class! Guice knows it has to call the constructor - magic
As last step, we need to add the connection to our context in our GuiceModule, like so:
#Override
protected void configure(){
install(new FactoryModuleBuilder()
.implement(IPagingDirectoryViewController.class, PagingDirectoryViewController.class)
.build(IPagingDirectoryControllerFactory.class));
}
Note I got the error: Cannot resolve method 'implement java.lang.Class<"The interface class">, java.lang.Class<"The implementing class">'. This was because I forgot to let my Controller class implement the interface.
Okay, so that's how I got it working.
As I said however, I'd be really happy about some opinions!
Regards, Christian
In your Module Configuration you could simply add a Provider Method for FXMLLoader, in which you assign Guices 'injector.getInstance()' as ControllerFactory for the loader.
#Provides
public FXMLLoader getFXMLLoader(com.google.inject.Injector injector) {
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(injector::getInstance);
return loader;
}
All you have to do now, is to bind your ViewControllers in the configure() method of your module configuration.
// for example:
bind(ViewController.class);
And make sure the controller class is properly bound in your fxml file.
fx:controller="your.package.ViewController"
Now you simply use your injector to get an instance of FXMLLoader.

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