I have a code generator that uses URLClassLoader to load classes on a specified path, scan them for annotations, and then using Reflection on the fields/methods, generate DTOs.
It works great, in the test app.
When I put it into the Maven MOJO, I suddenly lose the ability to see the javax.persistence.Entity annotations on the classes. It loads them, it can see all the fields, but the Entity annotation is no longer visible.
I am assuming this is something to do with Classpath issues - is it? Neither the test app (a main() function in the plugin itself) or the MOJO are part of the project that the scanned classes are from. But one works and the other doesn't.
I have a little bit of debug code that prints out all of the annotations on the class when it examines them, and in the non-running version it finds literally none.
Any ideas how I debug the problem/solve it?
The problem turned out to be pretty simple, although I'm not sure why it worked fine in one case and not in another.
My URLClassLoader creation didn't specify a parent classloader. So, I assume it couldn't find anything. As soon as I used
loader = new URLClassLoader(classUrls, Thread.currentThread().getContextClassLoader());
for the classloader, it all started working just fine. I'm pretty ignorant when it comes to the ins and outs of classloaders, so this wasn't obvious. Especially since the example I was following didn't specify a parent either.
When you scan loaded class for annotations, you can't see the annotations which can't be found in classpath. That is, to read JPA's Entity annotation your code generator should have a JPA API in classpath (javax.persistence:persistence-api:1.0 in Maven).
However, if you use classloader to load external classes and scan them for annotations, you may face other problems with missed dependencies and execution of static initializers. May be, the better approach is to use bytecode manipulation libraries, such as ASM, to scan classes without loading them.
Annotations have a persistence level associated with them. Some don't survive compile time (i.e. they are not put into the .class files.) Check to see that the ones you are referencing are not of this type.
I am assuming this is something to do with Classpath issues
My bet is that the persistence-api artifact is declared with a provided scope and isn't listed in the class path passed to the plugin. Can you confirm this?
Related
There is a way to use annotations to build a java class, based on the properties of multiple java classes?
I want to create a generic log history table for all operations and entities in a spring data jpa project, for this i was thinking if would be possible to get all properties of my entities at compilation time to generate this generic entity log class.
I don't know so much about annotations, but it is used to generate source files so i believe that isn't a impossible ideia.
Could someone give some direction? If it's possible would be nice to point me a good starting point. Or if there is something already done that match my intent.
Annotations themselves do not generate source files -- they signify pointcuts for other classes to enhance/enrich them, or as a marker interface.
However, you can definately use an annotation scanner to scan files and get all the fields.
Then what is left is generating a class from this.
(and then, compile it). Be aweare that this is a multi-step process, and it may seem a bit clunky: you create a file with name GenericEntity, make sure it's in the proper package (so start it with package my.fun.project, write the imports, and write the java class, all as strings which you send tot he file.
From you scan you have an annotated field / class and you can get the type and name (see the reflections library if necessary), and write that to your file as well. Then close the class properly with a }. Now it should a file which should not give compilation errors when loaded in your IDE.
This GenericEntityGenerator then has to be executed (using a maven plugin, probably) on your source code, probably during the generate-sources phase, after which your generated class will be compiled during the compile phase.... and bob's your uncle now.
In all, a fun project
I am building a tool from several different open source libraries. My buildpath is in the following order:
My first JAR file, stanford-corenlp-3.3.0.jar, contains a package called edu.stanford.nlp.process, which has the Morphology.class class.
My second JAR file, ark-tweet-nlp-0.3.2.jar, contains an identical package name (edu.stanford.nlp.process), and an identical class name Morphology.class.
In both JARS, inside their respective Morphology classes there exists a method called stem(). However, the constructors for these methods are different. I want to use the stem(String, String) method from my second JAR file, but since the import statement (import edu.stanford.nlp.process.Morphology;) does not specify which JAR to use, I get an error since it thinks the first JAR on the buildpath is the one I want to implement.
I don't want to change the order of my buildpath since it would throw off my other method calls.
How can I specify which JAR's Morphology class to use? Is there an import statement that specifies the JAR, along with the package.class?
EDIT: What about a way to combine my two JARs so that the two Morphology classes merge, giving me two methods with different constructors?
As several others pointed out above, it is possible to tweak Java's classloader mechanism to load classes from certain places… but this is not what you are looking for, believe me.
You hit a known problem. Instead of worrying how to tell Java to use a class from one JAR and not from the other, you should consider using a different version of ArkTweet.
Fetch the ArkTweet JAR from Maven Central. It does not contain Stanford classes.
When you notice that people package third-party classes in their JARs, I'd recommend pointing out to them that this is generally not a good idea and to encourage them to refrain from doing so. If a project provides a runnable fat-jar including all dependencies, that is fine. But, it should not be the only JAR they provide. A plain JAR or set of JARs without any third-party code should also be offered. In the rare cases that third-party code was modified and must be included, it should be done under the package namespace of the provider, not of the original third-party.
Finally, for real solutions to building modular Java applications and handling classloader isolation, check out one of the several OSGi implementations or project Jigsaw.
The default ClassLoader will only load one of the jars, ignoring the second one, so this can't be done out of the box. Maybe a custom ClassLoader can help.
For more info about ClassLoaders start from here.
Good luck!
EDIT: We are looking at some horrible packaging choices causing as side effect this Jar Hell here. The author of this "Ark Twitter" library decided it was a good idea to release a JAR artifact that includes a third party library (the Stanford NLP library). This leads to unnecessarily tight coupling between Ark Twitter and the specific version of the Stanford NLP library used by it. This is a very bad practice that should be discouraged in any case: this violates the whole idea about transitive dependencies.
EDIT (continued): One possible (and hopefully working) solution is to rebuild the Ark Twitter JAR so that it does not include the aforementioned library but only its own code (basically the cmu.arktweetnlp package only) and hoping that the version of NLP required by your project works with Ark Twitter. Ideally you should submit a pull request to the author of the library but in the meantime you can get away with un-jarring and re-jarring the existing JAR file.
EDIT 2: Looking at the JAR file again, it's much worse that I originally thought: ALL the dependencies are repackaged in the released JAR file. This is really the worst possible solution for releasing a library. Good luck.
I think your problem can be solved simply by using the lemma(String word, String tag) method in the current CoreNLP's Morphology class:
String word = ...;
String tag = ...;
String lemma = morphology.lemma(word, tag);
WordTag wt = new WordTag(lemma, tag);
When the class was revised a couple of years ago, the method you're looking for was deleted. The feeling was that with most of the Stanford NLP code moving to using CoreLabels, methods that return WordTag are less useful (though deleting all such methods is still a work in progress).
No there isn't. This is a weakness of Java, that cannot be simply solved. You should use only one of the libraries. Having both on the classpath will make java always select the first one.
This problem is named as Jar hell.
The order in the buildpath generally determines the order in which the classloader will search for the class. In general, though, you don't want duplicates of the same class in your build path--and it sure doesn't seem like ark-tweet-nlp-0.3.2.jar should have a edu.stanford package within it.
When you load a class, it's loaded at given address, and that address is then placed in the header of objects created from the class, so that (among other things) the methods in the class can be located.
So if you somehow load ClassA, with method abc(String), from zip file XYZ.zip, that loads into address 12345. Then (using a class loader trick) you load another ClassA, with method abc(String, String), from zip file ZYX.zip, and that loads into address 67890.
Now create an instance of the first ClassA. In its header will the class address 12345. If you could somehow attempt to invoke the method abc(String,String) on that class, that method would not be found in the class at 12345. (In actuality, you will not even be able to attempt the call, since the verifier will stop you because, to it, the two classes are entirely different and you're trying to use one where the other is called for, just as if their names were entirely different.)
I'm seeing an interesting behavior in some of the code I inherited where there's a project with a dependency jar in which there exists a class with the same name and in the same package as a class in the project:
Eclipse project:
src/com.abc.d.E
depends on XYZ.jar in which there exists a com.abc.d.E.class
Just curious if this setup is legal in Java. According to Eclipse, which doesn't mark it as an error and allows to create a new class that shadows an existing class in a dependency jar, except when one tries to rename such an existing class in the workspace - then it produces the following warning:
"Binary references to a refactored element have been found. They will
not be updated, which may lead to problems if you proceed."
And then this forward refactoring is allowed. However, if one would like to refactor backwards to a conflicting name, the following message appears in Eclipse:
"Type named 'E' already exists in package 'com.abc.d'
So is it
a correct Java
an allowed inconsistency in Eclipse's behavior or
.an Eclipse bug?
Thank you.
If both classes where accessible to the same classloader (for example if both jar files find themselves on the same classpath), then only one of them would be loaded (probably the one that's first in the list), which would lead to all kinds of nasty results, esepcially if they don't behave the same.
Assume library 1 uses E.frobnicate() while library 2 expects there to be a E.frob() method: one of those will get a NoSuchMethodError and generally just fail spectacularly.
Generally speaking, the package name and class name should uniquely identify a class. And if that's no longer the case then you'll get into trouble.
You can work around the issue as long as you never need those two libraries to be accessible from the same classloader, then the JVM can handle it just fine (because FQCN plus the classloader is used internally by the JVM to uniquely identify a class during runtime).
The only case where this is intentionally done is if some library re-implements classes of another one in a binary-compatible manner. See log4j-over-slf4j for an example.
There's nothing that states you cannot have two classes with the same package/class name.
This happens all the time when you accidentally have two versions of a library on your classpath, leading to things like IncompatibleClassChangeError or getting the wrong version because you're at the mercy of class load order.
Eclipse, OTOH, has to do something with those classes other than just loading the first one encountered on the classpath. You can't rename to something that already exists, because that wouldn't make any sense.
Renaming something out from under something that already exists is likely an error, but can't be guaranteed to be an error, because you might be fixing a naming problem, rather than just creating a new/different one.
The situation at hand is not as simple as the title seems to indicate.
Java 1.6_17 running via JWS.
I have a class, lets say MyClass and one of its instance member variables is a Type from an errant 3rd party library where during class initialization it dynamically tries loading some of its own classes with Class.forName(String). In one of these cases it happens to dynamically call: Class.forName("foo/Bar").This class name doesn't follow the JLS for binary names and ultimately leads to a java.lang.NoClassDefFoundError: foo/Bar.
We have a custom ClassLoader which I've added a sanitize method to ClassLoader.findClass(String) and ClassLoader.loadClass(String) which fixes this problem.
I can call stuff like:
myCustomClassLoader.findClass("foo/Bar")
Which then loads the class without any problems. But even if I load the class ahead of time, I still get the exception later. This is because during initialization of MyClass which refers to Bar - their code ends up calling Class.forName("foo/Bar") in a static block somewhere. This actually would be OK if the ClassLoader it was trying to use was my custom class loader. But it isn't. It is the com.sun.jnlp.JNLPClassLoader which doesn't do such sanitation, thus my problem.
I've made sure that Thread.currentThread().getContextClassLoader() is set to my custom class loader. But this (as you know) has no effect. I even set it as the first thing i do in main() due to some stuff I read and still, MyClass.class.getClassLoader() - is the JNLPClassLoader. If I could force it to NOT be the JNLPClassLoader and to use mine instead, problem solved.
How can I control which ClassLoader is used to load the class via their static Class.forName("foo/Bar") call made during class initialization? I believe if I can force MyClass.class.getClassLoader() to return my custom class loader, my problem will be resolved.
I'm open to other options if anyone has ideas.
TL;DR: Help me force all Class.forName(String) calls in a third party library which are referenced by MyClass - to use the classloader of my choosing.
This reminds me of an article I read 10 years ago about the classloading arrangements in Java. It's still there on JavaWorld.
The article won't answer your question directly, but it may help understand your problem. You need to cause MyClass to be loaded through your custom class loader and trump the default class loading behavior, which is to first delegate class loading to the parent classloader and only attempt to load a class if that fails.
Allowing MyClass to get loaded by a classloader other than yours will store a relationship from the instantiated class to that classloader (via getClassLoader) and cause Java to use that other classloader to try to discover any referenced classes found at compile time, effectively bypassing your custom class loader by virtue of the class loader hierarchy and the delegation model. If MyClass is instead defined by your class loader, you get a second chance.
It sounds like a job for something like URLClassLoader, overriding loadClass and trumping the delegation model for classes residing in your JARs. You'll probably want to use a bootstrap approach (as suggested by Thomas in a comment above) to force a single entrypoint class to be loaded through your custom class loader, dragging all the others with it.
Also informative is this other JavaWorld article by the same guy, which warns you about the caveats of Class.forName. That too may trip your classloading arrangements.
I hope this helps and proves informative. In any case, it sounds like a difficult solution that is easy to break as your code evolves.
I think everyone gave good solid attempts at answering the problem. However, it turns out that I misdiagnosed the problem.
I had a coworker take over the problem and asked him to get a JDK with debug flags on so we could debug the JNLPClassLoader to see what was going on as I had tried all of the suggestions here + some.
We ended up getting OpenJDK because recompiling the JDK from scratch is a total nightmare (we tried). After getting OpenJDK working with our product and debugging through the JNLPClassLoader - it turns out that it was still using a REALLY old .jnlp from months earlier that had the resource path wrong and thus why it couldn't find the class.
We were confused why it was still using the ancient .jnlp even though we had redeployed the server correctly many times with the correct .jnlp and lots of code changes between which were reflected in our client application when run.
Well, it turns out that on client machines, Java caches the .jnlp file. Even if your application changes and it redownloads your application, it still won't re-download the new .jnlp for whatever reason. So it will use all of the new code, but look up resources/class paths using the cached .jnlp.
If you run:
javaws -uninstall
On the client machine then that will clear the .jnlp cache and next time it will use the correct .jnlp file.
Really sad that this was the problem. Hopefully, this saves someone else endless hours of frustration like it caused us.
If you run out of ideas with patching the ClassLoaders themselves, you might consider rewriting the library bytecode itself -- just replace the "foo/bar" constant with the correct value, and then you don't need to customize further class loading at all!
You could do this either at runtime or beforehand.
How can I export a jar without some class or package which I don't want others to use,but which is needed in project,means I can't delete them.
This doesn't make a lot of sense.
If the classes are needed to compile the project, then there are likely to be static dependencies on them in other classes. (Otherwise ... you would be able to just delete it / them.)
But if there are static dependencies on those classes, then you won't be able to run the applications unless those classes are present in the JAR file. If you leave the classes out of the JAR to stop people using them (or whatever), your application will die on startup with "class not found" exceptions.
If you want to stop people using the classes directly, you could try the following:
Change the classes access to "package private". This doesn't make it impossible to use them, but it makes it more difficult.
Change your project so that the dependencies on the classes are entirely dynamic; e.g. via Class.forName(...) or dependency injection. Then you can exclude the classes from the JAR as required. But once again, if your application needs to use the classes, they have to be accessible at runtime (somehow), and it will therefore be possible (somehow) for other people to get hold of them.
Change your project to allow you remove the classes entirely. This is the only sure-fire solution.
Finally, before you go to all of this trouble you should ask yourself why you are even bothering to do this. Why don't you just let people use the classes anyway? What is to stop them getting the classes from somewhere else ... or implementing their own versions from scratch?
If the class is needed for your program to work, you can't omit it from the JAR.
You can put classes in a package that has something like "internal" in its name — e.g. com.example.internal — to indicate that the classes aren't meant for others to use or rely on. It can't prevent anyone from using your class for their own purposes, but it at least indicates that they do so at their own risk, that your internal interfaces might change in future versions of the program, etc.
A simple way is to use the package private access modifier for classes. That will make it difficult for others to access the classes, but it will also affect your ability to use them from other packages.
A more advanced way would be to use OSGi and only publish those classes you want others to use. That will make it impossible for others to access the classes while not restricting your access to them.
Put them into a separate jar file, include that jar file (as a single file, maybe with a nondescript name) into your jar file, create a custom class loader to open that embedded jar file from your code, bootstrap an entry point with reflection (because you cannot have static dependencies on it) into that new class loader .
Or just accept the fact that since the code is in there somewhere, any amount of obfuscation is just an inconvenience to everyone and cannot really hide anything.