|
Table of Contents |
IntroductionThis article discusses issues with the Libraries tab in Eclipse Build Path including how to set up library JARs from third-parties. The Eclipse User Library is a great mechanism for organizing JARs used in your project, but it's a little passé. In the years since it became an option, Maven, Ivy and the increase of the use of Eclipse in shared environments have obsoleted it. I expose this feature as I used to use it in Setting up a User Library in Eclipse Build Path. Before getting to the meat of libraries and Eclipse Build Path, let's look at some obliquely related issues that influence the discussion. |
One of the primo ways of managing project builds has become Maven, a build system by which dependencies (mostly JARs) are brought in from an official repository that guarantees no mistakes in version, origin (signed or not), etc. You should read more about Maven. A good place to start might be Using Maven with Eclipse for Web Applications and some notes I've made.
Maven was originally created by Sonatype, but has since been made an Apache project.
Maven projects are set up radically differently in Eclipse than the way Eclipse usually does things. I'm not going to discuss Maven here for that reason.
Ivy solves some of the complexity that is complained about by Maven users. I know little about it at this point except that it doesn't impose upon the structure of your Eclipse project in the least.
Eclipse has made some questionable decisions in how its configuration files are set up that gives developers a lot of trouble when it comes to sharing. In particular, Eclipse workspaces are not at all portable or transferrable. They can't be shared. Even projects themselves are shareable only through rigid, easily broken steps.
Workspaces are home to some settings relevant to the development of one or several logically connected projects. Reasons might include commonality between projects a team is working on or, in the case of a single Eclipse user, just the fact that he or she is working on the projects in the workspace. I tend to separate projects among workspaces, Android projects in one, web applications in another, sample code in still another. I maintain several separate workspaces for myriad web applications usually split by destination, purpose, employer, etc.
There are tricks to making projects portable, some of which are discussed in How to Set Up Git: Git and the Eclipse Project.
With this background, where do (third-party) JARs fit in?
JARs can live outside the Eclipse project, but doing so ruins the .classpath file for projects that are shared with other developers. JARs should rather live somewhere in the projects that consume them. Setting them up using the Eclipse User Library is a very elegant way of doing this, but it comes with baggage that also invalidates the project's .classpath file, contaminating it with full paths that aren't portable to your teammate's computers.
A better solution is to use Build Path -> Libraries -> Add JARs... to add them somewhere. That "somewhere" depends on the type of project you're working in.
From either of these places, you add them to project using Eclipse's Build Path. Use the Add JARs... button because that this ensures the paths are relative to the project and not full paths which are not portable. When someone imports this project after checking it out using Subversion, CVS, Git, etc., the .classpath file contains no full paths and is therefore accurate no matter what machine it's used on.
I also like to arrange my third-party libraries in subdirectories under lib that say where they're from, for instance, Hibernate JARs under lib/hibernate, Jersey JARs under lib/jersey. In that way, I understand at a glance where a library's from. (However, for building WAR deployment, this poses special problems that are covered in an appendix to this article.)
There is, however, a fly in the ointment. When you associate Javadoc or library source code with a JAR in Eclipse, this is done using a full path entry in .classpath. For this reason, any such associations you create will go into that file and, if you commit it to source-code control, render it invalid from a shared perspective. Your teammate will have to fix up Build Path after getting the new .classpath copy.
...UNLESS (!) you follow careful steps to avoid it. These follow in the next section.
Download, by preference, source and/or Javadoc JARs. This example uses them. Pure source code or Javadoc (HTML files) are not illustrated, though it's likely possible. This solution only works by keeping the third-party JAR inside the workspace project, i.e.: on a path like project-name/lib.
The example here is based on using Apache commons-configuration-1.7.jar and its accompanying source and Javadoc JARs, which I've downloaded to project TryStuff/lib/apache:
This resulted in adding the following to .classpath, nothing of which constitutes a non portable, full filesystem path:
<classpathentry kind="lib" path="lib/apache/commons-configuration-1.7.jar" sourcepath="lib/apache/commons-configuration-1.7.sources.jar"> <attributes> <attribute name="javadoc_location" value="jar:platform:/resources/TryStuff/lib/apache/commons-configuration-1.7-javadoc.jar!/" /> </attributes> </classpathentry>
If your project is a Dynamic Web Project, you'll also want to tick the new library off as a JEE module dependency. Please see Java EE Module Dependencies.
Practically speaking, you're going to do this when in debugging or running you find you're missing a class:
Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:248) ... 11 more
that you identify as coming from a third-party library such as log4j, slf4j, JSF (Apache MyFaces), Hibernate, JSTL, etc., all stuff that is commonly consumed, but not actually part of Java.
For our example here, we're debugging some new Hibernate code. Hibernate happens to use slf4j for its logging and so I have to provide a suitable slf4j JAR.
After adding the library and its JARs, if the class you're looking for is still missing, you must do more research to determine where it comes from. For example, if you are missing org.slf4j.impl.StaticLoggerBinder and though you were going to get it from a library you created called Slf4j and a JAR slf4j-api-1.5.11.jar, you might Google and stumble upon http://www.slf4j.org/codes.html where you learn that this class comes from one of a list of other JARs from among which you must choose one. You remove the API JAR and add slf4j-simple-1.5.11.jar, and try again.
Suddenly, you get further in your debugging and slf4j begins to work putting out logging messages detailing what's not working (and no more java.lang.ClassNotFoundException).
For trouble with Dynamic Web Projects missing symbols, please read Java EE Module Dependencies.
Eclipse's Build Path comes equipped to handle a number of built-in (or pre-prescribed) situations similar to the ones we've just played with here. These include:
You should use the first options listed in support of everything you do. Only when these fail to support you, as is the case when you create your own consumable library code, should you resort to using User Library or Web App Library, but then you should use those instead of hacking in your code via the Use External JAR option—again, wherever possible.
Note: The Web App Library is discussed in some detail in the article, Web App Libraries and Eclipse Build Path. It's a separate subject you should entertain if you're developing a web application or a library project contributing to a web application.
This was in fact already covered above in one of the steps. You can either edit and set the Javadoc location for the specific JAR (something I've not figured out how to do yet) or point the Source attachment path to the top of the source directory (if it's an open source or other JAR that comes with source code).
It has been noted elsewhere in this document that you can attach source code or Javadoc to JARs in order for Eclipse to display hints and doc blurbs at the right points in your code. This is especially useful for Apache Tomcat and other critical library support.
For an example of how to do this, read this.
By the time I got around to attaching Apache Tomcat 6.0 doc, which has only sources no Javadoc, the release number had advanced from 26 to 29. I downloaded the source (6.0.29) and attached it to the JARs (6.0.26) and it worked fine anyway.
If you're writing a servlet or other code relying on the Tomcat container, you might like source code and (therefore) Javadoc available. To do this, simply to to www.apache.org, downloads, then download the source tarball or zip. Extract it to somewhere, then go to Build Path -> Libraries, click to expand Apache Tomcat v6.0 (or whatever your version), click to expand servlet-api.jar and establish the source attachment per the instructions in the appendix above. The Apache source code path I used was C:\Users\russ\dev\downloads\apache-tomcat-6.0.26-src, in other words, the root of the source code project I downloaded and extracted.
The foregoing exercise gave me visibility of
This is for Eclipse prior to Helios (3.6). For modern Eclipse, be sure to read the next section.
When an Eclipse warning appears:
Classpath entry <JAR name> will not be exported or published. Runtime ClassNotFoundExceptions may result.
You can either go to Build Path -> Configure Build Path -> JEE
Module Dependencies
and click to include the library in Eclipse's
deployment (ultimately to the WAR file), or, as in the presumable case of
JUnit, exclude it from consideration (and eliminate the warning) by
right-clicking on the warning, choosing Quick Fix, then excluding it
as shown below.
Jan 24, 2011 1:26:30 PM org.apache.catalina.core.AprLifecycleListener init INFO: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: /home/russ/dev/jdk1.6.0_21/jre/lib/amd64/server:/home/russ/dev/jdk1.6.0_21/jre/lib/amd64:/home/russ/dev/jdk1.6.0_21/jre/../lib/amd64:/usr/lib64/xulrunner-addons:/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib Jan 24, 2011 1:26:30 PM org.apache.tomcat.util.digester.SetPropertiesRule begin WARNING: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.jee.server:de.vogella.jersey.first' did not find a matching property. Jan 24, 2011 1:26:30 PM org.apache.coyote.http11.Http11Protocol init INFO: Initializing Coyote HTTP/1.1 on http-8080 Jan 24, 2011 1:26:30 PM org.apache.catalina.startup.Catalina load INFO: Initialization processed in 466 ms Jan 24, 2011 1:26:30 PM org.apache.catalina.core.StandardService start INFO: Starting service Catalina Jan 24, 2011 1:26:30 PM org.apache.catalina.core.StandardEngine start INFO: Starting Servlet Engine: Apache Tomcat/6.0.29 Jan 24, 2011 1:26:30 PM org.apache.catalina.core.ApplicationContext log INFO: Marking servlet Jersey REST Service as unavailable Jan 24, 2011 1:26:30 PM org.apache.catalina.core.ApplicationContext log SEVERE: Error loading WebappClassLoader context: /de.vogella.jersey.first delegate: false repositories: /WEB-INF/classes/ ----------> Parent Classloader: org.apache.catalina.loader.StandardClassLoader@578088c0 com.sun.jersey.spi.container.servlet.ServletContainer java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1645) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1491) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1095) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:993) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4350) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4659) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardHost.start(StandardHost.java:785) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:445) at org.apache.catalina.core.StandardService.start(StandardService.java:519) at org.apache.catalina.core.StandardServer.start(StandardServer.java:710) at org.apache.catalina.startup.Catalina.start(Catalina.java:581) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Jan 24, 2011 1:26:30 PM org.apache.catalina.core.StandardContext loadOnStartup SEVERE: Servlet /de.vogella.jersey.first threw load() exception java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1645) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1491) at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1095) at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:993) at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4350) at org.apache.catalina.core.StandardContext.start(StandardContext.java:4659) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardHost.start(StandardHost.java:785) at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1045) at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:445) at org.apache.catalina.core.StandardService.start(StandardService.java:519) at org.apache.catalina.core.StandardServer.start(StandardServer.java:710) at org.apache.catalina.startup.Catalina.start(Catalina.java:581) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Jan 24, 2011 1:26:30 PM org.apache.coyote.http11.Http11Protocol start INFO: Starting Coyote HTTP/1.1 on http-8080 Jan 24, 2011 1:26:30 PM org.apache.jk.common.ChannelSocket init INFO: JK: ajp13 listening on /0.0.0.0:8009 Jan 24, 2011 1:26:30 PM org.apache.jk.server.JkMain start INFO: Jk running ID=0 time=0/11 config=null Jan 24, 2011 1:26:30 PM org.apache.catalina.startup.Catalina start INFO: Server startup in 268 ms
This happens because you've not done the deployment, something that used to happen in the context of Java Build Path in versions of Web Tools Platform prior to what went out with Helios. This used to be done in Java EE Module Dependencies right after adding the (user) library in Build Path.
Now (in Helios and beyond), this happens differently:
At this point, the errors you got trying to start the server (probably while running the project using Run on Server...) should disappear unless there is yet more missing from the deployment assembly.
The Java classpath is the mechanism by which executing Java code solves the problem that LD_LIBRARY_PATH on Linux solves for code written in C or C++ (i.e.: that uses the standard libraries).
It seems important to note how to solve a Java classpath problem in Eclipse without perverting the actual classpath or pulling foreign files artificially into an Eclipse project in order to solve the classpath issue.
This is a rather advanced topic; see Developing a Java Native Interface (JNI). If you are consuming a JAR that itself must consume a JNI, then you must either a) ensure the parent directory of that JNI is in the Java classpath or, as I show here with the Flexera® licensing JAR that consumes an Emerson/Avocent JNI in support of the Avocent Management Platform, name that JNI's parent in the Native library location in Build Path.
The suggestion of organizing third-party JARs under lib in appropriately named subdirectories that indicate origin leads to some trouble when the web application is packaged into a WAR. This is because the ant copy doesn't find the JARs. Use the flatten attribute as illustrated in the extensive sample code below.
<target name="deploy" depends="compile,updatedb"> <echo message="==== Creating WAR file and deploying... ====" /> <!-- Copy all the files needed to build WAR: --> <copy todir="${classes.dir}/resources"> <fileset dir="${src.dir}/resources"> <include name="**/*" /> </fileset> </copy> <copy file="${src.dir}/log4j.properties" todir="${classes.dir}" /> <copy file="webplatform-servicelocator.log" todir="${classes.dir}" /> <copy todir="${flat_libs.dir}" flatten="true"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> <exclude name="**/*ant-contrib*" /> <!-- only used by build.xml --> <exclude name="**/*sqlscript*" /> <!-- only used by build.xml --> <exclude name="**/*mockito*" /> <!-- only used in testing --> </fileset> </copy> <war destfile="${dist.dir}/blackpearl.war" webxml="${web.dir}/WEB-INF/web.xml"> <fileset dir="${web.dir}" /> <lib dir="${flat_libs.dir}" /> <classes dir="${classes.dir}" /> <manifest> <attribute name="Built-By" value="Russell Bateman" /> <attribute name="Implementation-Title" value="Fun application" /> <attribute name="Implementation-Version" value="0.9" /> <attribute name="Implementation-Vendor" value="Etretat Logiciels, LLC" /> </manifest> </war> <copy file="${dist.dir}/blackpearl.war" todir="${deploy.dir}" /> <echo message="==== Deploy complete ====" /> </target>