Libraries (or JAR-files if you will) exist in many different locations in your Liferay installation. Some reside in Tomcat's lib folder, others are included in the lib folder of Liferay's ROOT. And of course, there are the libraries that are included in your own portlets. It's no secret that Liferay performs some magic tricks to enable access to different classes in different contexts. This complexity is mostly abstracted away from developers, so you don't have to worry if class X is indeed available for you from within a portlet. However, there are some very specific particularities that you need to be aware of as a Liferay developer. Welcome to the wonderful world of classloading in Liferay!
By the way, understanding the principles in this article might help you in certifying for Liferay, as you're guaranteed to get some questions on this topic in your certification exam!
Classpaths in Liferay
Roughly speaking, 3 different classpaths are active in a Liferay installation.
The global classpath includes all JAR files that are defined in Tomcat's lib and lib/ext folders. All deployed webapps (including ROOT and your own portlets and hooks) have access to these libraries. Examples of included libraries are servlet-api.jar (Servlet API), portlet.jar (Portlet 2.0 API) and portal-service (Liferay Service API). Because they are already available, you don't want to include these libraries in your own portlet or hook. Otherwise, the library will be loaded twice, which in most cases will result in deploy failure.
The portal classpath includes all JAR files from the lib folder of ROOT. These 200+ libraries are only accessible from within the ROOT application itself. Examples of included libraries are portal-impl.jar (Liferay Implementation), util-java.jar, util-bridges.jar, util-taglib.jar, Spring libraries, ...
The portlet/hook classpath includes all JAR files from the lib folder of the deployed portlet or hook. These libraries are only accessible from within the portlet or hook itself.
Who can access what?
With all those different classpaths, it's important to know which libraries are available in which context.
Inside Liferay's ROOT, classes and JSPs can only access the global and the portal classpath.
Inside a portlet, you have access to both the global and the portlet classpath. This means that both the global libraries and the portlet libraries are loaded together. Or why it is extremely important never to include libraries in your portlet that are already available in the global classpath! If you're using Maven and you still want to compile against those libraries in the global classpath, put the scope of your dependency to provided.
Inside a hook the same rules apply as for portlets. Most hooks have access to both the global and the hook classpath. There is however one exception: JSP hooks. At deploy time, JSPs that are overriden in a JSP hook replace the original JSP in the ROOT. At this point, the JSP can only access the global and portal classpaths, NOT the hook classpath. For instance, it is impossible to let an overridden JSP access a class that's contained in a third-party JAR that's included in the lib folder of the hook.
Sharing libraries between portlets/hooks
If you've been paying attention, you'll agree with me that the only way to share libraries between different portlets is to add those libraries tot the global classpath, i.e. in Tomcat's lib or lib/ext folder. However, generally, this is not a great idea. At least, you should make sure that those libraries don't end up to be duplicated in different classpaths. An example.
Suppose all of your portlets make heavy use of the Spring framework. To not include libraries like spring-context.jar, spring-beans.jar, spring-core.jar in every portlet, you decide to add them to the global classpath in tomcat/lib/ext. But, oh my, suddenly your portal stops working. Why? Because Liferay's ROOT already includes these Spring libraries, and now Liferay tries to load those libraries twice for the ROOT application. Result: conflicts!
If you still wish to share libraries through the global classpath, take the following thoughts into account:
- Make sure the libraries don't already exist on the portal classpath. If so, forget about sharing these libraries and include them in every portlet or hook that needs them.
- Make sure the libraries are not included in any of the deployed portlets or hooks. If so, set the scope of these dependencies to provided instead of compile, so they are not packaged in the WAR file.
- Prepare to lose some amount of flexibility. Indeed, all your portlets and hooks will now have to commit to the same version of the library you're adding. This can be good for consistency, but do you really want to recompile all existing portlets and hooks if you suddenly need to use the latest version of library X in one of them?
Liferay does provide a mechanism to gain access to classes in the portal classpath while inside your portlet by the means of portal dependency JARs. This is a property you can set in the liferay-plugin-package.properties configuration file. It accepts a list of JAR files that are to be found in the portal classpath.
At deploy time, Liferay will copy over all matching JARs from the portal's lib folder to the lib folder of the portlet or hook. So you don't actually get access to the portal classpath. Instead, the necessary JAR files are copied over to the portlet/hook classpath.
In some rare cases, it can be useful to define these dependency JARs. Some framework JARs are enhanced by Liferay to enable some specific behavior. And by including the JAR this way, you're sure you have the exact same implementation version of the library. But in general, try to avoid this type of configuration as it tends to create more problems than it solves. If you're using it anyway and your compilation units depend on those JAR's, don't forget to specify these dependencies as provided in Maven. Otherwise, they will be added twice to the portlet's lib folder.
Being able to include dependency JARs from the portal context into your own portlet or hook effectively allows you to depend on portal-impl.jar in your plugin. Don't. Do. This. The portal-impl.jar library is not meant to be exposed to custom plugins. That's the whole point of having an API (portal-service.jar). And remember what we said about portal-service.jar: you don't explicitely add this library to your portlet as it's already available in the global classloader.
If you can solve these questions, you've totally mastered the classloading concepts in Liferay. Feel free to post a comment if you have more questions on the topic.
- I created a JSP hook that uses Apache POI to export the list of user groups to Excel through a new button in the UI. Which of the following approaches will work if you know that poi.jar is included in ROOT/WEB-INF/lib?
- Add poi.jar to the lib folder of my JSP hook
- Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my JSP hook
- Add poi.jar to the lib/ext folder of Tomcat
- None of the above
- I created a service hook that uses POI to export the list of user groups to Excel each time a new user group is created. Which of the following approaches will work? Multiple answers are possible
- Add poi.jar to the lib folder of my hook
- Add poi.jar to the list of portal.dependency.jars in the portlet-plugin-package.properties file of my hook
- Add poi.jar to the lib/ext folder of Tomcat
- None of the above
- My portlets always use the Vaadin framework. Which of the following approaches will work if you know that vaadin.jar is included in ROOT/WEB-INF/lib? Multiple answers are possible.
- Add vaadin.jar to every portlet's lib folder separately
- Add vaadin.jar to the lib/ext folder of Tomcat
- Add vaadin.jar to the lib/ext folder of Tomcat and remove vaadin.jar in ROOT/WEB-INF/lib
- Add vaadin.jar to the list of portal.depency.jars in the portlet-plugin-package.properties file of my portlets
Visit http://blogs.aca-it.be for more blogs on Liferay and Java.