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