Java Annotations don't work with external Jar Files - java

So, I'm currently working on a plugin system (plugins are jar files located in a plugins folder) for my command line "operating system". Works fine but the API doesn't.
I am using Annotations to get the Plugin main class and the event classes. However, it doesn't seem to find my Annotations.
This is what I use to load my plugins:
public static void loadPlugin(File file)
{
try
{
URL urlList[] =
{ new File("plugins/" + file.getName()).toURI().toURL() };
URLClassLoader classLoader = new URLClassLoader(urlList);
Class<?> pluginclass, eventclass = pluginclass = null;
String name, version = name = null;
Priority priority = Priority.NORMAL;
String[] classes = Utils.getJarClasses(file, "plugin").toArray(new String[]
{});
for (String s : classes)
{
System.out.println(s);
Class<?> c = classLoader.loadClass(s);
Plugin p = c.getAnnotation(Plugin.class);
EventHandler e = c.getAnnotation(EventHandler.class);
if (p != null)
{
System.out.println("not null!");
pluginclass = c;
name = p.name();
version = p.version();
priority = p.priority();
}
if (e != null)
{
eventclass = c;
}
}
switch (priority)
{
case NORMAL:
plugins.add(new Class<?>[]
{ pluginclass, eventclass });
case DEVELOPER_API:
apis.add(new Class<?>[]
{ pluginclass, eventclass });
}
print("Loaded Plugin: " + name + " V" + version + " with priority " + priority.toString() + ".");
} catch (Exception e)
{
e.printStackTrace();
}
}
It's printing out the class it's currently looping through just fine, but p and e are always null, no matter what. version and name are always null, too, obviously.
My Annotation classes have got #Retention(RetentionPolicy.RUNTIME), that's the main reason why I'm asking here.
I have tested a similar system, just that one didn't use jar files (classes inside of a package, instead) and worked fine.
Thanks in advance.

One reason that could explain the described behavior is that you are mixing class loaders: an annotation will only be visible if both the annotation class and the annotated type are loaded by the same class loader. This has to do with the use of the Reflection API as the mechanism employed to actually perform the annotation lookup process.
In your code you are using a custom class loader, classLoader, to load the classes from an external jar file, but both the Plugin and EventHandler classes are loaded by the class loader running your loadPlugin method.
In order to solve the problem, I think you can provide the class loader which is running the loadPlugin method as a parent class loader of the custom one, something like:
URLClassLoader classLoader = new URLClassLoader(urlList, YourClass.class.getClassLoader());
A similar behavior has been also formerly reported here, in stackoverflow. See, for instance, the accepted answers of this question and this other one.

Related

How to deal with dependency conflicts using custom classloader in java properly?

I have a multimodule project called k-sdk. It has modules like
---> agent
---> core
---> api
---> integration
---> sdk
where (sdk module contains core, api, integration modules and acting as a shaded jar).
This jar can be imported as a dependency in the user application.
Here k-sdk is acting as a middleware to capture request and response of the api calls (both external and internal) and do some custom processing. And to intercept the internal calls i am using servlet-filter & for external calls, i am using ByteBuddy and wrote the required code in the premain method. The corresponding interceptors are written in integration module. User can use this k-sdk by running agent jar(present in agent module) of the k-sdk.
Now comes to the actual problem. Suppose I have written an Interceptor for OkHttp client version 4.10.0 in the integration module and the user is using the same client of version 3.14.9, now these two versions share completely same package name. And there are some uncommon apis, methods etc, which is being used in the OkHttpInterceptor which results in java.lang.NoSuchMethodError because maven only resolves that version of dependency which has the shortest path. And only 1 version of a dependency will be loaded in the classloader.
Now, To resolve this version conflict what i was thinking that i will use the below custom classloader. (ref) as i know that the solution lies in this approach only.
public class ChildFirstClassLoader extends URLClassLoader {
private final ClassLoader sysClzLoader;
public ChildFirstClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
sysClzLoader = getSystemClassLoader();
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// has the class loaded already?
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass == null) {
try {
if (sysClzLoader != null) {
loadedClass = sysClzLoader.loadClass(name);
}
} catch (ClassNotFoundException ex) {
// class not found in system class loader... silently skipping
}
try {
// find the class from given jar urls as in first constructor parameter.
if (loadedClass == null) {
loadedClass = findClass(name);
}
} catch (ClassNotFoundException e) {
// class is not found in the given urls.
// Let's try it in parent classloader.
// If class is still not found, then this method will throw class not found ex.
loadedClass = super.loadClass(name, resolve);
}
}
if (resolve) { // marked to resolve
resolveClass(loadedClass);
}
return loadedClass;
}
#Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> allRes = new LinkedList<>();
// load resources from sys class loader
Enumeration<URL> sysResources = sysClzLoader.getResources(name);
if (sysResources != null) {
while (sysResources.hasMoreElements()) {
allRes.add(sysResources.nextElement());
}
}
// load resource from this classloader
Enumeration<URL> thisRes = findResources(name);
if (thisRes != null) {
while (thisRes.hasMoreElements()) {
allRes.add(thisRes.nextElement());
}
}
// then try finding resources from parent classloaders
Enumeration<URL> parentRes = super.findResources(name);
if (parentRes != null) {
while (parentRes.hasMoreElements()) {
allRes.add(parentRes.nextElement());
}
}
return new Enumeration<URL>() {
Iterator<URL> it = allRes.iterator();
#Override
public boolean hasMoreElements() {
return it.hasNext();
}
#Override
public URL nextElement() {
return it.next();
}
};
}
#Override
public URL getResource(String name) {
URL res = null;
if (sysClzLoader != null) {
res = sysClzLoader.getResource(name);
}
if (res == null) {
res = findResource(name);
}
if (res == null) {
res = super.getResource(name);
}
return res;
}
}
And to improve the user experience, i have to make changes only in the premain method. Now i don't know how to use this classloader to resolve this conflicting issue.
The above classloader is extending URLClassLoader, and the application class loader is no longer an instance of java.net.URLClassLoader in java 9+.
I am very new to this complex solution, Please! help me out.
Before this solution, i tried to repackage classes using maven-shade-plugin but that solution was not working because in premain method using ByteBuddy i was targeting a class of actual package and was using different package in the interceptor so the flow was not going to the interceptor as the class was not matching.

Custom Class Loader in java different behaviour

I am working on a project which requires dynamic Loading of a class from a file system, I googled and found out that I need to use custom ClassLoader. I have implemented my own class loader which is working fine when I run it on console, the problem I when I try to deploy the application on the server it results in ClassNotFoundException.
The problem is the class which I am trying to load contain some references to another class which is already loaded by the application but it is trying to load the reference from the same location.
public class HandlerClassLoader extends ClassLoader {
static Logger log = Logger.getLogger(HandlerClassLoader.class.getName());
URLClassLoader ucl;
private ProcessDefinition processDefinition = null;
boolean flag = false;
public URL[] loadJars() throws MalformedURLException {
PropertiesFiles propFiles = new PropertiesFiles();
File f = new File(propFiles.getValue("jarslocation"));
log.debug("Loading JARS files from " + f.getAbsolutePath());
List<URL> urls = new ArrayList<URL>();
String filesName[] = f.list();
for (String jars : filesName)
if (jars.endsWith("jar")) {
urls.add(new URL("file:///"
+ propFiles.getValue("jarslocation") + "/" + jars));
}
URL[] array = urls.toArray(new URL[urls.size()]);
return array;
}
public HandlerClassLoader() {
super(Thread.currentThread().getContextClassLoader());
log.debug("Called to the " + this.getClass().getName()
+ " No Parameter Constructor");
try {
ucl = new URLClassLoader(loadJars());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
public HandlerClassLoader(ClassLoader parent,
ProcessDefinition processDefinition) {
super(parent);
log.debug("Called to the " + this.getClass().getName()
+ " Parameterized Constructor");
try {
ucl = new URLClassLoader(loadJars());
} catch (MalformedURLException e) {
e.printStackTrace();
}
this.processDefinition = processDefinition;
}
public Class<?> findClass(String className) throws ClassNotFoundException {
log.debug("findClass method of " + this.getClass().getName()
+ " is called with className : " + className);
return ucl.loadClass(className);
}
I think the delegation principle is not working or maybe that server must have a different implementation of classloader.
Most likely, you do not delegate to the correct parent. I assume that picking up Thread.currentThread().getContextClassLoader() results in using the wrong class loader on the application server while this is simply the system class loader (class path) when running from a console app.
Therefore, you need to make sure that the parent that you are handing to your custom class loader is capable of seeing the classes you are intending to load by it.
On a final note, implementing your own class loader is a tricky business. For example, you have not accounted for locating ressources or for defining packages. Both might be required by third-party libraries that you are using. If you really only need to load files from disk, consider using a URLClassLoader.

JarClassLoader - loading jars dynamically - how to?

I have seen many class loader questions, but still was not able to figure why, the error here.
I am writing a program which uses 2 versions of jars. One is needed to get content from older storage, and another to store content in new storage.
Since, I need either of the jar to be loaded at a time, I used JarClassLoader to create a proxy for adding one jar and loading its classes. But I face ClassNotFoundException.
public class HbaseMigrator implements Runnable {
public void run() {
JarClassLoader jcl = new JarClassLoader();
jcl.add("hadoop-0.13.0-core-modified-1.jar");
Object obj1 = JclObjectFactory.getInstance().create(jcl, "UserMigThreadImpl", toProcessQueue,threadName, latch,DBUtil,lock);
MigThread mig = JclUtils.cast(obj1, MigThread.class, jcl);
Thread.currentThread().setContextClassLoader(jcl);
try {
Method method = MigThread.class.getMethod("callthis", new Class[]{});
method.invoke(mig, new Object[]{});
// mig.callthis();
} catch( Exception e) {
e.printStackTrace();
} catch(Error er) {
er.printStackTrace();
}
}
}
Method called is:
public void callthis() {
DFSUtil = new DFSAccessAPIImpl();
.........
}
This class instantiation internally uses hadoop modified jar, which is not picked up from my classloader and it throws ClassNotFoundException.
What is that I am doing wrong ?
JarClassLoader used here is jcloader :
org.xeustechnologies.jcl.JarClassLoader
I experienced this problem with loading plugins to my application, so I decided to try to load all .class files from all jars in path. Maybe this code snipped from my app will help you.
https://bitbucket.org/rsohlich/plagdetector/src/432b52f252ff7647221b7e91b08731bd9cbe2a70/PlagDetectorSpring/src/main/java/cz/sohlich/app/service/impl/PluginHolderImpl.java

Eclipse - JAR creation failed "Class files on classpath not found or not accessible for..."

I have a project in Eclipse that has a red cross on it and will not export to a runnable JAR. I can't remember if I have looked at it since I reinstalled Windows on my laptop, but I know that I haven't changed any code. There are no errors in any of the classes, however the error I get points to the following class that deals with the menu items on Mac OSx:
import java.lang.reflect.*;
public class osxhandler implements InvocationHandler {
protected Object targetObject;
protected Method targetMethod;
protected String proxySignature;
static Object macOSXApplication;
// Pass this method an Object and Method equipped to perform application shutdown logic
// The method passed should return a boolean stating whether or not the quit should occur
public static void setQuitHandler(Object target, Method quitHandler) {
setHandler(new HOsx("handleQuit", target, quitHandler));
}
public static void setAboutHandler(Object target, Method aboutHandler) {
boolean enableAboutMenu = (target != null && aboutHandler != null);
if (enableAboutMenu) {
setHandler(new HOsx("handleAbout", target, aboutHandler));
}
// If we're setting a handler, enable the About menu item by calling
// com.apple.eawt.Application reflectively
try {
Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[] { boolean.class });
enableAboutMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enableAboutMenu) });
} catch (Exception ex) {
System.err.println("MacOSHandler could not access the About Menu");
ex.printStackTrace();
}
}
public static void setPreferencesHandler(Object target, Method prefsHandler) {
boolean enablePrefsMenu = (target != null && prefsHandler != null);
if (enablePrefsMenu) {
setHandler(new HOsx("handlePreferences", target, prefsHandler));
}
// If we're setting a handler, enable the Preferences menu item by calling
// com.apple.eawt.Application reflectively
try {
Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[] { boolean.class });
enablePrefsMethod.invoke(macOSXApplication, new Object[] { Boolean.valueOf(enablePrefsMenu) });
} catch (Exception ex) {
System.err.println("MacOSHandler could not access the About Menu");
ex.printStackTrace();
}
}
// Pass this method an Object and a Method equipped to handle document events from the Finder
// Documents are registered with the Finder via the CFBundleDocumentTypes dictionary in the
// application bundle's Info.plist
public static void setFileHandler(Object target, Method fileHandler) {
setHandler(new HOsx("handleOpenFile", target, fileHandler) {
// Override MacOSHandler.callTarget to send information on the
// file to be opened
public boolean callTarget(Object appleEvent) {
if (appleEvent != null) {
try {
Method getFilenameMethod = appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null);
String filename = (String) getFilenameMethod.invoke(appleEvent, (Object[])null);
this.targetMethod.invoke(this.targetObject, new Object[] { filename });
} catch (Exception ex) {
}
}
return true;
}
});
}
// setHandler creates a Proxy object from the passed MacOSHandler and adds it as an ApplicationListener
#SuppressWarnings({ "unchecked", "rawtypes" })
public static void setHandler(HOsx adapter) {
try {
Class applicationClass = Class.forName("com.apple.eawt.Application");
if (macOSXApplication == null) {
macOSXApplication = applicationClass.getConstructor((Class[])null).newInstance((Object[])null);
}
Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener");
Method addListenerMethod = applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { applicationListenerClass });
// Create a proxy object around this handler that can be reflectively added as an Apple ApplicationListener
Object MacOSHandlerProxy = Proxy.newProxyInstance(HOsx.class.getClassLoader(), new Class[] { applicationListenerClass }, adapter);
addListenerMethod.invoke(macOSXApplication, new Object[] { MacOSHandlerProxy });
} catch (ClassNotFoundException cnfe) {
System.err.println("This version of Mac OS X does not support the Apple EAWT. ApplicationEvent handling has been disabled (" + cnfe + ")");
} catch (Exception ex) { // Likely a NoSuchMethodException or an IllegalAccessException loading/invoking eawt.Application methods
System.err.println("Mac OS X Adapter could not talk to EAWT:");
ex.printStackTrace();
}
}
// Each MacOSHandler has the name of the EAWT method it intends to listen for (handleAbout, for example),
// the Object that will ultimately perform the task, and the Method to be called on that Object
protected HOsx(String proxySignature, Object target, Method handler) {
this.proxySignature = proxySignature;
this.targetObject = target;
this.targetMethod = handler;
}
// Override this method to perform any operations on the event
// that comes with the various callbacks
// See setFileHandler above for an example
public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException {
Object result = targetMethod.invoke(targetObject, (Object[])null);
if (result == null) {
return true;
}
return Boolean.valueOf(result.toString()).booleanValue();
}
// InvocationHandler implementation
// This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
if (isCorrectMethod(method, args)) {
boolean handled = callTarget(args[0]);
setApplicationEventHandled(args[0], handled);
}
// All of the ApplicationListener methods are void; return null regardless of what happens
return null;
}
// Compare the method that was called to the intended method when the MacOSHandler instance was created
// (e.g. handleAbout, handleQuit, handleOpenFile, etc.)
protected boolean isCorrectMethod(Method method, Object[] args) {
return (targetMethod != null && proxySignature.equals(method.getName()) && args.length == 1);
}
// It is important to mark the ApplicationEvent as handled and cancel the default behavior
// This method checks for a boolean result from the proxy method and sets the event accordingly
protected void setApplicationEventHandled(Object event, boolean handled) {
if (event != null) {
try {
Method setHandledMethod = event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class });
// If the target method returns a boolean, use that as a hint
setHandledMethod.invoke(event, new Object[] { Boolean.valueOf(handled) });
} catch (Exception ex) {
System.err.println("MacOSHandler was unable to handle an ApplicationEvent: " + event);
ex.printStackTrace();
}
}
}
}
Any ideas as to why I can't export/compile? I've never had this issue before.
Just do a clean and/or rebuild on the project.
You can find it under the Project menu of Eclipse.
I also had a different, degenerate case of this problem. Turned out, we had a class in our project that had a file (so Eclipse kept it on the classpath) but no actual class defined in the file (the file only had imports and a class comment... probably a merge gone wrong). Anyway, deleting the file solved the issue.
It’s quite hateful that Eclipse always generates hidden files .project
and .classpath in project folder. Sometimes you’re not aware if
something goes wrong in these files.
After upgrading your Eclipse and if you found the following compile
error, I’d suggest you to check .classpath in your project folder.
The project was not built since its build path is incomplete. Cannot
find the class file for java.lang.Object. Fix the build path then try
building this project
Most likely you would see a line like this.
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/ org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/j2re1.4.2_03"/>
The stupid Eclipse appended this for no reason. Just simply remove it
to make it work again. ;)
/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/j2re1.4.2_xx
Source: http://hochit.com/2006/07/06/eclipse-upgrading-problem-javalangobject-not-found/
In addition, you can check your project settings in eclipse. Right click on your project and choose properties. Go to Java Build Path and there should be more specific information of the problem. Most likely you set the JDK to an Version which doesn't exist on the new System.
If this doesn't help too, select your project and then use the menu entry Source->Clean Up.
In my case, the classes were empty, and the compiler whined:
Class files on classpath not found or not accessible for: 'ibDemo/src/com/ib/controller/LocationCode.java'
Class files on classpath not found or not accessible for: 'ibDemo/src/com/ib/controller/PairPanel.java'
To solve this I'd to add a class declaration:
public class LocationCode
{
}
and
public class PairPanel
{
}
I got referred here, because I had the same error.
I am using maven on eclipse. I did right click on repo, chose build path->Conifgure build->Project References and checked the project references for my repo. This worked for me.
I was also getting the same error. In my case problem was, I had put same jar multiple times once through "user library" & next time through "build path" on the same Project. Just deleted the repeated jars from the classpath & got ride of the above error.
I had the same error and after trying out multiple recommendations, nothing had worked out. So I created a new workspace and refer to this project. After that, it got successfully built and exported the JAR without errors.
Not sure this might be the best possible solution, but do check java build path. I had it pointing to a wrong location because of which I was facing class not found error.
Once java build path was fixed, the problem was resolved.
I came here on same error. In my case, nothing was compiling (building?) and Eclipse didn't tell me there was any issue with the build other than these cryptic messages. I eventually unzipped the jar file and saw that it had no classes in it. It was because because the project I referenced in my build path wasn't built. In my case, the project would not compile in a million years, but I had access to jar files from R&D dept who could and did compile it in their own way. So I referenced those jar files instead. Now my classes compile and the error went away. I'm sure I would have done that in the first place but "Helpful" Eclipse suggested for me to reference the unbuilt project so I went along with the bad suggestion!
I closed all tabs with files in Eclipse, and it's fixed problem.
In my case, I was getting the same problem and I noticed I mvn clean and tried to export the jar and end-up getting the same error.
It worked for me after mvn install.

Chained classloader conundrum

I'm having some hard time with Java classloaders, maybe somebody could shed some light on this. I have extracted the essence of the problem to the follwing:
There are three classes - ClassLoaderTest, LoadedClass and LoadedClassDep. They are all on different paths.
ClassLoaderTest instantiates a new URLClassLoader - myClassLoader, priming it with the paths to the remaining two classes and it's own classloader (i.e. the application classloader) as parent. It then uses Class.forName("com.example.LoadedClass", true, myClassLoader) to load the LoadedClass through reflection. The LoadedClass imports the LoadedClassDep. If I run the above, using:
java -cp /path/to/the/ClassLoaderTest ClassLoaderTest "/path/to/LoadedClass" "/path/to/LoadedClassDep"
and using the command line arguments to prime the URLClassLoader everything works fine. Using static initialisers I confirm that the two classes are loaded with an instance of a URLClassLoader.
HOWEVER, and this is the problem, if I do:
java -cp /path/to/the/ClassLoaderTest:/path/to/the/LoadedClass ClassLoaderTest "/path/to/LoadedClassDep"
this fails to load the LoadedClassDep (ClassNotFoundException). The LoadedClass is loaded correctly, but with sun.misc.Launcher$AppClassLoader, not the URLClassLoader!
It would appear that since the application classloader is capable of loading the LoadedClass it also attempts to load the LoadedClassDep, disregarding the URLClassLoader.
Here's the full source code:
package example.bc;
public class ClassloaderTest {
public static void main(String[] args) {
new ClassloaderTest().run(args);
}
private void run(String[] args) {
URLClassLoader myClasLoader = initClassLoader(args);
try {
Class<?> cls = Class.forName("com.example.bc.LoadedClass", true, myClasLoader);
Object obj = cls.newInstance();
cls.getMethod("call").invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
private URLClassLoader initClassLoader(String[] args) {
URL[] urls = new URL[args.length];
try {
for (int i = 0; i < args.length; i++) {
urls[i] = new File(args[i]).toURI().toURL();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
return new URLClassLoader(urls, getClass().getClassLoader());
}
}
package com.example.bc;
import com.bc.LoadedClassDep;
public class LoadedClass {
static {
System.out.println("LoadedClass " + LoadedClass.class.getClassLoader().getClass());
}
public void call() {
new LoadedClassDep();
}
}
package com.bc;
public class LoadedClassDep {
static {
System.out.println("LoadedClassDep " + LoadedClassDep.class.getClassLoader().getClass());
}
}
I hope I made this clear enough. My issue is, I only know the path to ClassLoadeTest at compile time, I have to use strings at runtime for the other paths. So, any ideas how to make the second scenario work?
I'd expect the application classloader to load LoadedClass in the second case, since classloaders delegate to their parent initially - this is the standard behaviour. In the second case, LoadedClass is on the parent's classpath, so it loads the class instead of giving up and letting the URLClassLoader try.
The application classloader then attempts to load the LoadedClassDep because it is imported and referenced directly in LoadedClass:
public void call() {
new LoadedClassDep();
}
If you need to load these classes dynamically and independently at runtime, you can't have direct references between them in this way.
It is also possible to change the order in which classloaders are tried - see Java classloaders: why search the parent classloader first? for some discussion of this.

Categories

Resources