So, I am trying to do something ugly here, let's say it's a desperate measure as I cannot have control over the runtime environment.
So having that said, I run some code in an environment where I cannot have control over the classpath (bad thing)... What's still worse is that the classpath has two jars, let's say productV1.jar and productV2.jar Both are exactly the same, but in different versions, so they have same classes.
For some reason, in most environments the productV2.jar is loaded and the productV1.jar is left out, but in some other environment, productV1.jar is called, and it causes the program to crash.
What I want to do as a workaround is mess with the classloader to explicitly ignore "productV1.jar". Ideally by overwriting some classloader funtion. I have done this with other resources (like the persistence.xml file from Hibernate), but I don't want to filter based on containing classes, but rather on the jar file. Is there a way?
This is only meant to work until I can do the negotiations to get rid of the offending jar...
Edit: I will leave this question open in case there is some interesting hack. However, the issue is that everything that is in the classpth is loaded by the system classloader, and trying to switch it at runtime is not an easy task (or maybe not possible at all?). Only way I see is starting the process with a custom classloader, which is not what I am looking for.
Did you try to use URLClassLoader and manually add jars to the classpath? In constructor of URLClassLoader you pass an array of URLs which point to jars that you want to load.
You can make the following experiment: create an URLClassLoader subclass that includes only the jars you want (i.e. call super constructor with appropriate array of URLs) and then call your java with the following property:
-Djava.system.class.loader=test.CustomJarsClassloader
To set your classloader as the default one.
The classloader may look like:
public class CustomJarsClassLoader extends URLClassLoader{
public CustomJarsClassLoader(){
super(new URL[]{ /*List of URLs to jars... */});
}
}
UPDATE:
Ok, if you can't add this argument to command line then try another approach:
In your main() function create a new Thread
Set the classloader that I mentioned above as this thread's context classloader (see: javadocs)
Run all your application's code inside this Thread. Your classloader should be used to load classes.
Related
When the lombok jar file is opened in Intellij, all files other than the annotations end with .SCL.lombok (e.g. HandleAccessors.SCL.lombok). I was just wondering what the reason for this was and how it's handled.
The reason for it
Lombok has a public API - the stuff you're supposed to interact with. That'd be, for example, the #lombok.Getter annotation. Those are just class files in that jar, the aim is simply: add that jar to your classpath and your IDE autocomplete dialogs and the like will automatically start suggesting these, as per design.
But, lombok also has lots of classes that just 'make it tick', these aren't meant for public consumption. Things like lombok.eclipse.HandleGetter, which is the implementation for handling the #Getter annotation inside the eclipse agent. There is no point or purpose to referring to this class anywhere, in any project - it's an internal lombok thing. If we just stuck that jar file into the jar, and you typed Handle and hit your IDE's autocomplete shortcut key, you'd still get the suggestion.
Similarly, we ship a few dependencies straight into lombok.jar - it's a 'shaded jar' (a jar with all deps included), though we don't have many, keeping lombok.jar a nice small size. Still, ASM (a bytecode manipulation library) is in it, and that is fairly popular.
The standard shading solution offered by most shading tools is to prefix something to the name. ASM's org.objectweb.asm.AnnotationVisitor class would become org.projectlombok.shading.org.objectweb.asm.AnnotationVisitor. Point is, your IDE doesn't know that, and if you ALSO use asm in your project (where you also use lombok), and you want AnnotationVisitor thus you type AnnV and hit cmd+space or whatnot, your IDE suggests both. That's ugly and we'd like to avoid this.
Hence, we built our own shader, and it works by not having class files in the first place. This way, IDEs and any other automated tool doesn't even know either our ASM classes, or our implementation details, even exists. The only files that such tools (such as your IDE) sees are the types you're meant to see: lombok.Builder, lombok.extern.slf4j.Slf4j, lombok.experimental.UtilityClass, etcetera.
How does it work
Java's classloader architecture is abstracted: You can make your own. The primitives offered by a class loader is simply this: "Convert this byte array containing bytecode (i.e. the contents of a class file) into a Class<?> definition", and the primitives that you're supposed to implement when you write your own classloader is twofold:
Here is a resource key, such as "/com/foo/load.png". Please provide me an InputStream with this data.
Here is a fully qualified class name, such as "com.foo.MyApp". Please provide me with a Class<?> instance representing it.
Out of the box, java ships with a default classloader. This default classloader answers these questions by checking your CLASSPATH - which can be provided in various ways (via the jar manifest's Class-Path entry, or via the -cp argument to the JVM executable, or the CLASSPATH environment variable), and scanning each entry on the classpath for the resource requested, capable of reading the file system as well as opening jar files.
But that's just a classloader. One implementation of the general principle that's baked into java. You can write your own. You can write a classloader that generates resources on the fly, or that loads them from a network.
Or, as lombok does, that loads them by opening its own jar and looking for .SCL.lombok files.
Thus, lombok works like this: When you launch it, the 'entrypoint' (the class containing public static void main - or in lombok's case, for javac mode it's the annotation processor entrypoint and for eclipse it's agentmain), we 'hide' it from you using some fancy trickery: agentmain does not need to be in a public class (it can't be .SCL.lombok files - our classloader isn't available yet, we need to bootstrap that up first!). annotation processors do have to be in a public class, but, it's a public class inside a package private class, thus, just about every IDE knows it's 'invisible' and won't show it, but javac's annotation runner accepts it.
From there, we register a classloader that is capable of loading classes by way of reading in an .SCL.lombok file, and this lets us hide everything else we want to hide.
I want to develop lombok and this is getting in the way!
No need; just clone our repo, run ant eclipse or ant intellij, and off you go. There is no way to extend lombok without first forking it; we'd like lombok to be able to be extensible without it, but that would be far more complicated than simply not doing the .SCL.lombok thing. Eclipse runs on top of equinox, a runtime modularization system, and making that work properly requires all sorts of stuff that would make 'just toss some extra handlers on the classpath' not a feasible route to extending lombok in the first place.
I am working with Swing in Net Beans. I have my own jar which contains classes and methods inside it. I will call those classes and methods using JAVA Reflection API but before that I want to load my Jar into class path at run time. I have a J Button and on click of that I am getting Jar Name and Jar path. But I am failing to load Jar to classpath at run time. Got some links but were not helpful. Please provide me with simple example. I should load my jar to classpath. That's the only problem for me.I will take care of that. Please help.
You can load classes at run time through the use of a ClassLoader, take a look at URLClassLoader for example
File yourJarFile = ...;
URLClassLoader classLoader = new URLClassLoader(new URL[]{yourJarFile.toURI().toURL()});
This will then allow you to load classes and instantiate them...
Class class = classLoader.loadClass("fully.qualified.packagename.to.your.AwesomeClass");
You can then instantiate them using something like...
Object obj = class.newInstance();
Or reflection if you want to use a specific constructor. Just remember, you won't be able to reference these classes directly within the current class loader context, as the current class loader knows nothing about them
Consider a URLClassLoader parameterized with a collection of URLs which is a mix of expanded directories and jar files. For example:
URL[] urls = new URL[] {
new URL("file:/D:/work/temp/jars/spring-security-core-3.2.0.RELEASE.jar"),
new URL("file:/D:/work/temp/jars/spring-security-config-3.2.0.RELEASE.jar"),
...
new URL("file:/D:/work/temp/domain/bin/"),
new URL("file:/D:/work/temp/web/bin/"),
...
}
URLClassLoader cl = new URLClassLoader(urls);
The classloader correctly handles getResources() requests for resources located somewhere inside a package like "org/my/package/conf.properties". By correctly handles I mean the classloader successfully finds all matches inside both directories and jars.
A special empty string name passed in getResources("") is supposed to yield the URLs for all available roots (in both the directories and the jars). However there is a known limitation in ClassLoaders which results in only returning roots that correspond to directories. All roots to jars are discarded.
Using classloader.getURLs[] instead of classloader.getResources("") will not work with me as I have a complex graph of interdependent URLClassLoaders, so the results are going to be completely different. Also my classloaders are to be consumed by a third party classpath scanning facilities that uses getResources("") calls in order to set up an internal search base. This way resources located in jars are simply not found.
I currently have a working fix where I extend from URLClassLoader and manually handle requests with an empty string by forcing roots for jars in addition to those for directories within the returned collection of URLs.
However my questions are:
What was the conceptual/technical reason for this limitation (where paths to jars are not returned)?
By fixing this manually, do I violate any important contract?
Is there any nice way to get the desired behavior?
Thanks for any thoughts on that!
What was the conceptual/technical reason for this limitation (where paths to jars are not returned)?
The behavior of ClassLoader.getResources("") is unspecified.
The implementation for loading resources from the file system in URLClassPath$Loader is solely based on URLs. It constructs a new file URL by adding the resource name to the base URL of the directory
and returns the URL when it points to an existing resource.
There's no special handling for an empty resource name.
Whether this is wanted behavior or not is undocumented.
The implementation for JAR files in URLClassPath$JarLoader works on an index over JAR files. To get the same behaviour for JAR files the implementation would require a special handling for empty resource names, i.e. it would need to check for an empty resource name first and return the file URL of the JAR file instead of searching within the index. The implementation does not have a special handling for empy resource names. Whether this is wanted behavior or not is again undocumented.
Since the API specification does not specify the behavior for
empty resource names both implementations are valid.
Some may argue that exposing roots is a security issues, especially when running in a sandbox. Others may argue that getResources() should return null for empty resources since there actually does not exist a resource with the name "".
In any case current behavior of URLClassLoader leads to unexpected behavior in Class.getResource(). When this method is called with an empty string for a class in the default package it returns the root directory of the class when the class was loaded from the file system. This violates the contract of the method. For details see for example this open Java bug: https://bugs.openjdk.java.net/browse/JDK-8202687.
By fixing this manually, do I violate any important contract?
As long as you only override the findResource() method
of your ClassLoader, call the super method and then add the additional
URLs of your JAR files you shouldn't violate any contract.
But be aware that there are already implementations out there, that have a special handling for URLClassLoaders. For example
Spring's PathMatchingResourcePatternResolver has a special handling (here) for class loaders that are instances of URLClassLoader, which adds additional URLs for JARs.
Is there any nice way to get the desired behavior?
There is no nice way to get the desired behavior since every solution would be based on unspecified behavior that may theoretically change with every new JRE version.
With the introduction of multi-release JAR files in Java 9 the behavior already changed:
For a multi-release JAR file with Java 8 classes and Java 9 classes
ClassLoader.getResource("") returns now an URL for the JAR file when it is executed within a JRE version > 8. With JRE 8 it still returns no URL for the same JAR file. With it, the returned URLs for an empty resource string depend now even on the JRE version, resp. the type of JAR file.
There exist workarounds to get also the URLs for JAR files. PathMachintResourcePatternResolver for example loads JAR file names from the java.class.path system property (in case of the system class loader) and loads additional URLs by calling URLClassLoader.getURLs() (in case of a URLClassLoader). But again, these are only workarounds based on unspecified behavior.
Ideally searches on the classpath are only performed in the context of a java package. Frameworks like Spring (boot) perform searches on the classpath only in the context of a java package. This avoids to rely on unspecified behavior of class loaders and also avoids to search in JAR files of irrelevant third-party libraries. So, whenever possible I recommend to search on the classpath in context of a java package instead of searching resources by using an empty resource name.
Can I use ClassLoader's definePackage to override some packages from inside a jar?
For example, the application currently contains "javax.xml.bind" from abc.jar. If I call ClassLoader.definePackage(def.jar), in which the def.jar contains another version of javax.xml.bind, can I replace the classpath for the entire application to point to that of def.jar? Thanks.
No, you definitely can not use ClassLoader.definePackage to "override" some packages from inside a jar.
If I understand correctly, you want to make your JVM load any class under javax.xml.bind from def.jar while all other ones from abc.jar. In this case you can (in my personal order of preference):
1) Put def.jar before abc.jar in the CLASSPATH. This requires that no class you want loaded from abc.jar is present in def.jar.
2) Unzip def.jar, abc.jar, or both, and remove any conflicting classes so it is really irrelevant which jar comes first in the CLASSPATH. Then re-zip them. Or you can do this only on one jar and put it before the other.
3) Use a configurable classloader (sorry, no public domain one that I know of; let me know if you find one). This could be an interesting topic for an OS project, except that several initiatives with similar (but much broader) objectives are already ongoing, some at the core of the language.
4) Create a classloader for this purpose, probably extending the default one.
I am using Eclipse and I have got two libraries included in my project, foo.jar and bar.jar. In both JARs there is a class FooBar.java that includes the method getFoobar(Object xy).
Now I would like to load the method getFoobar(Object xy) either from foo.jar or from bar.jar on the basis of a properties file:
config.properties:
choice=foo
If choice==foo then the method of Foo.jar shall be picked, elsewise the method getFoobar(Object xy) from Bar.java. To make things more complicated the method getFoobar(Object xy) has in its method declaration objects loaded from another JAR which is included in foo.jar (and bar.jar respectively).
So my question is. How can I get the methods of the JARs respectively by Reflections? I have not yet found a solution. Is there a way to solve this issue?
Thank you.
Emrah
You can only load one or the other methods without getting complicated with class loaders. A simple workaround is to rename the package of one or both libraries with jarjar This allows you to have everything in foo.jar start with foo. and everything is bar.jar start with bar. and this avoids any confusion. I have seen this used to be able to load several version of Xerces based on configuration (and use them concurrently in the same app)
Write your ClassLoader that reads the property. Alternatively, you can use -D option in the jvm arguments
-Dchoice=foo
and in the code that loads the class from foo.jar or from bar.jar you can switch on this property value:
String choice = System.getProperty("choice");
if (Main.CHOICE_FOO.equalsIgnoreCase(choice)) {...}
...
OSGI can give you a similar capability.
How can I get the methods of the JARs respectively by Reflections?
You can't.
Reflection does not allow you to select between the two versions of the class. In fact, as far as the JVM is concerned, there is only one version; i.e. the one that appears earliest on the classpath.
You can solve this problem by defining multiple classloaders, each with a different classpath. However, there are complications:
If you manage to load both versions of the class into a running application, you will find that instances of the respective classes have different types, and you won't be able to convince the JVM otherwise.
When your application loads another class that statically depends on one of these classes, it will bind to the version of the class that is on the classpath of the dependent classes classloader. And you can't change that. So, uses of the class name in declarations / typecasts / etc in the dependent class will refer to the version of the class found by the dependent classes classloader, not the other one.
The upshot is that you can't use these same-named classes like regular classes ... especially if both versions need to be loaded in the same JVM. It is a better idea to give the two versions of the class different names. You don't gain anything by making the class names the same.