Controllers
using-lishys-notes.notes

Guide


The guide pane on the right contains information organized into sections.

Use the and buttons to move through the sections, or click an accordion tab (shown below) to go directly to a particular topic.



Code Reference


Each paragraph in the guide is usually associated with a particular piece of code. An orange arrow indicates that the code displayed in the middle pane is relevant to the guide text.



If the arrow is gray, simply click anywhere on the paragraph and the relevant code will be displayed.



Code Highlighter


The highlight feature identifies areas of the code which relate to a word or term in the guide. Hover the mouse over any underlined word and the relevant code will be highlighted.



You can disable the highlighting feature at any time by clicking on the button.

Don't forget to make the appropriate code page visible by clicking on any paragraph with a gray arrow.
AdvancedMappingController.java
package lishy.controllers.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/advanced.html")
public class AdvancedMappingController {
    
    @RequestMapping(params="param-one")
    public ModelAndView paramPresent() {    
        return new ModelAndView("advanced", "message", "Request parameter is present"); 
    }
    
    @RequestMapping(params={"param-one", "!param-two"})
    public ModelAndView paramAbsent() {    
        return new ModelAndView("advanced", "message", "Request parameter is absent"); 
    }

    @RequestMapping(params={"type=1", "param=abc"} )
    public ModelAndView paramEqualsValue() {
        return new ModelAndView("advanced", "message", "Request parameter is equal to a value");
    }
    
    @RequestMapping(method=RequestMethod.GET)
    public ModelAndView getRequest() {
        return new ModelAndView("advanced", "message", "A GET request "); 
    }
    
    @RequestMapping(method=RequestMethod.POST)
    public ModelAndView postRequest() {
        return new ModelAndView("advanced", "message", "A POST request"); 
    }

    @RequestMapping(headers="content-type=text/*")
    public ModelAndView headers() {
        return new ModelAndView("advanced", "message", "Narrowing with request headers"); 
    }
    
}

ArgumentsController.java
package lishy.controllers.web;

import java.util.Locale;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/argument/*")
public class ArgumentsController {
    
    @RequestMapping
    public ModelAndView cookieValue(@CookieValue("JSESSIONID") String cookieValue) {
        return new ModelAndView("arguments", 
                                "message", 
                                "Session id is " + cookieValue);        
    }
    
    @RequestMapping
    public ModelAndView cookie(@CookieValue("JSESSIONID") Cookie cookie) {        
        return new ModelAndView("arguments", 
                                "message", 
                                "Cookie name " + cookie.getName() +
                                ", value " + cookie.getValue());        
    }
    
    @RequestMapping
    public ModelAndView otherArguments(HttpServletRequest request,
                                       HttpSession session,
                                       Locale locale) {        
        return new ModelAndView("arguments", 
                                "message", 
                                "Host: " + request.getLocalName() +
                                ", session id: " + session.getId() +
                                ", locale: " + locale.getDisplayName());
    }
    
}

MethodReturnTypeController.java
package lishy.controllers.web;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import lishy.controllers.DomainObject;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MethodReturnTypeController {
    
    @RequestMapping(value="/model-and-view.html")
    public ModelAndView modelAndView() {          
        ModelAndView modelAndView = new ModelAndView("return-types");
        modelAndView.addObject("message", "This is added to the model");
        modelAndView.addObject("anotherMessage", " and so is this");
        return modelAndView;                      
    }
    
    @RequestMapping(value="/view-name.html")
    public String viewNameOnly(Model model) {   
        model.addAttribute("message", "A return type of String is a view name");
        return "return-types";                
    }
    
    @RequestMapping(value="/return-types.html", params="model")
    public Model model() {   
        return new ExtendedModelMap()
            .addAttribute("message", "This is added to a Model object")
            .addAttribute(new DomainObject(200, "an object with a default attribute name"));
    }
    
    @RequestMapping(value="/return-types.html", params="map")
    public Map<String, String> map() {   
        Map<String, String> modelData = new HashMap<String, String>();
        modelData.put("message", "This is added to a Map object"); 
        modelData.put("anotherMessage", " and so is this");  
        return modelData;
    }
    
    @RequestMapping(value="/return-types.html", params="void")
    public void voidReturnType(PrintWriter writer) throws IOException {   
        writer.print("<html><body>" +
                     "<h1>Raw Servlet Response</h1>" +
                     "<a href='model-and-view.html'>back</a>" +
                     "</body></html>");
    }
     
}
ModelAttributeController.java
package lishy.controllers.web;

import java.util.ArrayList;
import java.util.List;

import lishy.controllers.DomainObject;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ModelAttributeController {
    
    @RequestMapping(value="/model-attributes.html", params="primitive")
    @ModelAttribute("number") public int primitiveAttribute() {
        return 1234567890;              
    }   
    
    @RequestMapping(value="/model-attributes.html", params="object")
    @ModelAttribute("myData") public DomainObject namedAttribute() {
        return new DomainObject(100, "an object with a named model attribute");
    }
    
    @RequestMapping(value="/model-attributes.html", params="default-name")
    @ModelAttribute public DomainObject defaultAttributeName() {
        return new DomainObject(200, "an object with a default attribute name");
    }
    
    @RequestMapping(value="/model-attributes.html", params="collection")
    @ModelAttribute public List<DomainObject> collection() {
        List<DomainObject> objects = new ArrayList<DomainObject>();
        for (int i=1; i<=3; i++) {
            objects.add(new DomainObject(i, "object with id " + i));
        }   
        return objects;             
    }
    
    @ModelAttribute("ref") public String refData() {
        return "Reference data is added to the model for each request";
    }
    
    @RequestMapping(value="/model-attributes.html", params="ref")
    @ModelAttribute("msg") public String refParam(@ModelAttribute("ref") String ref) {
        return ref += " and can be accessed by @ModelAttribute annotated parameters";
    } 
    
}

RequestMappingController.java
package lishy.controllers.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("my-path/*")
public class RequestMappingController {
    
    @RequestMapping("one.html")
    public ModelAndView first() {        
        return new ModelAndView("request", "methodName", "First method");
    }
    
    @RequestMapping("tw*.html")
    public ModelAndView second() {        
        return new ModelAndView("request", "methodName", "Second method");
    }

    @RequestMapping
    public ModelAndView third() {          
        return new ModelAndView("request", "methodName", "Third method");
    }
    
    @RequestMapping
    public ModelAndView fourth() {        
        return new ModelAndView("request", "methodName", "Fourth method");
    }

}
RequestParameterController.java
package lishy.controllers.web;

import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/param/*")
public class RequestParameterController {
    
    @RequestMapping
    public ModelAndView requestParam(@RequestParam("one") long arg1,
                                     @RequestParam(value="two", required=false) String arg2,
                                     @RequestParam(value="three", defaultValue="-1") int arg3) {
        return new ModelAndView("params", 
                                "message", 
                                "Parameter values are " + arg1 + ", " +  arg2 + " and " + arg3);
    }
    
    @RequestMapping
    public ModelAndView map(@RequestParam Map<String, String> params) {            
        return new ModelAndView("params", 
                                "message", 
                                "Map contains values " + params.values());        
    }
    
    @RequestMapping
    public ModelAndView multiValueMap(@RequestParam MultiValueMap<String, String> params) {
        return new ModelAndView("params", 
                                "message", 
                                "MultiValueMap contains values " + params.values());
    }
    
}

SimpleController.java
package lishy.controllers.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class SimpleController {
    
    @RequestMapping(value="/simple.html")
    public ModelAndView simpleRequest() {
        return new ModelAndView("simple", 
                                "text", 
                                "This is added to the model as the 'text' attribute");
    }

}

DomainObject.java
package lishy.controllers;

public class DomainObject {

    private int id;
    private String name;

    public DomainObject() {
        this(987, "this is a typical domain object");
    }
    public DomainObject(int id, String name) {
        this.setId(id);
        this.setName(name);
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}


advanced.jsp
<html>
  <body>

    <h1>Advanced Mapping</h1>
   
    <p style="color:green">${message}</p>

    <p>
      <a href="advanced.html?param-one=123¶m-two=abc">Parameter present</a><br/>
      <a href="advanced.html?param-one">Parameter absent</a><br/>
      <a href="advanced.html?type=1¶m=abc">Parameter equals value</a><br/>
    </p>
    
    <p>
      <a href="advanced.html">GET request</a><br/>
    </p>
    
    <form action="advanced.html" method="POST">
      <button type="submit">Post request</button>
    </form>

    <p>
      <a href="../index.html">back</a>
    </p>

  </body>
</html>  
arguments.jsp
<html>
  <body>

    <h1>Handler Method Arguments</h1>
   
    <p style="color:green">${message}</p>
      
    <p>  
      <a href="cookieValue.html">Cookie value</a><br/>      
      <a href="cookie.html">Cookie details</a><br/>    
    </p>
    <p>      
      <a href="otherArguments.html">Other arguments</a><br/>
    </p>

    <p>
      <a href="../../index.html">back</a>
    </p>

  </body>
</html> 
model-attributes.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>  

<html>
  <body>
  
    <h1>Model Attributes</h1>
    <span style="color:green">${ref}</span> 
    
    <p>
      <a href="model-attributes.html?primitive">Primitive attribute</a> 
      <span style="color:green">${number}</span>
    </p> 
    
    <p>
      <a href="model-attributes.html?object">Object attribute</a> 
      <span style="color:green">${myData.id} ${myData.name}</span>
    </p> 
    
    <p>
      <a href="model-attributes.html?default-name">Default model attribute name</a>
      <span style="color:green">${domainObject.id} ${domainObject.name}</span>
    </p>
    
    <p>
      <a href="model-attributes.html?collection">Collection</a><br/>
      <c:forEach items="${domainObjectList}" var="domainObject">
        <span style="color:green">${domainObject.id} ${domainObject.name}</span><br/>
      </c:forEach>
    </p>
    
    <p>
      <a href="model-attributes.html?ref">Reference data</a> 
      <span style="color:green">${msg}</span>
    </p>
    
    <p>
      <a href="../index.html">back</a>
    </p>
    
  </body>
</html>
params.jsp
<html>
  <body>

    <h1>Request Parameters</h1>
   
    <p style="color:green">${message}</p>
    
    <p>
      <a href="requestParam.html?one=12345">One parameter</a><br/>
      <a href="requestParam.html?one=12345&two=abc">Two parameters</a><br/>
      <a href="requestParam.html?one=12345&two=abc&three=999">Three parameters</a><br/>
    </p>

    <p>
      <a href="map.html?param1=56789¶m2=xyz¶m3=999">Map</a><br/>
      <a href="multiValueMap.html?p1=111&p1=222&p2=aaa&p2=bbb">MultiValueMap</a><br/>
    </p>

    <p>
      <a href="../../index.html">back</a>
    </p>

  </body>
</html> 
request.jsp
<html>
  <body>

    <h1>Request Mapping</h1>
    
    <p style="color:green">${methodName}</p>

    <p>
      <a href="one.html">one</a><br/>
      <a href="two.html">two</a><br/>
      <a href="twenty.html">twenty</a><br/>
      <a href="third.html">third</a><br/>
      <a href="fourth.html">fourth</a><br/>
      <a href="fifth.html">fifth</a> (fails with resource not available)<br/>
    </p>
    
    <p>
      <a href="../../index.html">back</a>
    </p>
    
  </body>
</html>  
return-types.jsp
<html>
  <body>
  
    <h1>Handler Method Return Types</h1>
    
    <p style="color:green">${message} ${anotherMessage}</p>
    
    <p>
      <a href="model-and-view.html">Model and View</a> 
      <span style="color:green">${myData.id} ${myData.name}</span>
    </p> 
    
    <p>
      <a href="view-name.html">View name</a>
    </p>
    
    <p>
      <a href="return-types.html?model">Model object</a>
      <span style="color:green">${domainObject.id} ${domainObject.name}</span>
    </p>
    
    <p>
      <a href="return-types.html?map">Map object</a><br/>
    </p>
    
    <p>
      <a href="return-types.html?void">void</a>      
    </p>
    
    <p>
      <a href="../index.html">back</a>
    </p>
    
  </body>
</html>  
simple.jsp
<html>
  <body>

    <h1>Simple</h1>
   
    <p style="color:green">${text}</p>

    <p>
      <a href="../index.html">back</a>
    </p>

  </body>
</html> 
controllers-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-2.5.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="lishy.controllers.web"/>

  <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        p:prefix="/WEB-INF/jsp/"
        p:suffix=".jsp"/>

</beans>
web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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">
         
  <display-name>Controllers</display-name>
  
  <servlet>
    <servlet-name>controllers</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>controllers</servlet-name>
    <url-pattern>/app/*</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
  
</web-app>
index.html
<html>
  <body>
  
    <h1>Controllers</h1>
    
    <p>
      <a href="app/simple.html">Simple Controller</a>
    </p>

    <p>
      <a href="app/my-path/one.html">Request Mapping</a>
    </p>

    <p>
      <a href="app/advanced.html">Advanced Mapping</a>
    </p>
    
    <p>
      <a href="app/param/requestParam?one=999">Request Parameters</a>
    </p>
    
    <p>
      <a href="app/argument/cookie">Handler Method Arguments</a>
    </p>
    
    <p>
      <a href="app/model-and-view.html">Handler Method Return Types</a>
    </p>  
        
    <p>
      <a href="app/model-attributes.html?primitive">Model Attributes</a>
    </p>      
    
  </body>
</html> 

Controller Fundamentals

The @Controller annotation indicates that a class is a web controller, and @RequestMapping maps web requests such as

http://host/root/app/simple.html

onto a method of the controller.
Handler methods can use a ModelAndViewModelAndView:gt(0) object to specify the view name""simple"" and to populate the model with dataattribute.

Here we use the ModelAndViewModelAndView:eq(2) constructor because this method is only adding a single attribute""text" to the model.

Note: the terms handler and controller are used interchangeably. Technically, non-controller handlers can be plugged in to Spring, but this is very rare.

During start up, component-scan will scan annotated controller classes in the base-package for mapped methods by detecting @RequestMapping annotations.

Request Mapping

If a class level @RequestMapping@RequestMapping:eq(0) is used then method level @RequestMapping@RequestMapping:gt(0)s are relative to the specified pathmy-path/*.

For example, the first()first method would be mapped to this URL

http://host/root/app/my-path/one.html
Ant-style path patterns such as tw*.html are also supported, so URLs ending in /my-path/two.html and /my-path/twenty.html would both find their way to the second()second method.
Method names are taken into account if the path is omitted from @RequestMapping@RequestMapping:gt(2). So this URL

http://host/root/app/my-path/third.html

will execute the third()third method.

The class level @RequestMapping/* path must end in /* to support this.
A single method with no explicit path mapping will act as a default for the controller if a more specific mapped method is not found.

If the fourth()fourth method were to be removed from this class then the third()third method would become the default and requests would be handled like this

my-path/one.htmlone.html -> first
my-path/two.htmltw*.html -> second
my-path/xyz.html -> third

Advanced Mapping

We can narrow path mappings to target particular handler methods using request parameter conditions.

We can test for:

  • The presence of a request parameter regardless of its value: params="param-one"""param-one""
  • The absence of a request parameter: !param-two
  • A request parameter being equal to a particular value: type=1, param=abc
  • Any combination of these.

Here we have a class level path mapping of /advanced.html but the actual method to be executed is determined by the parameters submitted with the request.

The advanced.jsp page shows the request parameters which will trigger the paramPresent()param-two, paramAbsent()param-one" and paramEqualsValuetype=1 methods on our controller.

HTTP request methodsmethod (GETRequestMethod.GET, POSTRequestMethod.POST, HEAD, OPTIONS, PUT, DELETE and TRACE) and HTTP headersheaders= can also be used to narrow the primary mapping. This is especially useful when building RESTful web services.
These restrictions are supported by @RequestMapping@RequestMapping:first at the class level too. When used at the class level, all method level mappings inherit the restrictions.

Request Parameters

@RequestParam binds a method parameter to a web request parameter.
So, when the Three parameters link on params.jsp sends a query string of

?one=12345&two=abc&three=999
the arg1arg1:first parameter will be set to 12345, arg2arg2:first to abc and arg3arg3:first to 999. Spring takes care of type conversion automatically.

Parameters are mandatory by default but we can set the required attribute to false to make a parameter optional.

A defaultValue can also be specified. Supplying a default value implicitly sets required to false.
Incidentally, if the valuevalue= element of @RequestParam is omitted on a primitive value like this

@RequestParam long desc

then Spring will look for a request parameter with the same name as the method parameter, in this case desc.

However, this is only true if the code is compiled with debugging enabled. For this reason I would recommend that the valuevalue= element is always included to avoid potential problems when the application is deployed to a different environment.
@RequestParam can also be used on a MapMap<String:first or MultiValueMapMultiValueMap< method parameter to gain access to all request parameters.

Handler Method Arguments

@RequestParam annotated parameters are not the only type of handler method arguments supported by Spring.
Handler methods which are annotated with @RequestMapping@RequestMapping:gt(0) can have very flexible signatures, which accept a variety of parameter types.
The @CookieValue annotation, for instance, indicates that a method parameter, declared as type CookieCookie:eq(4) or as a cookie value type (String, int, etc), should be bound to an HTTP cookie.
Arguments of the following types are also supported:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession (not thread safe)
  • WebRequest / NativeWebRequest
  • InputStream / Reader
  • OutputStream / Writer
  • Locale

Handler Method Return Types

A controller can return the name of a view and a model populated with user data using a ModelAndViewModelAndView:gt(0) object.
A return type of Stringreturn-types:eq(1) is interpreted as a view name. In this case the model must be passed into the method as a ModelModel:eq(6) or Map argument if data is to be added to it.
A handler method can also return a ModelModel:eq(7) or MapMap:eq(9) object to add data to the model. Model extends the Map interface to support method chainingaddAttribute:gt(0) and automatic generation of model attribute namesaddAttribute:eq(2).

You will notice that the model()model:eq(10) and map()map:eq(1) methods do not supply the view name. Instead they rely on the DefaultRequestToViewNameTranslator (which is registered by default) to convert the URLreturn-types:gt(1):lt(2) of the incoming request into the logical view name.
We use a return type of voidvoid:eq(1) if the method handles the response itself using an HttpServletResponse or WriterWriter:eq(1) etc, or we wish to rely on the default URL to view name resolution strategy mentioned above.

Model Attributes

@ModelAttribute@ModelAttribute:lt(6) can be used to bind a return value to a named model attribute.

If the valuemyData element is omitted then the name is derived from the attribute type being returned (including primitives which produce names such as int, long, and boolean, which is probably not what we want).

For example, the defaultAttributeName()defaultAttributeName method adds the return value to the model with the name domainObjectDomainObject:eq(3), and the collection()collection:eq(1) method returns a list into the domainObjectListList:eq(2) attribute.

However, it is good practice to always name model attributes explicitly, as primitiveAttribute()number and namedAttribute()myData do, to improve readability and to safeguard against refactoring changes.
Reference data can also be added to the model by annotating an accessor method with @ModelAttribute@ModelAttribute:eq(4) but without a @RequestMapping. These methods are allowed to have any arguments that @RequestMapping supports for handler methods

In our example, the refData()refData method adds some simple reference data to the model and is executed before each request method.

In fact, this reference data can be accessed by other handler methods using parameters annotated with @ModelAttribute@ModelAttribute:eq(6). The refParam()refParam method demonstrates this.
None of these methods specify the name of a view. Instead, Spring uses DefaultRequestToViewNameTranslator to derive the view name from the URL.
Home  |  Getting Started  |  Controllers  |  Views  |  Forms  |  REST  |  Testing  |  Configuration  |  MVC Walkthrough  |   lishblog  |  Email Lishy