Why combine all jars together? - java

I have create RESTful web service based on the JAX-RS and used Jersey embedded web server. My ant script compiles code successfully while it gives me error ClassNotFoundException when I run my main class. So after doing research I came up with solution & here it goes java build ant file with external jar files . What I did was created a bundled jar file try to execute that & it works perfectly fine. I want to know the reason behind :
why this solution works ?
Why I should combine all jar file ?
Is it similar to war file which we create following J2EE architecture otherwise war will not be extracted by server ( say TOMCAT ) & in my case jar file for Jersey embedded HTTP server?
EDIT:
Here is my ant build.xml file
<property name="lib.dir" value="${user.dir}/lib"/>
<property name="build.dir" value="${user.dir}/build"/>
<property name="build.lib.dir" value="${build.dir}/lib"/>
<property name="build.classes.dir" value="${build.dir}/classes"/>
<property name="src.dir" value="${user.dir}/src/main/java"/>
<property name="main.class" value="com.assignment.ConsoleServer"/>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="init" depends="clean">
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build.dir}"/>
<mkdir dir="${build.classes.dir}"/>
</target>
<target name="copy_jars" depends="init" >
<copy todir="${build.lib.dir}" >
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
</copy>
</target>
<target name="compile" depends="copy_jars">
<javac srcdir="${src.dir}" destdir="${build.classes.dir}" classpathref="classpath" includeantruntime="false"/>
</target>
<target name="jar" depends="compile">
<jar destfile="${build.dir}/${ant.project.name}.jar" basedir="${build.classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main.class}"/>
</manifest>
<zipgroupfileset dir="${lib.dir}" includes="*.jar"/>
</jar>
</target>
<target name="run" depends="jar">
<java fork="true" classname="${main.class}">
<classpath>
<path refid="classpath"/>
<path location="${build.dir}/${ant.project.name}.jar"/>
</classpath>
</java>
</target>
Here is my folder structure
P.S. I am not java expert so pardon me if this question is stupid.

Why this solution works?
In your particular case, you probably didn't include all of the necessary dependencies in your deployment in your previous. (It is not clear from your question how you were originally doing the deployment.)
Now you have put all of the application and dependent class files, etc into one JAR file, and presumably you are deploying / running that file. It works because now it has everything that it needs to run ... which it didn't before.
Why I should combine all jar file?
In your case I suspect that it was not strictly necessary. There was probably a way to "deploy" all of the dependencies without combining them into a single JAR file.
However, there is one case where a "uber-jar" has advantages. That is when the JAR is intended to be an "executable" JAR, and you want to be able to distribute / install it as a single file. (And executable JAR
file can refer to external JARs, etc, but the way that you have to do
it is "fragile".)
Is it similar to war file ... ?
Sort of, though a WAR file contains JAR files ... and typically other kinds of resources that the web-container understands.

The solution works because you packed all you service classes and depending libraries in one jar. That jar and everything inside will be in the class path and visible to your execution virtual machines class loader.
If you leave your depending libraries out your Jersey Web server needs to have them on it's class path, then you wouldn't get ClassNotFoundExcpetion
You shouldn't pack web application in single jar. You should crate war file where you dependencies will be placed inside WEB-INF/lib. You would easily then deploy that war on any application server. Switching to Maven instead of Ant can help a lot.
EDIT: After you added more details to description and ant
If you don't want to use fat-jar you can either
modify your antjava task to specify classpath that will reference
all external libraries (basically telling ant how to build
-classpath parameter for java -jar command
even better, modify your javac ant task by making complete Manifest file that specifies Class-Path correctly, take a better
look at the solution (at the bottom) of the answer you linked (java build ant file with external jar files)
For completness reference on Manifest here

Related

Specification of configuration file (log4j2.xml) for log4j as VM argument just working in eclipse

I'm using the new Log4j2 - Java Logging Framework. If I specify the path to the configuration file in eclipse as VM argument -Dlog4j.configurationFile=/home/../config.xml everything works fine. The configure file is loaded and the logging works as expected, i.e. all logs are written to files. If I additionally use the -Dlog4j.debug, I get the corresponding debug messages from the framework which confirms the correct loading of my configuration file.
However, when I use ANT with a build.xml file the logging framework seems to be initialized with the default configuration - the logs no longer written to the files but to the console. I start the created prog.jar file with the following statement from the console:
java -Dlog4j.configurationFile=/home/../config.xml -Dlog4j.debug -jar prog.jar
The only debug message I get from to Log4j2 framework is the following:
DEBUG StatusLogger org.slf4j.helpers.Log4jLoggerFactory is not on classpath. Good!
In the following, parts of the build.xml file which I use to create prog.jar:
<target name="init">
<tstamp/>
<mkdir dir="${build}"/>
</target>
<path id="master-classpath">
<fileset dir="${lib}/apache-log4j-2.11.1-bin/">
<include name="log4j-api-2.11.1.jar"/>
<include name="log4j-core-2.11.1.jar"/>
</fileset>
</path>
<target name="compile" depends="init" description="compile the source">
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}" includeantruntime="true">
<classpath refid="master-classpath"/>
</javac>
</target>
<target name="jar" depends="compile" description="generate the distribution">
<mkdir dir="${dist}"/>
<jar jarfile="${dist}/prog.jar" basedir="${build}" compress="true">
<fileset dir="${src}" includes="**/*.java"/>
<zipgroupfileset dir="${lib}/apache-log4j-2.11.1-bin/" includes="*.jar" />
<manifest>
<attribute name="Main-Class" value="ch.zwas.aks.Runner"/>
</manifest>
</jar>
</target>
I'm confused why it works using Eclipse but does apparently not work when I create the project with ANT and specify the configuration file in the console. Furthermore, I have no idea why there are no more debug messages when I run the jar from the console.
Thanks for your support.
Thanks to the hint of Vikas Sachdeva I was able to solve the problem. The problem was that I apparently packed too many jar files from the log4j library into prog.jar. When I include just the following jar files it works as expected with ANT / console as well.
junit-X.jar
log4j-api-X.ja
log4j-core-X.jar
slf4j-api-X.jar
slf4j-simple-X.jar
The other jars which came with the log4j library I left out. It seems that Eclipse ignores these, but when included with the build.xml into to finally jar then they cause trouble.

Could not find or load main class Java

Good evening Stack Overflow!
I started learning Java quite a few days ago, but not using an IDE once my computer is a bit slow. So, I decided to use Sublime Text, and compile things using ant on the cmd, since it seems to be the most reasonable thing to do.
Today I started ( at least tried ) to follow along a series of LWJGL tutorials (which are really cool), from ThinMatrix, but I can't manage to solve an error which I get every time I try to compile the 'project'.
This is the structure of my project:
LWJGL
src
com
game
test
MainGameLoop.java
renderEngine
DisplayManager.java
build.xml
And ladies and gentlemen... the build.xml (following Ant's official HelloWorld tutorial):
<project name="LWJGL" basedir="." default="main">
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="lib.dir" value="lib"/>
<property name="jar.dir" value="${build.dir}/jar"/>
<property name="main-class" value="com.game.test.MainGameLoop"/>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar" />
</path>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" includeantruntime="false" classpathref="classpath" />
</target>
<target name="jar" depends="compile">
<mkdir dir="${jar.dir}"/>
<jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}">
<manifest>
<attribute name="Main-Class" value="${main-class}"/>
</manifest>
</jar>
</target>
<target name="run" depends="jar">
<java jar="${jar.dir}/${ant.project.name}.jar" fork="true">
<jvmarg value="-Djava.library.path=lib/natives-win" />
</java>
</target>
<target name="clean-build" depends="clean,jar"/>
<target name="main" depends="clean,run"/>
Every time I run ant on the command line, inside the root folder of my project, I get this:
[java] Error: Could not find or load main class com.game.test.MainGameLoop
[java] Java Result: 1
I'm struggling to understand what is the root problem of this, I kinda sexually abused Google Search trying to find an answer on Java forums, Blogs, and even here...
I don't usually like populating Stack Overflow with noob questions, but I have to admit I don't know what to do.
Thanks in advance!
Your code depends on libraries. You have successfully added the libraries in the classpath to compile your code, and have thus successfully created an executable jar files containing your classes. But these classes depend on libraries to run. And when running the jar file, you don't secify anywhere that Java should look for classes in libraries in addition to your jar file.
See Generate manifest class-path from <classpath> in Ant for how to add a Class-Path entry to the manifest of your executbale jar file. Beware: the paths of the libraries must be relatie to the path of the jar.
Ant is a bit outdated. If I were you, I'd try using gradle, which has a neat application plugin doing all that for you, and much more.
I finally managed to solve this problem that I was struggling with by the help from a good friend of mine. There are two tricky points:
First, when specifying the "Main-Class" in the manifest section, just use the following format "packageName.className", and there is no need to specify the "build" or "source" folders!
Second, try and zip all the jar files that your "jar" file is going to depend on using "zipgroupfileset"
This image shows how I have commented out the "Class-Path" attribute that I would use for addressing the dependencies of the project, and have replaced it with the "zipgroupfileset". I hope that help you as well.
Small thing that can cause this issue is using uppercase letters in file extension. For example you can't use MainGameLoop.Java, The correct extension should be all lowercase like below.
MainGameLoop.java
Please check that first.

Eclipse: Ant script to export User Library/Libraries for project

I am new to Java programming. I initially started with NetBeans but have moved to Eclipse given the advice from a friend.
In NetBeans, a pre-written ant build script for the project would generate a Project.jar file and place all required libraries/jars in a lib/ folder.
However, in Eclipse it appears that I need to write my own ant script. I have written a few lines to generate the jar file:
<target name="compile">
<mkdir dir="${build.dir}"/>
<javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="jars" debug="on"/>
</target>
How do I write a command to copy all of the jars in my User Library to a ${build.dir}/lib/ folder?
Thanks.
Use the copy task
like so, with the appropriate include or exclude pattern
<copy todir="${build.dir}/lib/">
<fileset dir="src_dir">
<include name="**/*.jar"/>
</fileset>
</copy>
<copy todir="${build.dir}/lib/">
<fileset dir="src_dir" excludes="**/*.java"/>
</copy>
If you are new to Java take the chance to have a look at maven. It is a build tool like ant with a lot of predefined 'goals' and a fully developed dependency (to other libraries) handling. You will find a eclipse plugin which will be very useful.
Maven projects have a special directory layout which is kind of best practise and helpful for beginners. If you work on a maven project you can just use the command
mvn dependency:copy-dependencies
as a console command (or eclipse run configuration) to copy your project dependencies (libraries) to the <project>\target\dependency directory.
I recommend to use ant4eclipse library for ant based eclipse projects. When you use it, you can access eclipse workspace/project settings, and can iterate tought eclipse project class path in ant.
See the example code bellow:
<path id="ant.classpath">
<fileset dir="${lib.dir}/ant4eclipse">
<include name="*.jar" />
</fileset>
<taskdef resource="net/sf/antcontrib/antlib.xml" />
<taskdef resource="net/sf/ant4eclipse/antlib.xml" />
<targetPlatform
<target name="copy_jars">
<getEclipseClasspath workspace="${basedir}/.."
projectname="TestProject"
targetPlatformLocation="c:/eclipse"
property="classpath"
relative="false"
runtime="true"
pathseparator="#" />
<!-- iterate over all classpath entries -->
<foreach list="${classpath}" delimiter="#"
target="copy_jar_file" param="classpath.entry" />
</target>
<target name="copy_jar_file">
<!-- check if current is a .jar-file ... -->
<if>
<isfileselected file="${classpath.entry}">
<filename name="**/*.jar" />
</isfileselected>
<then>
<!-- copy the jar file to a destination directory -->
<copy file="${classpath.entry}" tofile="${dest.dir}"/>
</then>
</if>
</target>
If you would like to use user libraries, you can define it by userlibraries command.

How can I build my jar file so that users who use the library will be able to see the javadoc in Eclipse

I'm working on a small library for our in-company use, and have been heavily documenting it. Now I'm building my jar with the following code:
<project name="commonutils" default="compile" basedir=".">
<property name="src" location="src" />
<property name="build" location="buildDirecotry" />
<target name="compile">
<delete file="${ant.project.name}.jar" />
<mkdir dir="${build}"/>
<javac srcdir="${src}" destdir="${build}" debug="on" target="1.5">
<classpath>
<pathelement location="lib/build/server.zip" />
<pathelement path="${java.class.path}/"/>
</classpath>
</javac>
<jar basedir="${build}" destfile="${ant.project.name}.jar" />
<delete dir="${build}" />
</target>
</project>
Which works fine, it builds my jar file with all the src files in it, but when I include the jar file in another project I no-longer have any of my javadoc comments. Using JDDecompiler I cannot see the comments in the class file, although I'm not sure if its the java compiler that's stripping them or JD.
My question is: How can I build my jar file so that users who use the library will be able to see the javadoc in Eclipse.
If you include the source files in the jar (each class and java file in the same package-directory) it should work.
<target name="jar.noCompile.src">
<jar destfile="${ant.project.name}.jar">
<fileset dir="${build}"/>
<fileset dir="${src}" includes="**/*.java"/>
</jar>
</target>
AFAIK the documentation is an Eclipse feature. You have to configure it manually. In your build generate the documentation (usually into folder 'javadoc') and package it with the JAR. Once someone wants to use your library, he/she has to go into Java Build Path select libraries, add yours, click next to it to open the tree node and then double click on Javadoc location to configure it.

How can include multiple jars in the classpath using ant?

I have a bunch of .java files in a "src" folder that depend on three jars in a "lib" folder. I have the following build.xml file:
<?xml version="1.0"?>
<project name="MyProj" basedir=".">
<property name="src" value="src"/>
<property name="build" value="build"/>
<property name="lib" value="lib"/>
<path id="master-classpath">
<fileset dir="${lib}">
<include name="activemq-all-5.1-SNAPSHOT.jar"/>
<include name="geronimo-jms_1.1_spec-1.1.1.jar"/>
<include name="activemq-core-5.3.0.jar"/>
</fileset>
</path>
<javac destdir="${build}">
<src path="${src}"/>
<classpath refid="master-classpath"/>
</javac>
</project>
This compiles fine, but when I try and run I get
"java.lang.NoClassDefFoundError:
javax/jms/Destination"
This program runs and compiles fine when I include the jars in the buildpath using Eclipse, though.
EDIT: So I copied the jars into the folder that has the compiled classes. The class with the main method is NDriver.class. When I try:
java -classpath
./geronimo-jms_1.1_spec-1.1.1.jar:./activemq-core-5.3.0.jar:./activemq-all-5.1-SNAPSHOT.jar
NDriver
This gives:
Exception in thread "main"
java.lang.NoClassDefFoundError:
NDriver
I'd appreciate any help.
You need to put the jars used at compile time on the classpath when running the application. Sadly, you didn't provide any detail on how you are actually running it so its hard to provide more guidance.
UPDATE: The directory containing the compiled classes needs to be added to the classpath too. If you launch java from the directory containing the compiled classes, then you can use . to designate the current directory. Add it to the classpath as shown below to tell java to look for classes there too (I've added . right after activemq-all-5.1-SNAPSHOT.jar):
java -classpath ./geronimo-jms_1.1_spec-1.1.1.jar:./activemq-core-5.3.0.jar:./activemq-all-5.1-SNAPSHOT.jar:. NDriver
One way (slightly different variables than yours)
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
<manifestclasspath property="manifest.classpath" jarfile="${jarfile}">
<classpath refid="classpath"/>
</manifestclasspath>
<target name="jar" depends="compile" description="create the jar">
<jar destfile="${jarfile}" basedir="${build.dir}">
<manifest>
<attribute name="Manifest-Version" value="${manifest-version}"/>
<attribute name="Created-By" value="${ant.java.version}"/>
<attribute name="Main-Class" value="${main-class}"/>
<attribute name="Class-Path" value="${manifest.classpath}"/>
</manifest>
</jar>
</target>
Of course here I'm assuming that you are creating a jar and running it (including the classpath there). Another option would be to have a run target which use the <java> tag and explicitly use the classpath there.
Are the library jars included in the classpath when you run the program? Eclipse automatically add these, but you need to specifying them when you run the program from the command line.
From my experience it seems Eclipse will often include classes and jars in the classpath without explicitly using the classpath declaration. Indeed it can sometimes be quite hard to remove classes from Eclipse's build (they have to be deleted or clean'ed).

Categories

Resources