Is there a standard way (i.e. defined by some Java/J2EE/etc. spec) to provide a custom class loader to a Java Servlet Container which should be used to load a WAR file?
On a new project we are extending a large commercial Java software package (Foo) with web services which requires some flexibility of deployment (as separate services, etc). In particular, we want to avoid the necessity to include in each WAR file all of the Foo software dependency Jar files as they are numerous, large, and will be changing with patch/bugfix releases as we develop. Similarly, it is highly undesirable to have to copy all of the dependencies into each Servlet container's "lib" directory.
Ideally, I would like to tell the Java application server that these WAR files must be loaded using a custom class loader which I will provide which automatically includes the Foo software dependency Jars. Something like this (in Java-pseudocode):
public class MyWarFileClassLoader extends ClassLoader {
protected URLClassLoader urlcl;
public MyWarFileClassLoader(File warFile) {
File installDir = System.getEnv("FOO_HOME");
List<File> fooEntries = new File(installDir, "jars").listFiles("*.jar");
fooEntries.add(new File(installDir, "resources"));
fooEntries.add(warFile);
this.urlcl = new URLClassLoader(fooEntries);
}
public Class<?> findClass(String name) {
return this.urlcl.findClass(name);
}
}
If there is no standard way to do this, is there a straightforward way to achieve the same goal for several WAR files, regardless of the target Servlet Container?
[Edit]
To put it another way: is there a common pattern for allowing WAR files to manage their own dependencies at runtime instead of relying on the Servlet Container configuration? I could, of course, have the WAR file manifest include a Class-Path attribute but then the entries are still "hardcoded" at build time rather than detected automatically at runtime.
There is no standard way to enforce the use of a particular custom classloader in a Java EE application, to load classes from a predefined source. There is however the ability to bundle libraries within a Java EE application, so that multiple modules (including web-modules residing in WARs) can load and access classes in the bundled libraries.
The Java EE specification allows an Enterprise application deployment (a .ear file) to bundle libraries in a library deployment directory; by default this is the lib directory within a .ear file. These libraries may then be used by multiple web-modules (located in different .war files) within the root of the .ear file. The relevant part of the Java EE 6 specification is Section EE 8.2.1, where the following is stated:
A .ear file may contain a directory that contains libraries packaged in JAR files. The library-directory element of the .ear file’s deployment descriptor contains the name of this directory. If a library-directory element isn’t specified, or if the .ear file does not contain a deployment descriptor, the directory named lib is used. An empty library-directory element may be used to specify that there is no library directory.
All files in this directory (but not subdirectories) with a .jar extension must be made available to all components packaged in the EAR file, including application clients. These libraries may reference other libraries, either bundled with the application or installed separately, using any of the techniques described herein.
It is important to note that all Java EE compliant application servers (WebLogic/WebSphere/JBoss et al) will support deployment of EAR files with bundled libraries. However, there are servlet containers (like Tomcat and Jetty) which do not comply with the entire Java EE specification; such containers will not support deployment of EAR files.
In the event where the libraries are required to be accessed by multiple web modules in a servlet container (either due to the choice of the container or due to a preference for WAR files), you ought to rely on the servlet container support for shared libraries to deploy the WAR files without the libraries. The Java EE specification does not mandate any requirement in this area concerning the use of installed libraries. Some containers support shared libraries better than others, by supporting versioned shared libraries (where deployed applications may use only one version among several), while others (like Tomcat) do not.
Related
I am trying to add some dependencies jar files. But these files when put in lib/endorsed or in WEB_INF/lib.jar results in startup error for jboss instances. I suppose this is happening because flat classloader structure of JBoss. If somebody has implemented the classloader settings in jboss-web.xml
<class-loading>
<loader-repository>com.example:archive=unique-archive-name</loader-repository>
</class-loading>
Can somebody give me a real life example ?
Also where should I place these jar files - lib/endorsed of jboss, or lib folder in deploy folder or in WEB_INF/lib
Duffymo's directive on not putting jars in endorsed is ignored at your peril.
In some additional detail:
Placing libraries in your WEB-INF/lib is a best practice for portability and consistency as it adheres to a standard provision for creating self-sufficient and distributable web archives, but you need to pay close attention to the class-loading declaration you're putting in your jboss-web.xml.
Assume a simple scenario without the class-loading declaration and a fictional example.jar:
If you place example.jar in WEB-INF/lib and it does not also exist in jboss//lib, then example.jar will only be visible to that specific WAR.
If you place example.jar in WEB-INF/lib and it does also exist in jboss//lib, the instance in WEB-INF/lib will essentially be ignored and the WAR will use the JBoss server instance's unified class loader to load the example classes from jboss//lib/example.jar. (The same would apply to any other WARs or EARs in the same server instance, assuming no class-loading overrides.)
The class-loading declaration is necessary in cases (such as) where you have two different versions of example.jar:
- jboss//lib: example1.0.jar
- WEB-INF/lib: example2.0.jar
In this case, JBoss will create a unique and isolated classloader for your WAR which will avoid jboss//lib/example1.0.jar in favour of WEB-INF/lib/example2.0.jar in the context of your WAR.
In summary, if you're only running one WAR in the jboss server instance and/or you have no conflicting JAR issues, ditch the class-loading declaration and put your JARs in jboss//lib. It makes the WAR file more lightweight, overall deployment may be simpler and you will not consume additional memory with extra class versions during hot-deploys.
They belong in the WEB-INF/lib directory of your WAR file. Don't put things in the endorsed folder.
I have an Enterprise project (EAR) with one EJB and several web modules, these web modules have lots of classes in common, they are exactly the same for each project, so if I modify one of them I'll have to manually copy the code to the other projects as well. I don't want to put them in my EJB module because they use a lot of front-end related resources.
Is there a way to share these classes between the web projects?
Obs: They also use classes and resources from the EJB module.
Make another module with all commun classes and package it as a Jar. Then add the jar as a dependency to the other project.
Maven should be a good tool for this project.
There is no way to have shared classes outside of a .war which are capable of having web-specific resources injected.
I would refactor the common classes into a separate .jar. You could make them EJBs, or just regular classes. Either way, you won't be able to inject web-specific resources; the classes in .wars will have to pass such things as method parameters. In the case of EJBs, you can't directly pass non-serializable objects like HttpServletRequests; I don't know if that will create a significant impediment.
An EJB .jar can be placed anywhere in the .ear, but if you choose to make a non-EJB .jar, it can be placed in the lib directory of your .ear file. It's also a good place for EJB interfaces, if you aren't writing no-interface EJBs. From the Java EE specification's "Application Assembly and Deployment" chapter:
A .ear file may contain a directory that contains libraries packaged in JAR files. The library-directory element of the .ear file’s deployment descriptor contains the name of this directory. If a library-directory element isn’t specified, or if the .ear file does not contain a deployment descriptor, the directory named lib is used. An empty library-directory element may be used to specify that there is no library directory.
All files in this directory (but not subdirectories) with a .jar extension must be made available to all components packaged in the EAR file, including application clients. These libraries may reference other libraries, either bundled with the application or installed separately, using any of the techniques described herein.
I have two applications deployed on a Tomcat server (v7) , both the applications use same shared library. I wanted to reduce the permgen memory foot print of these apps on JVM so I decided to take the common shared library out of the individual wars and put it in the tomcat library folder.
However this experiment backfired as the ClassLoader is per application context even though I moved the libraries to tomcat lib, shared library is getting loaded into the context of both the apps, worse it is getting loaded into the context of other apps that do not use this shared library.
Is there a way to load the library only once across the contexts in the web server?
I understand the risks of thread safety and security, those risks are mitigated in my case.
Classes from jars in the Tomcat lib directory get their own class loader which is shared amongst all deployed applications.
As you have noted each individual application also has it's own class loader.
At this point it's important to understand where Tomcat finds classes for each application. To paraphrase Apache Tomcat Class Loader HOW-TO, application classes are located in the following order:
JVM bootstrap classes
System classes
/WEB-INF/classes of the application
/WEB-INF/lib/*.jar classes
Common class loader (being jars in the "Tomcat lib" directory)
Items 1,2 & 5 above are separate class loaders with their own repository. Items 3 & 4 are a single class loader, but there is a separate one for each web-app.
Therefore, if you have copied your library jar into the Tomcat lib directory, and not removed it from your web-app then you will still be loading and using the jar in your web-app. Double check the deployment to ensure that the library jar is in fact no longer present in the WEB-INF/lib directory of each app.
under a Java Webapp,
I want to set /WEB-INF/lib to D:/somepath/lib
and /WEB-INF/classes to E:/somepath/classes
and web.xml to F:/somepath/config.xml
how to config these under tomcat or jetty to change the convention?
The specification (Java™ Servlet Specification Version 2.3) only mentions standard folders:
The web application classloader must load classes from the WEB-INF/classes
directory first, and then from library JARs in the WEB-INF/lib directory.
With Tomcat or Jetty you cannot do it with "out-of-box" provided functionality:
Tomcat:
WebappX — A class loader is created for each web application that is deployed in a single Tomcat instance. All unpacked classes and resources in the /WEB-INF/classes directory of your web application, plus classes and resources in JAR files under the /WEB-INF/lib directory of your web application, are made visible to this web application, but not to other ones.
Jetty:
The normal configuration is for each web context (web application or war file) is given it's own classloader, which has the system classloader as it's parent. Such a classloader hierarchy is normal in Java, however the servlet specification complicates the hierarchy by requiring that:
Classes contained within WEB-INF/lib or WEB-INF/classes have priority over classes on the parent class loader. This is the opposite of the normal behaviour of a java 2 class loader.
However, you can:
Add extra classpaths to Jetty
Add directory to Tomcat classpath
Cheers
On a Unix system you could do this manually by setting up soft links from the expected places to the locations on the other disks.
Both application servers you mention are open source, so you could write your own version of them. I guess the part that implements the classloader for a web app is what you would have to change.
But if you must engage in such shenanigans, you should suspect that you have made a wrong decision earlier. I can think of no good reason to do what you want to do.
I am trying to load some classes that are common to all the web applications of my ear in a java ee 5 application.
I tried to do this by putting the classes (not jar) in
a) directory called "lib"
b) also specifying in application.xml's
<module><java>lib/common.jar</java></module>
and was not successful by either option a or b
but when I jarred the classes into common.jar, I was able to load the classes by method b)
Do both these method need the classes to be jarred ?
what is the difference between providing the classes via the above 2 methods ? why does it seem like there are two ways to specify loading common classes ?
I'm not sure which application server is being referred to here, and the nature of the common.jar file. For now, I'll assume that the application server is any Java EE 5 container, and that the common.jar file is a utility jar (and not an EJB or similar module).
The Java EE 5 Platform Specification actually defines the manner in which library support is to be provided by containers:
A .ear file may contain a directory that contains libraries packaged in JAR files. The library-directory element of the .ear file’s deployment descriptor contains the name of this directory. If a library-directory element isn’t specified, or if the .ear file does not contain a deployment descriptor, the directory named lib is used. An empty library-directory element may be used to specify that there is no library directory.
All files in this directory (but not subdirectories) with a .jar extension must be made available to all components packaged in the EAR file, including application clients. These libraries may reference other libraries, either bundled with the application or installed separately, using any of the techniques described herein.
This does not mean that method B is incorrect, it is the one to be used for application servers like JBoss 4, that did not support the library-directory element in application.xml. I believe, Glassfish also supports the lib directory concept without a corresponding library-directory element.
Coming back to the question, placing classes alone in a directory in the EAR file appears to be supported only in WebLogic Server via the APP-INF\classes structure (needless to say, this is not a platform standard). Hence, it is recommended to jar the common classes and use the application server supported mechanism to make these common classes available to other modules in the application.