Controllers in detail

Controllers, with their methods annotated with @RequestMapping, handle web requests. They accept input data in multiple forms and transform them into Model attributes to be consumed by views that are displayed back to the client. They connect the user to service-layer beans, where your application behavior is defined.

A Controller in Spring MVC has the following signature:

public interface Controller {

   ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

A Controller is designed as an interface, allowing you to create any kind of implementation. Starting from Spring version 2.5, you can turn any class into a Controller just by annotating it with @Controller. It relieves you from implementing any specific interface or extending a framework-specific class:

@Controller
public class HomeController {

   @RequestMapping(value = "/", method = RequestMethod.GET)
   public String home(Model model) {
      logger.info("Welcome to Taskify", locale);
      return "home";
   }
}

The @Controller annotation assigns the role of a Controller to the given class. A Spring MVC application autodetects all the controllers in its classpath and registers them with WebApplicationContext if you enable component scanning, as shown here:

<context:component-scan base-package="com.taskify" />

@Controller, @RequestMapping, and a set of other annotations form the basis of Spring MVC. These annotations allow flexible method names and signatures for controllers. We will explore them in detail in the following section.

Mapping request URLs with @RequestMapping

The @RequestMapping annotation maps request URLs onto an entire @Controller class or its handler methods. It can be applied at the class as well as the method levels. Typically, you apply class-level @RequestMapping annotation to map a group of related URLs, such as a form with many actions, and method-level @RequestMapping annotation for specific actions, such as create, read, update, delete, upload, and download. Let's take a look at a typical form-based Controller with various actions in a pure REST model (GET, POST, PUT, and DELETE):

@Controller
@RequestMapping("/users")
public class UserController {

   @Autowired
   private UserService userService;

   @RequestMapping(method = RequestMethod.GET)
   public String listAllUsers(Locale locale, Model model) {
      model.addAttribute("users", userService.findAllUsers());
      return "user/list";
   }

   @RequestMapping(path = "/new", method = RequestMethod.GET)
   public String newUserForm(Model model) {
      User user = new User();
      user.setDateOfBirth(new Date());
      model.addAttribute("user", user);
      return "user/new";
   }

   @RequestMapping(path = "/new", method = RequestMethod.POST)
   public String saveNewUser(@ModelAttribute("user") User user, Model model) {
      userService.createNewUser(user);
      return "redirect:/user";
   }
   @RequestMapping(path = "/{id}", method = RequestMethod.GET)
   public ModelAndView viewUser(@PathVariable("id") Long id) {
      return new ModelAndView("user/view").addObject("user", userService.findById(id));
   }

   @RequestMapping(path = "/{id}/edit", method = RequestMethod.GET)
   public String editUser(@PathVariable("id") Long id, Model model) {
      model.addAttribute("user", userService.findById(id));
      return "user/edit";
   }

   @RequestMapping(path = "/{id}", method = RequestMethod.PUT)
   public String updateUser(@PathVariable("id") Long id, @ModelAttribute("user") User user, Model model) {
      userService.updateUser(user);
      model.addAttribute("user", userService.findById(user.getId()));
      return "redirect:/user/" + id;
   }

   @RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
   public String deleteUser(@PathVariable("id") Long id, Model model) {
      User existingUser = userService.findById(id);
      userService.deleteUser(existingUser);
      return "redirect:/user";
   }
}

UserController, listed in the preceding code, has methods that serve as request handlers for URLs representing CRUD operations on user entities with the help of UserService, which is injected as a dependency into the Controller. Since this Controller is based on web views, the handler methods fill up the Model and returns either a view name or ModelAndView object for further display. The final two handler methods, updateUser() and deleteUser(), redirect the requests at the end. They perform URL redirection after returning the response to the client.

Notice that UserController has a root URL (/user) and handler methods have a more narrow mapping with a combination of HTTP methods. They are invoked by the exact URLs seen in the following table:

URL

Handler method

HTTP method

Matching URL (sample)

/

listAllUsers

GET

http://localhost:8080/user

/new

newuserForm

GET

http://localhost:8080/user/new

/new

saveNewUser

POST

http://localhost:8080/user/new

/{id}

viewUser

GET

http://localhost:8080/user/123

/{id}/edit

editUser

GET

http://localhost:8080/user/123/edit

/{id}

updateUser

PUT

http://localhost:8080/user/123

/{id}

deleteUser

DELETE

http://localhost:8080/user/123

The HTTP methods GET and POST are supported by default, in line with the limited HTML (hence browser) support for the other two. However, for PUT and DELETE to work, you need to register HiddenHttpMethodFilter in your web.xml file. Use this code:

<filter>
   <filter-name>httpMethodFilter</filter-name>
   <filter-class>org.springframework.web.filter. HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>httpMethodFilter</filter-name>
   <servlet-name>rootDispatcher</servlet-name>
</filter-mapping>

HiddenHttpMethodFilter works even without Spring MVC; you can use it with any Java web framework or even a plain Servlet application.

URI template patterns with the @PathVariable annotation

In the sample UserController listing in the preceding code, you might have noticed templated URL patterns with variable names replaced by values when handling requests. See this, for example:

@RequestMapping(path = "/{id}/edit", method = RequestMethod.GET)
public String editUser(@PathVariable("id") Long id, Model mdl) { … }

Here, the templated variable, id, is mapped to an @PathVariable annotation. It is enclosed inside curly braces and annotated as a method argument for mapping. A URL can have any number of path variables. They support regular expressions as well as path patterns in the Apache Ant style. They help you build perfect URI endpoints in the classic REST model.

Binding parameters with the @RequestParam annotation

Request parameters that are inline with URI strings can be mapped with method arguments using the @RequestParam annotation. See the following excerpt from TaskController:

@Controller
public class TaskController {
...
   @RequestMapping(path = "/tasks", method = RequestMethod.GET)
   public String list(@RequestParam(name = "status", required = false) String status, Model model) {
      model.addAttribute("status", status);
      model.addAttribute("tasks", taskService.findAllTasks(status));
      return "task/list";
   }
...
}

A typical URL invoking the above handler is http:<context-root>/tasks?status=Open.

@RequestParam has four attributes: name, required, value, and defaultValue. While name is a mandatory attribute, all the others are optional. By default, all request parameters are required to be set to true, unless you specify them as false. Values of @RequestParam are automatically type-converted to parameter types by Spring.

Request handler method arguments

The @RequestMapping methods can have flexible method signatures; a mix of frameworks, custom objects, and annotations are supported. They are injected automatically during request processing if found as method arguments. Here is a list of a few supported framework classes and annotations; refer to the Spring official documentation or the Javadoc of RequestMapping for the complete list.

Supported classes

Annotations

javax.servlet.ServletRequest

@PathVariable

javax.servlet.ServletRequest

@RequestVariable

javax.servlet.http.HttpSession

@RequestParam

org.springframework.ui.Model

@RequestHeader

org.springframework.validation.BindingResult

@RequestBody

Java.util.Map

@RequestPart

Java.io.InputStream

@InitBinder

While the framework classes do not need any specific annotation, custom classes often need to accompany one of the supported annotations for the handler adapters in order to convert/format from the incoming web request object into the class instances.

Request handler method return types

Similar to flexible argument types, methods annotated by @RequestMapping can have either custom types (often annotated as @ResponseBody) or one of the many supported framework classes. The following list contains some of the many supported types:

  • org.springframework.web.servlet.ModelAndView
  • org.springframework.ui.Model
  • java.util.Map
  • org.springframework.web.servlet.View
  • java.lang.String
  • void
  • java.util.concurrent.Callable<?>
  • org.springframework.http.HttpEntity

Setting Model attributes

Model attributes are for the consumption of the view for display and binding with form elements. They can be set at both the controller and handler method level.

Any method with a non-void return type can be annotated as @ModelAttribute to make the method return type a Model attribute for all views resolved by the declared Controller. See an example:

@ModelAttribute(value = "users")
public List<User> getUsersList() {
   return userService.findAllUsers();
}

Model attributes specific to a view are set inside the handler method from where the view was resolved. Here is an example:

@RequestMapping(path = "/tasks/new", method = RequestMethod.GET)
public String newTaskForm(Model model) {
   model.addAttribute("task", new Task());
   return "task/new";
}

Building RESTful services for JSON and XML media

A web application often needs to expose some of its services as web APIs with the XML or JSON data formats, or both, for the consumption of AJAX requests from browsers as well as other devices, such as mobile and tablets.

REpresentational State Transfer (REST), is an established architectural style for building web APIs that align with native web protocols and methods. With REST, data is represented as resources that can be accessed and manipulated using a URI over the stateless protocol of HTTP. REST insists on the mapping of the create, read, update, and delete operations (CRUD) around a resource with the HTTP methods POST, GET, PUT, and DELETE, respectively.

Spring MVC makes it extremely easy to build simple API endpoints that consume and produce different media types such as text, JSON, and XML. A request handler method in an @Controller annotation can accept JSON, XML, or any other media type using the following two steps:

  1. Set the attribute consumes to the appropriate media type(s) at the RequestMapping method, for example, consumes = {"text/plain", "application/json"}).
  2. Annotate the method argument of the required type with @RequestBody. The web request is expected to contain the data in the format mentioned in step 1 (consumes; JSON, XML, and so on) and is resolved to this type by HttpMessageConverter during handling.

Similarly, the request handler method can produce JSON, XML, or any other media type using the following two steps:

  1. Set the attribute produces with the appropriate media type(s) at the RequestMapping method, for example, consumes = {"text/plain", "application/json"}).
  2. Annotate the return type of the handler method or the method declaration itself (next to @RequestMapping) with @ResponseBody. The handler will transform the return value into the data format specified in the produces attribute of RequestMapping.

The consumes and produces attributes of RequestMapping narrow down the primary mapping to the given media type (for example, consumes = "application/xml") or a sequence of media types (for example, consumes = {"text/plain", "application/json"}).

In addition to the attributes, make sure the following library exists in the pom.xml file:

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.6.2</version>
</dependency>

Here is an example handler method that accepts a JSON request and returns a JSON response:

@RequestMapping(path = "/tasks/new.json", method=RequestMethod.POST, consumes = "application/json", produces = "application/json")
@ResponseBody
public CreateTaskResponse createNewTaskJSON(@RequestBody CreateTaskRequest createRequest) {
   Task task = new Task();
   task.setName(createRequest.getTaskName());
   ...
   return new CreateTaskResponse(taskService.createTask(task));
}

This handler method expects a web request with JSON content:

{
   "taskName":"Project estimation",
   "priority": 2,
   "creatorId": 1,
   "assigneeId": 2,
   "comments": "Involve the team in the process"
}

Now, the same method could be modified slightly to support XML content, consumes as well as produces. Look at the following listing:

@RequestMapping(path = "/tasks/new.xml", method = RequestMethod.POST, consumes = "application/xml", produces = "application/xml")
@ResponseBody
public CreateTaskResponse createNewTaskXML(@RequestBody CreateTaskRequest createRequest) {
   Task task = new Task()
   task.setName(createRequest.getTaskName());
   . . .
   return new CreateTaskResponse(taskService.createTask(task));
}

Make sure you have the JAXB annotation @XmlRootElement at the root of both RequestBody and ResponseBody types (CreateTaskRequest and CreateTaskResponse in this case).

You can invoke the preceding XML handler by sending the following content with the web request to the handler URI:

<CreateTaskRequest>
   <taskName>Estimate the project</taskName>
   <priority>2</priority>
   <creatorId>1</creatorId>
   <assigneeId>2</assigneeId>
   <comments>Involve the team in the process</comments>
</CreateTaskRequest>

Building a RESTful service with RestController

RestController is a convenient stereotype provided for building REST API endpoints that serve custom media types such as JSON or XML. It combines @Controller with @ResponseBody, that is, you do not need to annotate @ResponseBody in the handler methods. @RequestMapping methods assume @ResponseBody semantics by default.

Let's see what the JSON handler method looks like when it becomes part of an @RestController annotation:

@RestController
public class TaskRestController {
   . . .
  @RequestMapping(path="/api/tasks/new.json", method=RequestMethod.POST, consumes="application/json",produces= "application/json")
  public CreateTaskResponse createNewTaskJSON(@RequestBody CreateTaskRequest createRequest) {
    Task task = new Task();
    task.setName(createRequest.getTaskName());
    . . .
    return new CreateTaskResponse(taskService.createTask(task));
  }}
}

Notice that the only difference in the mapping is the missing @ResponseBody annotation. It is best practice to define your REST APIs inside REST controllers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset