I am developing a software with an automated update function, which works even when the user has no write permission to the installation folder.
For that, the application analyses the classpath and checks whether there are newer JAR files for the entries in the classpath in a separate user directory. In that case the application generates a revised classpath and starts a new VM with that classpath.
My problem arises when the java default class loader changes the order of the JAR files by inserting libraries, which are mentioned in a manifest file.
I will give you a simplified example, which lacks a lot of details and does not make sense for a real update scenario, but it will explain my problem. So let's assume I am not allowed to change the contents of main.jar.
I start the application with
java -jar main.jar
main.jar has a proper MANIFEST file, which contains the Main-Class and a Class-Path, referencing the additional libraries c.jar and d.jar.
So the complete Class-Path during runtime would be:
main.jar, c.jar, d.jar
The order is important, since main.jar overrides classes from c.jar.
Now the application finds out that there are updates available, which require the additional libraries a.jar and b.jar.
So the application now calculates the new classpath:
main.jar, a.jar, b.jar, c.jar, d.jar
Again, the order is important, since a.jar overrides classes from d.jar.
Now the application starts a new VM with an explicit classpath:
java -cp main.jar:a.jar:b.jar:c.jar:d.jar <Main-Class>
Unfortunately, java alters the class-path by inserting the libraries, referenced by the MANIFEST of main.jar, at the position of main.jar. So the effective classpath would be:
main.jar, c.jar, d.jar, a.jar, b.jar, c.jar, d.jar
-------- ------------ --------------------------
CP MANIFEST CP
With that order, c.jar now has a higher priority than a.jar. And the overriding classes from a.jar will not get loaded.
The obvious solution to this problem would be to provide a main.jar without a manifest.
But this would lead to other problems, I would like to avoid.
My question is: Is there a way to disable the behaviour of java to include the referenced JAR files from a MANIFEST while searching the classpath?
Just a quick and untested idea. If you are able to dynamically load your updated jars at runtime, instead of specifying them at the command line, you could create a URLClassLoader for these jars and explicitly load your classes using that custom class loader:
URL[] jarurls = new URL[]{jar1,jar2,...};
URLClassLoader customCL= new URLClassLoader (jarurls, getClass().getClassLoader());
Class clazz = Class.forName ("com.blablabla.MyClass1", true, customCL);
MyClass1 instance = (MyClass1)clazz.newInstance();
You could load every jar which resides in your user folder into that class loader. When instantiating a class you would prioritize your custom class loader and failover on the default one.
This certainly adds a complexities/risks to your design since classes would now need to be instantiated by reflection.
Related
can someone tell me please what are the difference between Rsrc-class-Path and Class-Path sections of a runnable-jar's mannifest file?
Now I take them as granted as generated by Eclipse, but I'd like to understand how it works.
What I think based on how Eclipse generates code seems that the first is about jars my app needs, the second is always .. But I have no clues what folder . refers to.
The Class-Path attribute. This is a standard attribute defined by the JAR file specification. It contains a list of relative URLs for that will be included on the runtime classpath when you run the JAR using java -jar ....
This provides a way to add external JARs (and directories) to the runtime classpath. The entries must be relative, and are resolved relative to the directory containing the main JAR. (For security reasons ...)
The Rsrc-class-Path attribute is non-standard. This is used by Eclipse's "jars-in-jar" launcher. A typical manifest looks like this:
Manifest-Version: 1.0
Rsrc-Main-Class: com.abc.Master
Main-Class: com.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader
Rsrc-Class-Path: ./ lib/xyz.jar
where com.abc.Master is your apps (real) main class, and lib/xyz.jar is a relative URL for a JAR file that is nested within this JAR.. You will also see that the JAR contains the ".class" file for JarRsrcLoader.
This is what happens with you run java -jar this.JAR arg1 arg2.
The JVM is created
The JVM jar loader opens the JAR, reads and parses the MANIFEST.MF above.
It loads the JarRsrcLoader class given by Main-Class/
It calls the above classes main method, passing it ["arg1", "arg2"]
The JarRsrcLoader examines the manifest, and extracts the Rsrc-Class-Path and Rsrc-Main-Class.
Then JarRsrcLoader creates a special classloader that knows how read JARs embedded within the current JAR. The classpath for this classloader is "./" follows by "lib/xyz.jar", where these URLs are resolved within the outer JAR file.
Then JarRsrcLoader loads the class com.abc.Master using the special class loader.
Then JarRsrcLoader calls the main method for com.abc.Master, passing the same string array containing the arguments.
Finally, the application runs.
In short, Rsrc-Class-Path is an attribute that the JarRsrcLoader class understands, and uses to construct the actual application classpath.
In this context, the Class-Path: . attribute serves no real purpose. Everything needed to run JarRsrcLoader will be in the JAR.
As a final note, the SpringBoot loading mechanism is similar, but it uses a different non-standard attribute for the application's main class, and puts the application's resources (e.g. JARs) into a particular directory ("/boot-inf") within the main JAR.
I have recently come across of a scenario where we would have a empty manifest jar (i.e. jar with just META-INF/MANIFEST.MF having Class-Path attribute) referring to other jar file somewhere on file system. I believe this manifest jar has been created to act like a soft link to a specific version jar so that consumer need not deal with minor version changes of implementing jar.
manifest.jar:-
----META-INF/
--------MANIFEST.MF
Actual version specific implementation jar (test-1.1.jar):-
----META-INF/
--------MANIFEST.MF
----test/
--------Test1.class
Including manifest.jar in classpath in application does not load actual implementing jar and hence I see ClassNotFoundException.
Any idea why it does not load and how can I get this jar loaded as expected?
Update:
I tried this with a sample executable jar and it (sun.misc.Launcher$AppClassLoader) seems to load the class as expected. Referring just manifest.jar in Class-Path in-turn includes the actual implementing jar.
Also, I found that we do have a legacy custom class loader for the project, not sure if that causing this weird behavior.
By default, shouldn't it take care of the referencing of other jars from manifest.jar?
Is there a way to check whether this custom class loader is actually referencing jars mentioned in Class-Path?
When using java -jar somejar.jar, then the classpath is set up as follows:
Environment variable CLASSPATH is entirely ignored.
The -cp / -classpath switch is entirely ignored.
The Class-Path entry in the manifest of the jar you specified explicitly, i.e. somejar.jar is read, split on spaces, any relative paths are resolved relative to the directory the jar is in, and that is used as classpath.
The Class-Path entry in any other jar file, including jar files listed in the Class-Path entry of somejar.jar is entirely ignored.
The Main-Class entry in the manifest of somejar.jar is read, the string found there is loaded as class, and its main method is invoked. Any Main-Class entries in any jars you mentioned in the Class-Path entry is entirely ignored.
In other words, this proxy concept cannot really work. The jar you put in java -jar thejar.jar must have the Main-Class attribute and must list all the required jars to run that application in its Class-Path attribute.
If you use this instead: java -cp somejar.jar:lib/dep1.jar:lib/dep2.jar:etc com.foo.Main, then the manifest is entirely ignored, and thus whatever you set up in any manifest's Class-Path or Main-Class attribute is irrelevant here.
I think what you are observing may be explained by the JAR file spec section on the Class-Path attribute.
Specifically:
"Currently, the URLs must be relative to the code base of the JAR file for security reasons. Thus, remote optional packages will originate from the same code base as the application."
"Each relative URL is resolved against the code base that the containing application or library was loaded from. If the resulting URL is invalid or refers to a resource that cannot be found then it is ignored."
So, if the Class-Path attribute in the MANIFEST.MF file for manifest.jar uses absolute URLs, or URLs starting with "/", or if they do not resolve relative to the location of manifest.jar itself, the corresponding JARs, etc won't be added to the internal classpath. They are silently ignored.
This would happen if you just copied the manifest.jar into your project ...
What happens, if there is a manifest Class-Path entry for a jar which doesn't exists at the location, but is available by another means (in the lib-folder of application server for example)
Will the unresolved Class-Path entry cause any errors?
JVM loads & searches classes in following order:
Bootstrap classes - Classes that comprise the Java platform, including the classes in rt.jar and several other important jar files.
Extension classes - Classes that use the Java Extension mechanism. These are bundled as .jar files located in the extensions directory.usually $JAVA_HOME/lib/ext directory.
User classes - Classes defined by developers. Location of these classes using the -classpath option on the command line or by using the CLASSPATH environment variable.
If the JAR-class-path points to a JAR file that was already included (for example, an extension, or a JAR file that was listed earlier in the class path) then that JAR file will not be searched again. (This optimization improves efficiency and prevents circular searches.) Such a JAR file is searched at the point that it appears, earlier in the class path.
To verify this, I also did following test
1. Created lib(jar) "classpath-test" containing a Util class.
2. Created another lib(jar) i.e wrapper-lib which uses classpath-test's Util class.
3. In wrapper-lib's MANIFEST.MF, added below entry.
Class-Path: lib/classpath-test.jar
Copied classpath-test.jar under lib dir and ran below command
java -jar wrapper-lib.jar
Above command Ran fine. Ran same command after deleting lib/classpath-test.jar, and it failed.
Another test, deleted classpath-test.jar from lib & copied in JAVA_HOME/lib/ext and ran
java -jar wrapper-lib.jar
It worked.
I am relatively new to Java but have a fair understanding about how the class path works with respect to providing a list of folder and jars that make classes available to other classes.
I have compiled a JAR (lets say example.jar) that has a main function where execution normally begins. Sometimes I want execution to begin in a different class (lets say myAlternateClass.java), with its own main. I can achieve this by doing using the -cp argument when executing the jar, for example;
java -cp example.jar myAlternateClass
This works as I require but I am unsure of what exactly is happening here.
I'm not 100% sure on exactly what you're looking for, but I'll give it a shot.
There are two ways to use a jar file. If the jar file has a Main-Class specified in its META-INF/MANIFEST.MF file, then you can load java with the jar file and execution will start in the main method of that class.
java -jar example.jar
On the other hand, a jar file can simply be loaded onto the classpath, which makes all of the classes within it available for use. This is the example you are giving:
java -cp example.jar org.somewhere.MySecondClass
The -cp example.jar puts all of the classes within the jar on the class path and the second argument org.somewhere.MySecondClass gives the class at which execution should begin. This second argument would have to be within the jar since specifying a classpath overrides the default (which is just the current directory). In this case, java ignores any Main-Class specified in the MANIFEST.MF file of the jar (if one even is specified).
Multiple jar files as well as directories of java files not in a jar can be specified by putting colons between them. So,
java -jar example.jar:. MyClass
could launch MyClass from the current directory, but place example.jar on the classpath so that MyClass could create instances of whatever classes are available within example.jar.
I have some minor issue with class loading. I have a jar say abc.jar and under that jar there is another jar called libs/cde.jar. I had written a a class in that jar that used the cde.jar. I packaged the abc.jar correct and inspected that the dependent jar was correctly packaged under /libs/cde.jar.
However, when I try load the jar (abc.jar) and execute the class which depend on cde.jar, it cannot find that dependent cde.jar. Any pointer as to how I can do it? I cannot use other jar loaders like OneJar, etc. I have to do it with pure java class loading if possible. I tried putting it in the classpath by using "file:////C:/abc.jar!/libs/cde.jar" without any success
Thanks
Masti
Java can't, by default, deal with embedded Jars (ie Jars within Jars)
If you really want this type of behavior, you should take a look at OneJar
Otherwise you will need to leave dependent Jars externally and specify there dependency via the parent Jar's Class-Path attribute in its Manifest file