April 8, 2010

Spring 3 + maven2 + Google App Engine: Part 3 [Spring Setup]

This is a continuation of a multi-part series discussing how to get Spring 3 running on Google App Engine using maven.  This part discusses in more detail how to get Spring 3 MVC configured using annotations.

If you have ever had to setup a typical J2EE Web Application you remember (or perhaps have suppressed) the massive amounts of XML needed to configure your app.  Spring 2.5 took great steps towards reducing the use of XML by introducing annotation based configuration.  Annotation based configuration is probably the best achievement towards sustainable RAD.  For example, lets take your typical Spring web application.  You need a web.xml, dispatcher-servlet.xml, urlrewrite.xml (to have the greatest control of your URLs for SEO purposes), data-config xml files and a build.xml.  While you can not completely get rid of all of the XML, using annotations you can reduce the file size and best yet, remove dependencies to your business beans.

To start setting up your application with annotations, create a file called "dispatcher-servlet.xml" and place it in your WEB-INF folder.  This file will need to have several namespaces defined.

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

</beans>

After saving the skeleton file above, it is time to add the meat of the configuration. First, we will tell Spring to wire Controllers to your POJOs that represent the "C" (controller) of MVC. In order to do this, we will need to place our controllers inside of a package structure so Spring can scan the package looking for the @Controller annotation. For our example, we setup our controller to be under com.example.web.controllers. Where you used to have to wire in a bean that used the class name to guess the controller, now we add the annotation to the class and that's it!

Add this to your dispatcher-servlet.xml file:
<context:component-scan base-package="com.example.web.controllers" />

That's it! Now we have the basics for setting up your first controller. Let's build our first Controller.  Create a file called HelloController.java under /src/main/java/com/example/web/controllers
package com.example.web.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.ui.ModelMap;

@Controller
public class HelloController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String helloGet(ModelMap map) {
        // this is your model (in future post, I will show how to use the ModelAttribute and command objects)
        map.put("name", "mike!");
        // for now, this is where your "View" is
        return "hello";
    }
}

For now, we will go with just the basics. Save this file and let's create the "View" so we you can run your application.

Create a file named hello.jsp and place it in /src/main/webapp/WEB-INF/jsp/views folder.
<%@ page contentType="text/html; charset=UTF-8" contentEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<-- it is recommended to take the preceding 
four lines and place them in a file to include on 
the top of each page -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello Controller</title>
    </head>
    <body>
        <h1>Hello ${name}!</h1>
    </body>
</html>

Lastly we must hook up our dispatcher servlet (front-loading servlet) so we can handle web request. Open /src/main/webapp/WEB-INF/web.xml file and add in the following:
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/movie/*</url-pattern>
</servlet-mapping>

Now we're ready to run our app!
mvn clean gae:run

NOTE: If this is the first time you have run Google App Engine application using Maven2, then type the following to "unpack" Google App Engine
mvn gae:unpack


Navigate to http://localhost:8080/movie/hello


Now that you have have checked out your first controller, you can already see things that are static and probably be changed. For example, take a look at your "helloGet" method. The String being returned points directly to the view. Rather than hard-code the entire path, we can setup a ViewResolver so you only need a small part of the view name.

Open /src/main/webapp/WEB-INF/dispatcher-servlet.xml and add in the following:
<bean id="viewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver"
  p:prefix="/WEB-INF/jsp/views/" 
  p:suffix=".jsp" 
  p:viewClass="org.springframework.web.servlet.view.JstlView"
  />

Now in your HelloController.java file, simply return "hello" (see how the strings concatenated together create the entire path?


You now have the basics of Spring MVC setup and so therefore this concludes this portion of the series on how to get Spring and Maven2 running on Google App Engine

11 comments:

Anonymous said...

Hi, good post; very helpfull. I have it all up and running, but can't figure out have i can dynamiccaly file the modelattribute "name". For me only the text "Hello {name}!" is shown undisplay.

Could a how-to on this be added in this tutorial? Thanks.

Mike! said...

Hey,

It looks like I forgot to add in the map (or ModelMap) to the signature. I have updated the example above to include the new parameter.

Mike!

Anonymous said...

change "contentEncoding" to "pageEncoding" in hello.jsp to display "mike!"

Mike! said...

The content encoding shouldn't make a difference, I forgot to add the "name" to the model :)

Mike!

Anonymous said...

Hi,

Along with the ModelMap parameter it needs to be imported :)

import org.springframework.ui.ModelMap

Mike! said...

Good point...I added the ModelMap quickly. Usually a modern IDE, would handle the imports for you :-)

Thanks for keeping me on my toes and not letting me be lazy!

Sebastiano Armeli said...

Hi Mike!
Awesome post, integrating Spring, Maven and GAE is quite tricky.

I'm following your instructions steps-by-steps but I get "Problem accessing /WEB-INF/view/hello.jsp. Reason: NOT_FOUND" when I redirect the controller to the view.
The jsp is existing of course in the right folder and the spring configuration should be pretty straightforward. Is there anything special I need to enable on GAE to make it work?

Cheers,
Sebastiano

Mike! said...

Hey Sebastiano,

It looks like you need to create a folder called "jsp" in your /WEB-INF folder and then the /views folder inside of that...you can change where you want Spring to resolve the views from inside of your dispatcherServlet.xml file (look for the ViewResolver)

If I get a chance, I'll package up the whole project and upload it so everyone can get a sample. Most of what I've put in this blog is just snippits of the code and I have plans to discuss more advanced methods to persist data using Objectify. I'm expecting to have that up in a week or two, so subscribe to my blog and hopefully that'll come out soon!!

Mike!

Mike! said...

Hey Sebastiano,

I just noticed that in the method at the top, I had the full path to the "view", and you only need the file name (without suffix) because the InternalViewResolver will find the file based based on the inputs you have setup in your dispatcherServlet.xml file

Sorry for the confusion, I typically strip out un-necessary pieces so I can emphasize my discussion points, but looks like I took out and changed too much :)

Mike!

Sebastiano Armeli said...

Hi Mike,

thanks for your help. I tried to chuck hello.jsp inside jsp/views and I updated the Spring config file. I still get "Problem accessing /WEB-INF/jsp/views/hello.jsp. Reason:". Any idea? It seems like the folder is not accessible or protected..really weird..

Yeah it'll be good if you can upload the whole project when you get a chance, anyway I'll subscribe to your blog

Thanks mate

David Ross Drake said...

I'd note that because you no longer use the full path to the view in your example, it won't work before the view resolver is added (which means the first time you try to run the app in your example, it fails to resolve /hello). You'll get an error like the following:

javax.servlet.ServletException: Circular view path [hello]: would dispatch back to the current handler URL [/movie/hello] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default
view name generation.)

Otherwise, it's a great set of documentation here. I'm using your instructions here to modify a pom generated from the gae-archetype-objectify-jsp, and it's working already.

Drake, from Dynacron