creating an uber jar with spring dependencies - spring

I'm trying to create an application über jar, but running into an issue due to a dependency on the spring framework. In particular, the namespaces for the xml schemas are problematic. You get the infamous NamespaceHandler problem:
Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/c]
For creating (simple) uber jars, Creating a bundle jar with ant, but this doesn't work if you have spring dependencies due to the fact that the spring jars have files such as spring.handlers, spring.schemas and spring.tooling in the META-INF directories of many of their jar files. The namespace resolution is dependent, I believe, on these files.
The über jar seems to somehow contain all necessary files, but I'm guessing the runtime is seeing only one.
For example, a jar -tf of my uber jar shows (in part)
META-INF/spring.handlers
META-INF/spring.schemas
META-INF/spring.tooling
META-INF/license.txt
META-INF/notice.txt
META-INF/spring.factories
META-INF/spring.handlers
META-INF/spring.schemas
META-INF/spring.tooling
META-INF/license.txt
META-INF/notice.txt
META-INF/license.txt
META-INF/notice.txt
META-INF/spring.handlers
META-INF/spring.schemas
META-INF/spring.tooling
META-INF/license.txt
META-INF/notice.txt
META-INF/license.txt
So: question.. is there a way to create an uber-jar that has the spring jars bundled inside? Do I need to merge the META-INF files? Anyone have experience doing file merger with ant builds?

Well.. this was a pain.
<target name="make-bundle" depends="jar">
<!-- retrieve the dependencies -->
<ivy:retrieve conf="deploy" pattern="${dist.dir}/dependencies/[artifact].[ext]"/>
<delete dir="${dist.dir}/dependencies/uber" failonerror="false" />
<mkdir dir="${dist.dir}/dependencies/uber"/>
<!-- iterate over the dependencies -->
<for param="file">
<path>
<fileset dir="${dist.dir}/dependencies">
<include name="**/*.jar"/>
</fileset>
</path>
<sequential>
<propertyregex override="yes"
property="jarname" input="#{file}"
regexp=".*/([^/]*)\.jar" replace="\1"/>
<!-- put the spring.* jars into special sub-directories -->
<mkdir dir="${dist.dir}/dependencies/${jarname}"/>
<unzip dest="${dist.dir}/dependencies/${jarname}" src="#{file}">
<patternset>
<include name="**/META-INF/spring.*"/>
</patternset>
</unzip>
<!-- put everything else in the 'uber' directory -->
<unzip dest="${dist.dir}/dependencies/uber" src="#{file}">
<patternset>
<exclude name="**/META-INF/spring.*"/>
</patternset>
</unzip>
</sequential>
</for>
<!-- build the concatenated spring.* files -->
<mkdir dir="${dist.dir}/dependencies/META-INF"/>
<concat destfile="${dist.dir}/dependencies/META-INF/spring.handlers" fixlastline="true">
<fileset dir="${dist.dir}/dependencies/" includes="*/*/spring.handlers"/>
</concat>
<concat destfile="${dist.dir}/dependencies/META-INF/spring.schemas" fixlastline="true">
<fileset dir="${dist.dir}/dependencies/" includes="*/*/spring.schemas"/>
</concat>
<concat destfile="${dist.dir}/dependencies/META-INF/spring.tooling" fixlastline="true">
<fileset dir="${dist.dir}/dependencies/" includes="*/*/spring.tooling"/>
</concat>
<concat destfile="${dist.dir}/dependencies/META-INF/spring.factories" fixlastline="true">
<fileset dir="${dist.dir}/dependencies/" includes="*/*/spring.factories"/>
</concat>
<!-- build the uber jar! -->
<delete file="${dist.dir}/myproject-with-dependencies.jar" failonerror="false"/>
<jar destfile="${dist.dir}/myproject-with-dependencies.jar">
<!-- all dependency files except spring.* -->
<fileset dir="${dist.dir}/dependencies/uber"/>
<!-- the spring.* files -->
<fileset dir="${dist.dir}/dependencies/" includes="META-INF/*"/>
<!-- my project's classes & etc -->
<zipgroupfileset dir="${dist.dir}" includes="myproject.jar" />
<manifest>
<attribute name="Main-Class" value="${main.class}"/>
</manifest>
</jar>
</target>

Related

Spring Boot uber jar packaging classes to root instead of BOOT-INF/classes

Hi Spring Boot Experts -
I am trying to create a spring boot uber jar that needs to be deployed to a apache storm cluster. But, the catch is that Storm is expecting all the class files in the root of the jar while the packaged app files are under "BOOT-INF/classes" when packaged using the "spring-boot-maven-plugin".
Is there a way I can have my app classes packaged directly under the root instead of "BOOT-INF/classes"?
I tried using the "maven-assembly-plugin" with the "spring-boot-maven-plugin" as shown below which creates the Uber jar with all the class files from the dependency jars packaged at the root of the uber jar, but the app classes are still at BOOT-INF/classes.
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.apache.storm</groupId>
<artifactId>storm-core</artifactId>
</exclude>
</excludes>
<requiresUnpack>
<dependency>
<groupId>com.myorg</groupId>
<artifactId>my-app-artifact</artifactId> <!-- This does not help! :( -->
</dependency>
</requiresUnpack>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
So, for my future self or for anyone who is trying to find an answer for a similar question. Here are the different things that I realized during my research for this -
Storm wants an executable java jar file
Spring Boot provides a custom jar packaging. While it confirms with java jar packaging, Spring Boot loads the classes from the BOOT-INF/classes
So, to make a Spring Boot jar work on the storm cluster while behaving as Spring Boot - we would need to create a copy of all the classes from BOOT-INF/classes to the root of the jar file.
Is this possible? and the answer is yes.
Using the approach describe here, I was able to create a Spring Boot jar with the BOOT-INF/classes copied to the root of the Spring Boot jar. This approach requires ant build.xml, ivy settings and an ivy.xml as shown below. (disclaimer: config tested only till packaging on not on the storm cluster)
Since we are able to create a Spring Boot Jar hacked with classes at the root -
Should we do it? NO.
Here are the reasons -
Spring strongly advises not taking this approach to not end up with unwanted class overwrite and class versioning issues for classes with same names across jar files and with different versions.
Spring Boot Jar packaging is not a format intended for using as a dependency jar. Read the first line here. Hence for dependency use cases, you need to stick with your plain old java modules. Spring Boot is for more of standalone executables or for deployment on containers like tomcat.
Good luck!
build.xml
<project
xmlns:ivy="antlib:org.apache.ivy.ant"
xmlns:spring-boot="antlib:org.springframework.boot.ant"
name="spring-boot-sample-ant"
default="build">
<description>
Sample ANT build script for a Spring Boot executable JAR project. Uses ivy for
dependency management and spring-boot-antlib for additional tasks. Run with
'$ ant -lib ivy-2.2.jar spring-boot-antlib.jar' (substitute the location of your
actual jars). Run with '$ java -jar target/*.jar'.
</description>
<property name="spring-boot.version" value="1.4.2.RELEASE" />
<property name="lib.dir" location="${basedir}/target/lib" />
<property name="start-class" value="com.my.main.class" />
<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve pattern="${lib.dir}/[conf]/[artifact]-[type]-[revision].[ext]" />
</target>
<target name="classpaths" depends="resolve">
<path id="compile.classpath">
<fileset dir="${lib.dir}/compile" includes="*.jar" />
</path>
</target>
<target name="init" depends="classpaths">
<mkdir dir="target/classes" />
</target>
<target name="compile" depends="init" description="compile">
<javac srcdir="src/main/java" destdir="target/classes" classpathref="compile.classpath" />
</target>
<target name="clean" description="cleans all created files/dirs">
<delete dir="target" />
</target>
<target name="build" depends="compile">
<spring-boot:exejar destfile="target/${ant.project.name}-${spring-boot.version}.jar" classes="target/classes">
<spring-boot:lib>
<fileset dir="${lib.dir}/runtime" />
</spring-boot:lib>
</spring-boot:exejar>
</target>
<target name="unjar_dependencies" depends="compile">
<unzip dest="target/classes">
<fileset dir="${lib.dir}/compile">
<include name="my-app-common-0.1-SNAPSHOT.jar" />
</fileset>
</unzip>
</target>
<!-- Manual equivalent of the build target -->
<target name="manual" depends="compile, unjar_dependencies">
<jar destfile="target/manual/${ant.project.name}-${spring-boot.version}.jar" compress="false">
<mappedresources>
<fileset dir="target/classes" />
<globmapper from="*" to="BOOT-INF/classes/*"/>
</mappedresources>
<mappedresources> <!-- **** this mapped resources block does what I was looking for **** -->
<fileset dir="target/classes" />
<globmapper from="*" to="/*"/>
</mappedresources>
<mappedresources>
<fileset dir="src/main/resources" erroronmissingdir="false"/>
<globmapper from="*" to="BOOT-INF/classes/*"/>
</mappedresources>
<mappedresources>
<fileset dir="${lib.dir}/runtime" />
<globmapper from="*" to="BOOT-INF/lib/*"/>
</mappedresources>
<zipfileset src="${lib.dir}/loader/spring-boot-loader-jar-${spring-boot.version}.jar" />
<manifest>
<attribute name="Main-Class" value="org.springframework.boot.loader.JarLauncher" />
<attribute name="Start-Class" value="${start-class}" />
</manifest>
</jar>
</target>
</project>
ivysettings.xml
<ivysettings>
<settings defaultResolver="chain" />
<resolvers>
<chain name="chain" returnFirst="true">
<!-- NOTE: You should declare only repositories that you need here -->
<filesystem name="local" local="true" m2compatible="true">
<artifact pattern="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision].[ext]" />
<ivy pattern="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision].pom" />
</filesystem>
<ibiblio name="ibiblio" m2compatible="true" />
<ibiblio name="spring-milestones" m2compatible="true" root="http://repo.spring.io/release" />
<ibiblio name="spring-milestones" m2compatible="true" root="http://repo.spring.io/milestone" />
<ibiblio name="spring-snapshots" m2compatible="true" root="http://repo.spring.io/snapshot" />
</chain>
</resolvers>
</ivysettings>
ivy.xml
<ivy-module version="2.0">
<info organisation="org.springframework.boot" module="spring-boot-sample-ant" />
<configurations>
<conf name="compile" description="everything needed to compile this module" />
<conf name="runtime" extends="compile" description="everything needed to run this module" />
<conf name="loader" description="Spring Boot loader used when manually building an executable archive" />
</configurations>
<dependencies>
<dependency org="org.springframework.boot" name="spring-boot-starter" rev="${spring-boot.version}" conf="compile">
<exclude org="ch.qos.logback" name="logback-classic"/>
</dependency>
<dependency org="org.springframework.boot" name="spring-boot-loader" rev="${spring-boot.version}" conf="loader->default" />
<dependency org="org.apache.storm" name="storm-core" rev="1.0.2">
<exclude org="org.apache.logging.log4j" name="log4j-slf4j-impl"/>
<exclude org="org.apache.logging.log4j" name="log4j-core"/>
</dependency>
<dependency org="com.mycompany" name="app-common" rev="0.1-SNAPSHOT"/>
<dependency org="org.apache.storm" name="storm-kafka" rev="1.0.2"/>
<dependency org="org.apache.kafka" name="kafka_2.10" rev="0.10.1.0"/>
<dependency org="org.apache.kafka" name="kafka_2.10" rev="0.10.1.0"/>
<dependency org="org.apache.httpcomponents" name="httpcomponents-client" rev="4.5.2"/>
<dependency org="org.eclipse.paho" name="org.eclipse.paho.client.mqttv3" rev="1.1.0"/>
<dependency org="com.amazonaws" name="aws-java-sdk-s3" rev="1.11.53"/>
<dependency org="com.jcraft" name="jsch" rev="0.1.54"/>
<dependency org="io.netty" name="netty-handler" rev="3.7.0.Final"/>
</dependencies>
</ivy-module>
Is there a way I can have my app classes packaged directly under the root instead of "BOOT-INF/classes"?
Yes, you just need to use Spring Boot 1.3. Back to maven... in your pom.xml if you declare your parent like this:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
then your classes (and other files) will be placed at the root level. This is the "old way" for spring boot.
In version 1.4 they changed the spring boot jar structure to use the BOOT-INF directory. So, if you use <version>1.4.1.RELEASE</version> for example, then your classes will be under BOOT-INF/classes. An undesirable side effect is that your configuration files (e.g., application.properties, application-myprofile.properties, etc.) will also be under BOOT-INF/classes, even though they are not Java classes.

ant war plugin trying to exclude folders

Hi I am using ant war plugin and trying to exclude a folder from teh war. however this is not being excluded.
Here is the structure
XYZ
src
main
webapp
web-inf
web.xml
common
api
a.js
b.js
My build.xml for target = production looks like this, i am try to exclude teh common folder. However I see that it is not excluding it at all
<target name="production" depends="moveresources"
description="Building the Production Directory Content">
<echo message="Creating -> ${workingDirectory}"/>
<war destfile="${workingDirectory}/horizonadmin.war" webxml="${srcDirectory}/WEB-INF/web.xml">
<fileset dir="${webappsrc}"/>
<lib dir="${srcDirectory}/WEB-INF/lib"/>
<classes dir="${srcDirectory}/WEB-INF/classes"/>
<exclude name="common/api/*.js/>
</war>
<echo message="Done Creating -> ${workingDirectory}"/>
</target>
in my pom.xml i have referenced the folder as
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>production</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<property name="unpackfolder" value="${unpack.folder}"/>
<property name="webappsrc" value="src/main/webapp"/>
<property name="srcDirectory" value="${project.basedir}/target"/>
<property name="workingDirectory" value="${PUBLISH_DIR}/production"/>
<ant antfile="${project.basedir}/build.xml" target="production"/>
</target>
</configuration>
</execution>
How can i exclude these js files in my war file? for production mode . I plan to exclude these js files and include a minified js file. any pointers
Include the <exclude> in the <fileset>:
<fileset dir="${webappsrc}">
<exclude name="common/api/*.js" />
</fileset>
The exclude you add directly inside the task not nested into one of the filesets applies to the implicit fileset the task forms when you use the basedir attribute. Without any basedir it doesn't do anything. This may not be mentioned inside the war doc, but inside the linked jar page.
You have to place the exclude inside the fileset that pulls in the files you don't want. lib and classes are filesets with different names (they really only are zipfilesets with hard-coded prefix attributes).

Maven unpack dependencies from Ant plugin mojo

I have a Maven ant plugin that bundles up a library of Ant tasks. One of them has a lot of CI tasks.
I have the plugin working and can hit the task by running
mvn -U ci:options
This brings up a menu for the different operations.
The issue i'm having is that I need to resolve dependencies in the pom before the task is executed.
From reading up I would have thought that I could add
<execution>
<goal>dependency:unpack-dependencies</goal>
</execution>
To the pluginMetaData xml file that defines the mojo, though this doesn't seem to do anything
ci.mojos.xml
<pluginMetadata xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/plugin-metadata-1.0.0.xsd">
<mojos>
<mojo>
<!-- target name to call in ant script -->
<call>run</call>
<!-- mojo goal name -->
<goal>options</goal>
<execution>
<goal>dependency:unpack-dependencies</goal>
</execution>
<parameters>
<parameter>
<name>artifactId</name>
<property>artifactId</property>
<required>true</required>
<readonly>true</readonly>
<type>java.lang.String</type>
<defaultValue>${project.artifactId}</defaultValue>
<description>Project Artifact Id</description>
</parameter>
....
ci.build.xml
<property name="project.home" location="."/>
<property name="target.dir" value="${project.home}/target"/>
<property name="build.dir" value="${target.dir}/build"/>
<property name="dependency.dir" value="${target.dir}/dependency"/>
<!-- Add contrib to the classpath -->
<taskdef resource="net/sf/antcontrib/antlib.xml"/>
<!-- Include ant utils from the shared resource -->
<include file="${dependency.dir}/shared_ant/build.xml"/>
<!-- Continuous Integration Options -->
<target name="run" description="Continuous Integration Options">
<ci.options/>
</target>
</project>
Any help greatly appreciated.
James

batch updating manifest.mf for existing jars using maven

I would like to add a custom key/value pair to the MANIFEST.MF of several existing jar files in my war project (those jars are not the project dependencies).
I already can pack/repack those jars using an ant task.
I read about "manifest" task, how can I apply that task to a fileset (if there is a way)? Thanks in advance.
This is my first answer at StackOverflow. Hope it suits you :)
I've done it like this:
<target name="xxx.modifyManifests">
<echo message="Modifying jar manifests to add trusted-library" />
<apply executable="jar">
<arg value="umf" />
<arg line="${xxx.resources}/manifest/custom_manifest.mf" />
<srcfile />
<fileset dir="${xxx.target}/applets" includes="*.jar" />
</apply>
</target>
The call is a simple one using maven-antrun-plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>xxx.modifyManifests</id>
<phase>compile</phase>
<configuration>
<target>
<property environment="windows" />
<property name="xxx.resources"
value="${project.build.directory}/../src/main/resources" />
<property name="xxx.target"
value="${project.build.directory}/${project.artifactId}-${project.version}" />
<ant antfile="${basedir}/build.xml">
<target name="xxx.modifyManifests" />
</ant>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
And my custom_manifest.mf is like this:
Trusted-Only: true
Trusted-Library: true

Ant can't write to directory

i cannot seem to install my spring mvc app on tomcat 7 due to this error:
BUILD FAILED
C:\Users\xxxx\Work\My side projects\FreedomSpring\build.xml:106: java.io.FileNotFoundException: C:\spring (Access is denied)
I have disabled UAC and tried running Spring in admin mode and diddnt work.
here is my build.xml file
<?xml version="1.0"?>
<project name="springapp" basedir="." default="usage">
<property file="build.properties"/>
<property name="src.dir" value="src"/>
<property name="web.dir" value="war"/>
<property name="build.dir" value="${web.dir}/WEB-INF/classes"/>
<property name="name" value="FreedomSpring"/>
<path id="master-classpath">
<fileset dir="${web.dir}/WEB-INF/lib">
<include name="*.jar"/>
</fileset>
<!-- We need the servlet API classes: -->
<!-- * for Tomcat 5/6 use servlet-api.jar -->
<!-- * for other app servers - check the docs -->
<fileset dir="${appserver.lib}">
<include name="servlet*.jar"/>
</fileset>
<pathelement path="${build.dir}"/>
</path>
<target name="usage">
<echo message=""/>
<echo message="${name} build file"/>
<echo message="-----------------------------------"/>
<echo message=""/>
<echo message="Available targets are:"/>
<echo message=""/>
<echo message="build --> Build the application"/>
<echo message="deploy --> Deploy application as directory"/>
<echo message="deploywar --> Deploy application as a WAR file"/>
<echo message="install --> Install application in Tomcat"/>
<echo message="reload --> Reload application in Tomcat"/>
<echo message="start --> Start Tomcat application"/>
<echo message="stop --> Stop Tomcat application"/>
<echo message="list --> List Tomcat applications"/>
<echo message=""/>
</target>
<target name="build" description="Compile main source tree java files">
<mkdir dir="${build.dir}"/>
<javac destdir="${build.dir}" source="1.5" target="1.5" debug="true"
deprecation="false" optimize="false" failonerror="true">
<src path="${src.dir}"/>
<classpath refid="master-classpath"/>
</javac>
</target>
<target name="deploy" depends="build" description="Deploy application">
<copy todir="${deploy.path}/${name}" preservelastmodified="true">
<fileset dir="${web.dir}">
<include name="**/*.*"/>
</fileset>
</copy>
</target>
<target name="deploywar" depends="build" description="Deploy application as a WAR file">
<war destfile="${name}.war"
webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}">
<include name="**/*.*"/>
</fileset>
</war>
<copy todir="${deploy.path}" preservelastmodified="true">
<fileset dir=".">
<include name="*.war"/>
</fileset>
</copy>
</target>
<!-- ============================================================== -->
<!-- Tomcat tasks - remove these if you don't have Tomcat installed -->
<!-- ============================================================== -->
<path id="catalina-ant-classpath">
<!-- We need the Catalina jars for Tomcat -->
<!-- * for other app servers - check the docs -->
<fileset dir="${appserver.lib}">
<include name="catalina-ant.jar"/>
</fileset>
</path>
<taskdef name="install" classname="org.apache.catalina.ant.DeployTask">
<classpath refid="catalina-ant-classpath"/>
</taskdef>
<taskdef name="reload" classname="org.apache.catalina.ant.ReloadTask">
<classpath refid="catalina-ant-classpath"/>
</taskdef>
<taskdef name="list" classname="org.apache.catalina.ant.ListTask">
<classpath refid="catalina-ant-classpath"/>
</taskdef>
<taskdef name="start" classname="org.apache.catalina.ant.StartTask">
<classpath refid="catalina-ant-classpath"/>
</taskdef>
<taskdef name="stop" classname="org.apache.catalina.ant.StopTask">
<classpath refid="catalina-ant-classpath"/>
</taskdef>
<target name="install" description="Install application in Tomcat">
<echo message="deploy path = ${deploy.path}/${name}"/>
<install url="${tomcat.manager.url}"
username="${tomcat.manager.username}"
password="${tomcat.manager.password}"
path="${deploy.path}/${name}"
war="${deploy.path}/"/>
</target>
<target name="reload" description="Reload application in Tomcat">
<reload url="${tomcat.manager.url}"
username="${tomcat.manager.username}"
password="${tomcat.manager.password}"
path="/${name}"/>
</target>
<target name="start" description="Start Tomcat application">
<start url="${tomcat.manager.url}"
username="${tomcat.manager.username}"
password="${tomcat.manager.password}"
path="${deploy.path}/${name}"/>
</target>
<target name="stop" description="Stop Tomcat application">
<stop url="${tomcat.manager.url}"
username="${tomcat.manager.username}"
password="${tomcat.manager.password}"
path="${deploy.path}/${name}"/>
</target>
<target name="list" description="List Tomcat applications">
<list url="${tomcat.manager.url}"
username="${tomcat.manager.username}"
password="${tomcat.manager.password}"/>
</target>
<!-- End Tomcat tasks -->
</project>
Build properties:
# Ant properties for building the springapp
appserver.home=C:/Program Files/Apache Software Foundation/Tomcat 7.0
# for Tomcat 5 use $appserver.home}/server/lib
# for Tomcat 6 use $appserver.home}/lib
appserver.lib=${appserver.home}/lib
deploy.path=C:/spring
tomcat.manager.url=http://localhost:8080/manager
tomcat.manager.username=xxxxx
tomcat.manager.password=xxxxx
i notice that everytime i do a build, deply, deploywar and install, the c:/spring folder is always being set to read-only? i tried to manually change the permissions of that folder and just tried running the install ant target from my build and no joy. The build.xml succesfully creates the WAR file and the folder that contains the binary class files inside c:/spring but it cant istall it on tomcat7
Any suggestions?
im using windows 7 64bit by the way.
I am following this guide http://static.springsource.org/docs/Spring-MVC-step-by-step/part1.html
here is m web.xml if it helps:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>FreedomSpring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FreedomSpring</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
</web-app>
And here is my servlett.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<!-- the application context definition for the springapp DispatcherServlet -->
<bean name="/hello.htm" class="springapp.web.HelloController"/>
</beans>

Resources