Monday, July 2, 2012

Spring MVC and Velocity WebApp

This tutorial shows how to use Velocity templates with Spring MVC. Velocity is more flexible templating engine than JSP/JSTL standard templates. We will create a simple application that contains 2 pages: list and detail. These pages will have common layout.

Start with an empty Maven web application

We need to add Spring and Velocity dependencies to our pom.xml file:

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.1.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-tools</artifactId>
            <version>2.0</version>
        </dependency>

    </dependencies>



Web Application will be deployed to Jetty Web Container. Add the following plugin to plugins section of pom.xml:

  <plugin>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>maven-jetty-plugin</artifactId>
      <version>6.1.16</version>
      <configuration>
           <contextPath>/</contextPath>
           <scanTargets>
              <scanTarget>target/classes/</scanTarget>
           </scanTargets>
          <scanIntervalSeconds>5</scanIntervalSeconds>
      </configuration>
  </plugin>

To start the application call mvn jetty:run

The application will be available on http://127.0.0.1:8080/. When you open this address with a browser you will see just a Hello World message from index.jsp, indicating that empty application was successfully deployed.

Note: You may use any web container you like (e.g. Tomcat). I prefer Jetty because it is very fast and light-weight.


Dummy data model

Our application will be showing a list of Feeds with link to detail.

Entity Feed contains Id and Title.

package com.blogspot.vozis.springvelocity.data;

/**
 * Feed
 * @author sergej.sizov
 */
public class Feed {

    private Integer id;
    private String title;

    public Feed(Integer id, String title) {
        this.id = id;
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

}

FeedService is a provider of Feeds. It has 2 methods: getFeedById() and getFeeds().

package com.blogspot.vozis.springvelocity.data;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;

/**
 * Feed Service
 * @author sergej.sizov
 */
@Service
public class FeedService {

    private Map<Integer, Feed> storage = new HashMap<Integer, Feed>();

    public FeedService() {
        storage.put(1, new Feed(1, "Spring Tutorial"));
        storage.put(2, new Feed(2, "Velocity Tutorial"));
        storage.put(3, new Feed(3, "Java Tutorial"));
    }

    public Feed getFeedById(Integer id) {
        return storage.get(id);
    }

    public List<Feed> getFeeds() {
        List<Feed> list = new ArrayList<Feed>();
        list.addAll(storage.values());
        return list;
    }

}


Spring Context Configuration

Now it is time to configure Spring framework.

Create spring-context.xml file in WEB-INF and put the following lines into it:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">
    <context:component-scan base-package="com.blogspot.vozis.springvelocity.data" />

</beans>

You need to modify base-package parameter according to your package name.

Next, we need to register ContextListener that starts Spring Context on application start. Add the following lines to web.xml:

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener> 


Creating templates

Create new folder views in WEB-INF. This folder will contain Velocity templates. Application has 2 pages: list and detail, both having the same layout.

Let's create layout file layout.vm and put it into WEB-INF/views:

<html>
    <head>
        <title>Spring MVC and Velocity</title>
    </head>
    <body>
        <h1>Spring MVC and Velocity</h1>

        $screen_content

        <hr />
        Copyright &copy 2012 Sergej Sizov
    </body>
</html>

As you may see $screen_content variable contains the content of pages.

Now let's create list.vm and detail.vm pages:


<h2>List of Feeds</h2>
<ul>
    #foreach($feed in $feeds)
        <li><a href="/feed/${feed.id}">${feed.title}</a></li>
    #end
</ul>


<h2>Detail of Feed</h2>
<p>Id: ${feed.id}</p>
<p>Title: ${feed.title}</p>


Configuring Spring MVC to use Velocity templates

Create a file servlet-context.xml in WEB-INF and put the following lines into it:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <mvc:annotation-driven />

    <bean id="velocityConfig"
        class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
        <property name="resourceLoaderPath" value="/WEB-INF/views/" />
    </bean>

    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
        <property name="cache" value="true" />
        <property name="prefix" value="" />
        <property name="layoutUrl" value="layout.vm"/>
        <property name="suffix" value=".vm" />
    </bean>

    <context:component-scan base-package="com.blogspot.vozis.springvelocity.web" />

</beans>


Then register Spring Servlet to intercept all HTTP request. You need to add the following lines into web.xml:

    <servlet>
        <servlet-name>servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
   
    <servlet-mapping>
        <servlet-name>servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>


Programming the Controller

Create a new java class WebController:

package com.blogspot.vozis.springvelocity.web;

import com.blogspot.vozis.springvelocity.data.FeedService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Controller
 *
 * @author sergej.sizov
 */
@Controller
public class WebController {

    private FeedService feedService;

    @Autowired
    public WebController(FeedService feedService) {
        this.feedService = feedService;
    }

    @RequestMapping("/")
    public String list(ModelMap model) {
        model.put("feeds", feedService.getFeeds());
        return "list";
    }

    @RequestMapping("/feed/{id}")
    public String detail(@PathVariable(value = "id") Integer feedId, ModelMap model) {
        model.put("feed", feedService.getFeedById(feedId));
        return "detail";
    }
}



Spring Controller is annotated with @Controller. Spring MVC automatically scans for @Controllers and creates mapping to methods according to @RequestMapping URL pattern.

ModelMap map contains data to be passed to Velocity, the return parameter defines the template to be used (e.g. list, detail).


4 comments:

  1. Helpful tutorial.
    This is the only tutorial i found using a VelocityLayoutView along with Spring annotated controllers.
    Thanks

    ReplyDelete
  2. Thank you for this post. It was helpful for me.

    ReplyDelete
  3. Thank you sir,
    This post was very helpful to me.

    ReplyDelete