REST with Spring 3

In this article we will learn to create a basic REST service (hello world) using Spring 3, Logback, Maven, Tomcat, and Eclipse.

I have already written about Hello World with Spring 3 MVCHandling Forms with Spring 3 MVCJUnit, Logback, Maven with Spring 3Handling Form Validation with Spring 3 MVCIntroducing Spring Integration and Spring Integration with Gateways. This is an extension of the same series of Spring 3. It is not necessary, but it might be helpful for the audience to have a cursory glance at previous articles.

REST is best explained to a layman at How I Explained REST to My Wife. Clear, concise, conversational and informational. That's how articles ought to be. Easier said than done. For the geeky ones, here is the link to the PhD thesis by Roy, where he introduced the term REST. Google on if you want to know about the rest of REST :)

Spring 3 has excellent support, should you chose to go the REST way in your architecture. I highly recommend this article and the official documentation provided by SpringSource. Also there are some excellent material by IBM.

With that context setting done, let's roll up our sleeves and fire up the editor. We will learn as we code. To begin with, we will create a basic web application using Maven archetypes. Maven Tips is worth a quick read at this point, if you are not hands on with Maven.

File: \MavenCommands.bat

ECHO OFF 

REM =============================
REM Set the env. variables. 
REM =============================
SET PATH=%PATH%;C:\ProgramFiles\apache-maven-3.0.4\bin;
SET JAVA_HOME=C:\ProgramFiles\Java\jdk1.7.0
 
REM =============================
REM Standalone java web application. 
REM =============================
call mvn archetype:generate ^
    -DarchetypeArtifactId=maven-archetype-webapp ^
    -DinteractiveMode=false ^
    -DgroupId=foo.bar.spring3 ^
    -DartifactId=rest001

pause

If you run this batch file, it will give you a nice and functional web site, with correct folder structure, ready to be deployed on any of the many servlet containers (Tomcat 7 in my case), without writing a single line of code at all. If that alone does not make you check Maven website, I don't know what will (assuming of course that you don't use Maven already).

At this point I generally import it into Eclipse (I use STS and can honestly recommend it. Although I have to admit I am using 2.9.1 version for the current article and not the latest one, because that was being a bit twitchy. I am hoping this is temporary). If you are using standard Eclipse, consider investing time in getting m2e configured (with STS you get that pre-configured). I don't recall when was the last time I had used the standard build icon of Eclipse. My fingers type Alt+Shift+X, M  by muscle memory now, and I love m2e for making this seamless.

The next thing is to use Maven to get all requisite libraries.

File: \rest001\pom.xml

<!-- Unit test. -->                                     
<dependency>                                            
 <groupId>junit</groupId>                            
 <artifactId>junit</artifactId>                      
 <version>4.10</version>                             
 <scope>test</scope>                                 
</dependency>                                           
                                                        
<!-- Logging -->                                        
<dependency>                                            
 <groupId>ch.qos.logback</groupId>                   
 <artifactId>logback-classic</artifactId>            
 <version>1.0.6</version>                            
</dependency>                                           
                                                        
<!-- A bridge between commons logging and slf4j.        
It is required for Spring classes. -->                  
<dependency>                                            
 <groupId>org.slf4j</groupId>                        
 <artifactId>jcl-over-slf4j</artifactId>             
 <version>1.6.1</version>                            
 <scope>runtime</scope>                              
</dependency>                                           
                                                        
<!-- Spring MVC, without standard commons logging -->                                     
<dependency>                                            
 <groupId>org.springframework</groupId>              
 <artifactId>spring-webmvc</artifactId>              
 <version>3.1.3.RELEASE</version>                    
 <exclusions>                                        
  <exclusion>                                     
   <groupId>commons-logging</groupId>          
   <artifactId>commons-logging</artifactId>    
  </exclusion>                                    
 </exclusions>                                       
</dependency>                                           
                                                        
<!-- JSTL -->                                           
<dependency>                                            
 <groupId>javax.servlet</groupId>                    
 <artifactId>jstl</artifactId>                       
 <version>1.2</version>                              
</dependency>

I have put brief remarks on each of those dependencies. Hopefully it will be enough for you guess what they are for. If not, don't worry, we will find out soon.

Our next job is to configure our web application to handover all incoming requests to Spring (FrontController is it?). This is done quite easily.

File: \rest001\src\main\webapp\WEB-INF\web.xml

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">

 <!-- Register Spring servlet. -->
 <servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>
   org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <!-- Send all requests to Spring -->
 <servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 
</web-app>

A couple of thing to note here.

Note 1: The header and the web-app section of this file is much beefier than the standard web.xml that Maven had created. You need this for JSTL to work. We will use JSTL later in the article.

Note 2: It is tempting to put the URL pattern as * instead of /. It works, but only till you get to the point of view resolution. At that point it refuses to find the view even with correct path. I just spent some humiliating amount of time chasing this issue. Avoid it.

Now, we have already asked the website to route all incoming requests to Spring servlet. We need to now configure the Spring servlet. Again, xml is the answer, although this one is going to be a bit chunky. Don't be afraid of the xml. I will explain that in plain english immediately after.

File: rest001\src\main\webapp\WEB-INF\spring-servlet.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" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

 <!-- Enables automatic scan for the class that has Spring annotations -->
 <context:component-scan base-package="foo.bar.controllers" />

 <!--To enable @RequestMapping process on type level -->
 <bean
  class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

 <!--To enable @RequestMapping process on method level -->
 <bean
  class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

 <!-- Content negotiation. -->
 <bean
  class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
  <property name="mediaTypes">
   <map>
    <entry key="xml" value="application/xml" />
    <entry key="html" value="text/html" />
   </map>
  </property>
  <property name="viewResolvers">
   <list>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
    <bean
     class="org.springframework.web.servlet.view.InternalResourceViewResolver">
     <property name="viewClass"
      value="org.springframework.web.servlet.view.JstlView" />
     <property name="prefix" value="/WEB-INF/views/" />
     <property name="suffix" value=".jsp" />
    </bean>
   </list>
  </property>
 </bean>


</beans>


In plain english, this is what we have done. We have told Spring to look for Controllers in the package foo.bar.controllers. We have also said that the requests to the website, depending upon their format, could be hooked up with methods, wherein we will write code to handle those requests. Finally, we have in a oblique way (under the content negotiation bit) said that the views i.e. the stuff that shows up in browser (or other screens) should be served by jsp pages under WEB-INF/views directory.

Now we are at final piece of configuration. We need to tell logback, which we are going to use for logging (I am a bit of stickler when it comes to logging. You might want to read Log don't SOP, Logging revisited) what all to log and how.

File: \rest001\src\main\resources\logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
   <pattern>%d %5p | %t | %-20logger{20} | %m %n</pattern>
  </encoder>
 </appender>

 <!-- Turn off 3rd party logging -->
 <logger name="org.springframework" level="OFF" />

 <root>
  <level value="DEBUG" />
  <appender-ref ref="CONSOLE" />
 </root>
</configuration>

Now we are all set to write a piece of code that will handle a REST style URI and respond to it. If you are hands on (even occasional coder) you will find that it does not take more than 10 - 15 minutes to have this bit up and running.

It is worth reflecting on this fact. Starting from an empty directory, to having all the bells and whistles in place to start writing a REST style webservice, complete with support for continuous integration, support for unit / integration testing, support for code quality software like Sonar, support for industrial strength logging like logback is not a trivial bit. Not too long back - say a decade back - it would take weeks to get this much framework configured, if not months. One can not help but appreciate the kind of efficiency that tools like Spring, JUnit, Logback, Eclipse etc have brought in to IT industry. And it also follows that hands on knowledge of the tools in market is that much crucial. It might be the difference between the success and failure of a project.

Anyway, rant aside, let's write the final pieces. A class.method() to handle the request.

File: \rest001\src\main\java\foo\bar\controllers\HelloWorld.java

@Controller
public class HelloWorld {
 private final static Logger logger = LoggerFactory
   .getLogger(HelloWorld.class);

 @RequestMapping(value="/greet/{name}", method = RequestMethod.GET)
 public String greet(@PathVariable String name, ModelMap model){
  model.addAttribute("name", name); 
  logger.debug("Greeting {}", name);
  return "greet"; 
 }
}

And a view to deliver the result.

File: \rest001\src\main\webapp\WEB-INF\views\greet.jsp

<html>
<head>
<title>REST with Spring 3</title>
</head>
<body>
Hello, ${name}. 
</body>
</html>

All done. Compile this and put it in Tomcat. You should be able to hit http://localhost:<portnumber>/rest001/greet/partha and have the web page greet you back. You will notice that there are no .jsp or .html or .do with the URL. These are the REST style URLs. In later articles we will look at more details, run some tests and look at some alternate ways for achieving REST style URLs.

With this I will close this article, but I will leave you are parting question to ponder upon. Are these the baby steps towards internet of things?

If you want to get in touch, you can look me up at Linkedin or Google+.

No comments: