I have a a library that is bundled as an executable jar file and added to weblogic / tomcat classpath, how can I execute a main method from the jar file when the server is starting and loading the classes from the jar file.
what I want to is to have some initialization code to be executed first thing when the jar file is loaded and server is starting without any user intervention.
Note: I know I can bundle my jar in a war file, but I have some aspectj code in my library that I want to weave all running applications in the jvm, when I bundle my jar in war file, the aspectj code will only weave into the classes in the war file so I added my library jar file in the classpath.
Thanks in advance.
Add a class inside your JAR with the following code:
public class TomcatStartupListener implements org.apache.catalina.LifecycleListener {
public void lifecycleEvent(org.apache.catalina.LifecycleEvent event) {
if (event.getType().equals("after_start")) {
// call your main method here
}
}
}
Note: In order to compile this, you need to add <tomcat-dir>/lib/catalina.jar to your classpath. Otherwise when compiling it won't be able to find the necessary interfaces (org.apache.catalina.LifecycleListener and org.apache.catalina.LifecycleEvent). Once you're done with the compiling, put the JAR as usual under <tomcat-dir>/lib.
Now open <tomcat-dir>/conf/server.xml and add the following under the <Server> section:
<Listener className="com.yourpackage.TomcatStartupListener" />
Now whenever your Tomcat server starts, this TomcatStartupListener class inside your JAR will be called, and you can invoke your main method. There are a whole lot of other event types too! You can use any of these event types:
before_init
after_init
before_start
configure_start
start
after_start
before_stop
stop
configure_stop
after_stop
before_destroy
after_destroy
This approach is necessary because of the way the classloaders work in Tomcat (or even most JVMs). Here are the important points from that link:
There are three aspects of a class loader behavior
Lazy Loading
Class Caching
Separate Namespaces
The JVM will get very heavy if all classes inside all JARs get loaded indiscriminately. So the classes inside shared JARs are loaded only on-demand. The only way for you to invoke the main method is to add the above lifecycle listener.
Perhaps the simplest thing to do is to deploy a trivial servlet in a .war file that references your .jar file. The servlet can be configured to start up upon deployment/container start, and then it can invoke the class containing your main() method.
As application servers / servlet containers typically have a lot of different classloaders, you'll most likely need a different strategy for weaving aspects into your code than in standalone applications.
I would recommend to add the aspects to every war file deployed at build time. This might be following a common technique - as opposed to a server specific one.
Further, I'm not sure it can actually be done (properly & supported) on a server. Typically a server is built to separate all webapps from each other. You might get it to work, but it might break on the next update of the server.
It might be easier to suggest an alternative technique if you'd state the problem that you want to solve with your proposed approach.
Edit after your comment: Consider the standard web application lifecycle: You can execute some code, e.g. in a servlet, upon it being deployed. If you insist on your code being contained in main, you can call this method from your webapp's initialization code.
You need to register a Java Agent. See this link: java.lang.instrument.
java.lang.instrument provides services that allow Java programming language agents to instrument programs running on the JVM.
This is the right way to do this.
Related
I have Jar file which dependency on another project jar. Both are thin jars and are at same location. 1st jar has manifest file which list second jar in its class-path property.
In 1st jar I am launching second jar as a process using ProcesBuilder class in java. To do so I need absolute path of second jar. In 1st jar i have class XClient
If I do XClient.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
i am getting absolute path of 1st jar. Then I can split and add the name of second jar(hard-coded) to build the absolute path
In second jar I have class XServer
If I do
XServer .class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
Its throws exception
I am not sure if I am doing the right approach but my goal is very clear I wanted to get the absolute path to the dependent jar.
Please help
I tried to use the same approach (but used File file=new File(this.getClass().getProtectionDomain().getCodeSource().toUri()) instead of getPath()) but this can fail in different ways:
when the class is inside a jar the File object points to the jar instead the folder the jar is in - so an if(file.isFile()) file=file.getParentFile(); is needed to get the directory instead of the jar file
when the jar file is loaded by something other than the usual URLClassLoader (last time I tried was back in 1.8 - and I only know that since Jigsaw the main classloader can't be cast to an URLClassLoader anymore) this may will return some unspecified result, if at all, so actual behaviour depends on the very system setup - wich can make it difficult to debug when used on a remote system not under your control
UNC paths (Windows shares) are error prone by themselfs - adding another layer on top of it (java) just add a lot of potential other pitfalls you all have to test and debug - wich often ends up you tell the client what to use and how to setup instead of design your code to follow the java principle: "write once, compile once, run everywhere" (btw: this also applies even if you "mount" a network share so you can address it by a local drive letter instead of a remote network path - but this even causes problems when you try to link two machines where one is a clone of the other)
as already mentioned as comment: "it doesn'T work" is not a usefull or meaningfull description - if you get an error message (in this case as you mentioned an exception stacktrace) post it along with the code wich produced it (if accessible)
How I solved my problem? I just ask the user for the directory / file by a swing JFileChooser. Yes, this isn't fool proof and maybe not the best way - but it works as swing still ships with SE JVM (instead of FX).
If you want to find a path use Class.getResource() and let java do the work, pretty much like crypto: don'T do your own.
Aside from all that: Your mentioned "usecase" doesn'T require what you try to do. You said that the server is already in the classpath - so it gets loaded on startup and you can access the XServer class. The easiest way instead of forking another process is to just run it in another thread. If you know wich class has the main (the manifest of the server.jar will tell you) and you can access it in classpath just do something like this:
Thread serverThread=new Thread(new Runnable()
{
public void run()
{
String[] args=Arrays.asList("required", "parameters");
XServer.main(args);
}
});
serverThread.start();
If no paramters required you can just pass an empty String array. As main() should not throw Exceptions (at least no checked ones) no exception should be needed.
Before all those comments are thrown at me: Yes, I am very well aware of possible issues with such approach like classpath issues (same classname in same packagename but different versions) and such it may be more feasible than try to figure out the absolute path and launch a fork / sub process.
Also: Starting another process may require to interact with its streams (provide required input into child process inputstream and read the child process outputstream and errorstream - otherwise the forked process may can "hang" as it waits for the pipelines to get cleared. It's a pain in the but to debug that kind of issue if it's not your own code and you can have a profiler and debugger attached to it to figure out why all just suddenly stopped to work.
If you really want to (I don't think there's any requirement forcing a "you need to") launch your server along with the client do it with a launch script outside of java but with os level stuff.
I am working on a project using karaf 4.0.5, and osgi. We have client side code to invoke a REST API, which requires to load 3 "*.properties" files. I have been given a client jar that I'm using to invoke the server side classes and methods (containing code that I cannot update). The required property files are present in the provided client jar, but their code still doesn't locate and load them.
On debugging my pax exam I found the below possible reasons for it not loading resource files from the jar.
the code to load the files seems to attempt to load resources only from the Bundle Classloader, and
it calls the "getResource()" method instead of the "getResourceAsStream()" method.
Alternatively, I tried adding the resources to a directory on my file system, and appending the classpath with the directory's location, as in:
"-cp .;C:/Users/abcUser/Desktop/resourceFolder/;"
(windows 7, classpath entry added as a VM argument while executing pax exam using junit 4+ from eclipse) -> this doesn't work either and its still unable to locate these files.
What other options do I have so the Bundle Classloader locates these files?
Note: We already have a bunch of other *.cfg files whose contents are loaded into beans using blueprint and are registered in the containers, but that's not what I need to do here. During runtime, these files should be available to the BundleClassloader, and should be retrieved by the "getResource()" method.
Update: Following the below portion of the accepted answer, the properties files were successfully loaded by the application.
Another thing to check is whether the client code is actually using the bundle classloader when trying to load these resources. In some cases the code tries to be clever by using the Thread Context Classloader, which would need to be set appropriately before calling the client code.
The code from the client jar was exactly as guessed: the resource loading was happening using the Thread.currentThread().getContextClassLoader(). I was able to set the ContextLoader to the CustomAbstractProcessor's classloader and it now loads the properties files from that bundle's classpath!
ClassLoader previousCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(CustomAbstractProcessor.class.getClassLoader());
try {
//REST call to server using classes and methods from provided client jar.
}
finally {
Thread.currentThread().setContextClassLoader(previousCL);
}
I have been given a client jar that I'm using to invoke the server side classes and methods (containing code that I cannot update). The required property files are present in the provided client jar, but their code still doesn't locate and load them.
If this client jar is running as an OSGi bundle then it should be able to find resources using its own class loader (the bundle class loader) if (and only if) the resources are on the bundle's classpath.
The default classpath for an OSGi bundle is . i.e. the root of the bundle. This can be overridden using the Bundle-ClassPath manifest header and used to one or more locations in the bundle.
One possibility is that the client bundle has a different classpath set and the properties files are not on it.
Another possibility is that the properties files are on the classpath, but that the locations don't match what's expected, e.g. the code is looking for foo.properties and the file is at `/props/foo.properties'
it calls the getResource() method instead of the getResourceAsStream() method.
getResourceAsStream() is just a null-safe version of getResource().openStream().
What other options do I have so the Bundle Classloader locates these files?
Another thing to check is whether the client code is actually using the bundle classloader when trying to load these resources. In some cases the code tries to be clever by using the Thread Context Classloader, which would need to be set appropriately before calling the client code.
I have a JNI library (.so) shared between two web applications deployed in Tomcat7. I am loading the library using the System.loadLibrary only once in the first web application that is being deployed and then in the second I'm checking if it already was loaded to not load anymore (I tried loading it in both and I got UnsatisfiedLinkError - library was loaded by another classloader). I can make any call to the native library in the first application, but in the second one I get UnsatisfiedLinkError with the method name that I am trying to call.
I am running out of ideas of what I can do. Any ideas? I tried most of the solutions on SO.
Thank you.
EDIT
Yes, I tried adding the library in the tomcat lib folder and loading it from there. Initially it was in the bin folder and the same issue occurs.
Yes, this will happen when you try to load the library that has already loaded my another web application. Tomcat, uses separate class loaders for each of the web application, and it wont allow you load a same native library more than once to JVM via another class loader
Move any share jar files if any that consumes JNI from you sharedlib.so. Add the system path to sharedlib ,
export LD_LIBRARY_PATH=/path/to/whereyourlinklibrary
Write a simple class like this which enables you to load your shared library when tomcat starts. Just compile this class and drop it in tomcat lib folder
package msm;
public class DLLBootstrapper {
static {
System.loadLibrary("sharedlib");
}
public static void main(String args[]) {
System.out.println("Loaded");
}
}
you can now load this class from any of your web application ( probably in startup listener)
Class.forName("msm.DLLBootstrapper");
Good to go!
Have you tried putting shared JNI library just inside the lib directory of server.It should be shared by all the web applications deployed.
You need to load any native code from within the server classloader and not the webapp classloader. I recommend to write a Listener which loads the binary image of the shared object into the VM. This will happens only once. Please see the AprLifecycleListener on how to properly do that. It included a JNI compnent which likely represents you case exactly.
The shared object has to reside in ${catalina.home}/lib and LD_LIBRARY_PATH hat to point to it.
Tomcat has a built-in solution for this issue as of versions 9.0.13, 8.5.35, and 7.0.92:
1) Use the JniLifecycleListener to load the native library.
2) Use the loadLibrary() or load() from org.apache.tomcat.jni.Library instead of System.
See more details and examples in my answer at java.lang.UnsatisfiedLinkError: Native Library XXX.so already loaded in another classloader
We have a java based (jersey+grizzly) REST Server which exposes calls like
foo.com/{game}/rules
foo.com/{game}/players
foo.com/{game}/matches
There can be arbitrary number of games
Each game has different implementations for rules, players, matches
For historical reasons, we would want separate jars for each game implementation
So there is REST Server
as and when there is a call like foo.com/tennis/rules
we want the REST Server to dynamically load 'tennis' jar. The jar does its operation. Then jar should be unloaded
If the next call was for foo.com/football/players
we want the REST Server to dynamically load 'football' jar. The jar does its operation. Then jar should be unloaded
Is there a technique to do this ?
Apparently there is a very old question around [this]: java: is there a framework that allows dynamically loading and unloading of jars (but not osgi)?
I don't know how it works on Java 8, but unloading a class in Java 7 requires unloading not only the class, but its loader, along with all references from other objects that this class might have.
Once all of them were unloaded the System.gc will be called. If other classes are still holding references then the gc won't do its job.
OSGI (as suggested by #Joop Eggen) is a viable option. JRebel, is not.
proxy-object proxy-object library
Load java jar files dynamically in isolated class loader to avoid dependency conflicts and enable modular updates. All jar files in the [main jar folder]/lib/myLib/2.0/*.jar will be loaded.
Code Examples
Create Object from a JAR file located in the myLib/2.0 folder:
File libDir = new File("myLib/2.0");
ProxyCallerInterface caller = ObjectBuilder.builder()
.setClassName("net.proxy.lib.test.LibClass")
.setArtifact(DirArtifact.builder()
.withClazz(ObjectBuilderTest.class)
.withVersionInfo(newVersionInfo(libDir))
.build())
.build();
String version = caller.call("getLibVersion").asString();
I am using one third party jar in my code. In the jar file , in one of the classes, when I opened the class using de-compiler, the code below is written:
java.net.URL fileURL = ClassLoader.getSystemResource("SOAPConfig.xml");
Now I am using this in my webapplication, where should I place this SOAPConfig.xml so that it will find the fileURL.
Note: I have tried putting this XML in WEB-INF/classes folder. But it is not working. Your help will be appreciated.
In Addition: In the explaination you have given, It is telling me not to use this code snippet inside the third party jar in this way...What is the exact usage of this statement
ClassLoader.getSystemResource will load the resource from the system classloader, which uses the classpath of the application as started from the command line. Any classloaders created by the application at runtime (i.e. the one that looks in WEB-INF/classes) are not on the system classpath.
You need to
Look through the script that starts your server, find out which directories are on the classpath there, and put your SOAPConfig.xml in one of those. If necessary, change the classpath in the script to look in a separate directory that's just used for your config file.
Track down the person who used ClassLoader.getSystemResource in the library, kick them squarely in the nuts, and tell them never to do that again.