Maven Project class file not being found by eclipse - java

I have couple of maven projects let's say A & B. I have mvm installed them both before loading them inside eclipse.
One of the class in project A runs a class from project B. This class is trying to create a instance of a another class in B using the newInstance method.
Class clazz = Class.forName(className);
Constructor construct = clazz.getConstructor(new Class[] { String.class, ThreadFactory.class });
ResultClassFromB api = (ResultClassFromB) construct.newInstance(new Object[] {file, threadFactory });
I get InvocationTargetException for above code. On the surface, I understand eclipse can't find class file for ResultClassFromB. I've tried adding the parent folder of ResultClassFromB in the class path of project A but I still see the issue. How do I get past this?
info: file is mostly with comments.

Related

Class loading via reflection using a single class loader

I have a webapp project, which I am running through tomcat. The webapp project has a dependency of a core project. (The core project jar is present in WEB-INF/lib folder).
I have another sample project which has same core project dependency. I need to load a class and invoke a particular method present in the sample project jar via reflection.
I need to load core project's classes that are being used in the sample project as well as web app project via common class loader.
The problem is :- I am unable to load core project classes and sample project classes in a common class loader.
I have tried a lot from past two days like loading up classes using a custom class loader and system class loader, but I always end up getting either NoSuchMethodException or ClassCastException or ClassNotFoundException.
How should I handle this situation of having a common class loader?
Class which I need to load and invoke a method (Sample Project)
public class ObjectiveFunction implements ScriptInterface{//ScriptInterface is a interface present in core project
#Override
public void execute(Map<Integer, CustomObject> map, IModel inputModel, List<AbstractNode> nodes) {
for (Node node : nodes){// this line throws ClassCastException
// IModel is an interface in core project
// AbstractNode (abstract class), CustomObject and Node are present in core project. Different nodes extends AbstractNode
// Some logic
}
Class loading code present in core project which is invoked by webapp
project
URLClassLoader classLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(classLoader, url);
Class<?> classToLoad = classLoader.loadClass(className);
Method executeMethod = classToLoad.getDeclaredMethod("execute", Map.class, IModel.class, List.class);
Object instance = classToLoad.newInstance();
executeMethod .invoke(instance, value1, value2, value3); // This line throws NoSuchMethodException

How to implemented to a shared interface pulled from a WAR

I have a web service we'll call service.war. It implements an interface we'll call ServicePluginInterface. During the startup of service.war, it reads in environment variables and uses them to search for a jar (MyPlugin.jar). When it finds that jar, it then uses a second environment variable to load the plugin within the jar. The class that it loads looks like this:
public class MyPlugin implements ServicePluginInterface {...}
The servlet attempts to load the plugin using code like:
try {
if (pluginClass == null) {
plugin = null;
}
else {
ZipClassLoader zipLoader = new ZipClassLoader(Main.class.getClassLoader(), pluginJar);
plugin = (ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
plugin.getAccount(null,null);
}
} catch (Exception e) {
...
}
The trick is that I don't have source or a jar for ServicePluginInterface. Not wanting to give up so easily, I pulled the class files out of the service.war files. By using those class files as dependencies, I was able to build, without compiler warnings, MyPlugin. However, when actually executed by Tomcat, the section of code above generates a runtime exception:
java.lang.ClassCastException: com.whatever.MyPlugin cannot be cast to com.whomever.ServicePluginInterface
As a second point of reference, I am also able to construct a synthetic class loader (separate java executable that uses the same class loading mechanism. Again, since I do not have the original source to ServicePluginInterface, I used the class files from the WAR. This second, synthetic loader, or faux-servlet if you will, CAN load MyPlugin just fine. So I would postulate that the Tomcat JVM seems to be detecting some sort of difference between the classes found inside the WAR, and extracted class files. However, since all I did to extract the class files was to open the WAR as a zip and copy them out, it is hard to imagine what that might be.
Javier made a helpful suggestion about removing the definition of ServicePluginInterface, the problem with that solution was that the ZipClassLoader that the servlet uses to load the plugin out of the jar overrides the ClassLoader findClass function to pull the class out of the JAR like so:
protected Class<?> findClass(String name) throws ClassNotFoundException
{
ZipEntry entry = this.myFile.getEntry(name.replace('.', '/') + ".class");
if (entry == null) {
throw new ClassNotFoundException(name);
}
...
}
The class ZipClassLoader then recursively loads all parent objects and interfaces from the jar. This means that if the plugin jar does not contain the definition for ServicePluginInterface, it will fail.
Classes defined by different class loaders are different:
At run time, several reference types with the same binary name may be
loaded simultaneously by different class loaders. These types may or
may not represent the same type declaration. Even if two such types do
represent the same type declaration, they are considered distinct. JLS
In that case zipLoader returns an instance of MyPlugin that implements the other ServicePluginInterface (is it loaded from the zip too?):
(ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
It seems that the application server already has a definition of ServicePluginInterface, then you don't need to redeploy it. It should be enough to add the required files (ServicePluginInterface, etc.) as non-deployed dependecies of your project.
Another approach goes by living with the fact, and accessing methods in ServicePluginInterface via reflection (use the Class object returned by zipLoader, instead of ServicePluginInterface.class).

Double bind jar dependency

I have one Java project (Project A) in which I load other jars. These jars (Project B) all have one class implements an application interface. In order to know that interface, project b has to have project A as library.
Now, when starting project A, it loads the jar of project B, looks for the application class, gets the constructor and trys to create an object, but this fails:
The constructor from B's class creates an object implementing the interface which is known to project B, ALTHOUGH this is the same interface as in project A, but it seems that these two are not seen as being equal.
How can I solve this double bind dependency?
Edit:
This is how I try to create an application in project A:
clazz = Class.forName(className, true, loader);
ctor = (Constructor<? extends Application>) clazz.getConstructor();
Application app = ctor.newInstance();
The exception occurs in the third line where it says app is of type SpecificApplication (which is defined in the loaded jar and implements the Application interface) and thus cannot be cast to Application.
Put the interface in another jar, which is referenced by projects A and B.

ServiceLoader.next causing a NoClassDefFoundError

I'm asking because I'm totally not sure I've done the right thing. I'm using Eclipse for a web project. Let's call it WebProject (duh) in the package com.web.project.
I want WebProject to load JAR plugins at runtime, so I thought I could take advantage of java.util.ServiceLoader. So I created an interface com.web.project.WebProjectPlugin in the WebProject project with all the methods the plugins must implement.
Then I created the project PluginProject, adding WebProbject/build/classes in its Build path as a class folder:
package com.web.project.plugin;
import com.web.project.WebProjectPlugin;
public class TestPlugin implements WebProjectPlugin {
// Implementation of the interface methods...
}
Then I created a META-INF/services folder in the plugin project, put the text file com.web.project.WebProjectPlugin inside, containing the sole line "com.web.project.plugin.TestPlugin".
I exported the JAR file checking out the added build/classes folder and put it somewhere in the hard drive. When WebProject starts up, it does the following:
File[] jlist = pluginsDir.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getPath().toLowerCase().endsWith(".jar");
}
});
URL[] urls = new URL[jlist.length];
for (int i = 0; i < jlist.length; i++)
urls[i] = jlist[i].toURI().toURL();
URLClassLoader ucl = new URLClassLoader(urls);
ServiceLoader<WebProjectPlugin> srvl =
ServiceLoader.load(WebProjectPlugin.class, ucl);
Iterator<WebProjectPlugin> iter = srvl.iterator();
while (iter.hasNext()) {
WebProjectPlugin plugin = iter.next();
plugins.add(plugin);
}
pluginsDir is a File object pointing to the directory the JAR file is in. At first it seems that srvl does its job, since iter isn't empty, but then it throws the dreaded NoClassDefFoundError when it reaches iter.next().
I've already managed to create a plugin manager project to test ServiceLoader, and it runs just fine, but it's a plain console Java application, not a web project. So, what am I doing wrong here?
I'm a little puzzled: how can it not find the class definition for com.web.project.WebProjectPlugin, since it's in the same project that is running? Has that something to do with the URLClassLoader object I'm using?
This is the stack trace.
Try assigning the parent classloader to your URLClassLoader
URLClassLoader loader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
A WebProject expects a certain hierarchy of class loaders, so it might be that your classes are not visible to each other if the parent/child hieararchy is not set properly.

How to build automatically different JARs from same code using Eclipse?

I have many more classes in the project but for now please consider only A, B and C classes.
abstract public class A {...}
public class B extends A {...}
public class C extends A {...}
Then later I have a code, say in class D, like this
A a = new B();
//A a = new C();
//use a's methods
So my question now is how to easily configure in Eclipse building of two separate JARs. First one should have B.class included and C.class excluded and code as A a = new B(); The second one should have C.class included and B.class excluded and code as A a = new C();
I do not know many things about Ant and Maven. Do I need to use them in this case?
Maybe, something wrong with my design, if so, please let me know.
What you want to is mainly a code loading problem, not an Ant problem.
Just the two statements you presented for creating a new class instance:
A a = new B();
A a = new C();
The constructors are called using static code. Ant can not change the code, therefore the only way with Ant I see is generating a Factory class with Ant as part of the build process, depending if A or C is included into the JAR
But that would result in a project that can no longer be used directly in Eclipse as the original source code in the Eclipse project misses the factor class.
A IMHO better approach is dynamic class loading (may be combined with reflection). You can automatically search for classes that extend A or you add a configuration file/info to the JAR specifying which class to create (e.g. properties file).
Place the properties file in the src folder with the Java file and load it via this.getClass().getResource("myclass.config");
The config file can contain the class name that should used for creating a new instance.
The following code snipped assumes that B and C both have a public constructor that does not take any argument:
String classNameToLoad = ... // loaded from config file example "mypackage.B"
final Class<?> c = Class.forName(classNameToLoad);
final Class<? extends A> ac = c.asSubclass(A.class);
final Constructor<? extends A> a_ctor = ac.getConstructor();
final A a = a_ctor.newInstance();
In your eclipse you if you right click your project, you will have an option Export
Click on that, and select jar, then name the jar, select the classes you want in it.
Repeat this for the other jar.
check this http://help.eclipse.org/juno/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftasks-33.htm
The easiest way to do this would be to have 3 separate Java projects in Eclipse. Based on your example, that would be
B code project
C code project
A common code
Eclipse allows you to reference the bin directory of another project from your project in the build path.
Then you can export (or use Ant) the B and C projects as separate projects.

Categories

Resources