An´alisis y dise˜no del prototipo
7.2. Diseño y maquetas
Let’s build a simple application (in the broadest sense of the term) to manage a ros-ter of some sort. We’re going to present this without tons of detailed explanation;
our goal is to give you an intuition for how things work in Spring Web MVC, rather than exhaustively cover all the bases. This leisurely stroll will give you a context against which to understand a more detailed discussion afterward. You’ll begin with the app configuration.
3.2.1 Configuring the application
The following listing shows a bare-bones web.xml configuration, but it will work just fine.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Source project: sip03, branch: 01 (Maven Project) -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet </servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main/*</url-pattern>
</servlet-mapping>
</web-app>
Listing 3.1 Simple DispatcherServlet configuration in web.xml
Declares DispatcherServlet
B
Maps requests to it
C
As promised, this is as simple as they come. You’ve defined a minimal Dispatch-erServlet
B
, which again is Spring Web MVC’s front controller, and you’ve indicated that you want to send /main/* requests to itC
. You’re not tied to that particular map-ping; that’s just what you happen to have chosen.Now let’s see the application context. You didn’t define a ContextLoaderListener in web.xml, so you might be wondering where the app context comes from. The answer is that each DispatcherServlet instance creates its own local app context using an XML configuration we provide, so here’s that configuration.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Source project: sip03, branch: 01 (Maven Project) -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean name="/roster/*"
class="com.springinpractice.ch03.web.RosterController"/>
<bean class="org.springframework.web.servlet.view.
➥ InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>
</beans>
DispatcherServlet knows where to find this file by using a convention you can prob-ably guess.4 You can configure the location, but we won’t worry about that right now.
First you define the controller, RosterController
B
. The controller name speci-fies the requests that the RosterController services.You also define a ViewResolver
C
, which allows you to convert logical view names to views. For now it’s enough to know that a logical view name such as foo is converted to /WEB-INF/jsp/foo.jsp given the definition here. When dealing with JSP views in particular, it’s a good practice to place them somewhere inside the WEB-INF folder (WEB-INF/jsp is the official recommendation) so clients can’t access them directly.That’s what you do here.
That does it for configuration. You’ll create a controller in a few minutes, but in preparation for that, let’s create a domain object to represent a member of your roster.
3.2.2 A simple domain object
The following listing shows Member.java, a simple domain object you’ll use in your controller.
Listing 3.2 /WEB-INF/main-servlet.xml, the DispatcherServlet’s local app context
4 The location convention is /WEB-INF/[servlet-name]-servlet.xml, if you’re feeling lazy.
Controller
B
beanMaps view names to views
C
// Source project: sip03, branch: 01 (Maven Project) package com.springinpractice.ch03.model;
public class Member {
private String firstName;
private String lastName;
public Member() { }
public Member(String firstName, String lastName) { this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName;
}
public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName;
}
public String toString() {
return firstName + " " + lastName;
} }
There isn’t anything too special here. You include two constructors because you’ll eventually use both of them.
Now let’s get to the controller, which is more interesting.
3.2.3 Writing a basic controller
The next listing shows the RosterController. To keep things simple, you’ll hard-code some fake roster data directly in the controller. In a real application, the controller would typically delegate to either a service or a DAO to obtain this data on the control-ler’s behalf. This is a pattern we’ll demonstrate in chapter 4.
// Source project: sip03, branch: 01 (Maven Project) package com.springinpractice.ch03.web;
import java.util.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.springinpractice.ch03.model.Member;
@Controller public final class RosterController {
Listing 3.3 Member.java
Listing 3.4 RosterController.java, the simple controller
@Controller for controllers
B
Just a POJO
C
private List<Member> members = new ArrayList<Member>();
public RosterController() {
members.add(new Member("John", "Lennon"));
members.add(new Member("Paul", "McCartney"));
members.add(new Member("George", "Harrison"));
members.add(new Member("Ringo", "Starr"));
}
public void member(@RequestParam("id") Integer id, Model model) { model.addAttribute(members.get(id));
} }
There’s a lot packed into this small controller class, and you’re relying heavily on con-ventions. Don’t feel bad if you’re not seeing the details of how everything is wired up;
you’ll have plenty of time to get to that in the pages ahead.
Because the controller is a POJO, it’s useful for certain purposes (for example, for request mapping purposes) to have an alternative way to flag it as a controller. That’s what the @Controller annotation is doing
B
. You’re not extending any other classes or implementing any interfacesC
; you’re effectively defining the contract for this controller.WARNING It should be obvious that you’ve hardcoded the member list into the controller. In a real application, you’d likely grab that from a service bean. But here you’re trying to see how the MVC part works, so you’re faking the member list.
You attach an @RequestMapping attribute to the list() and member() methods
D
. This identifies them as request-servicing methods. The actual paths involved are spec-ified by conventions you’ll see soon; here the paths are /roster/list[.*] and /roster/member[.*] respectively, where * is any extension. For example, /roster/list, /roster/
list.do, and /roster/list.html all map to the list() method.
The signature of the list() method is largely up to you. You declare whatever you’d like to have, within certain bounds of course. (This is the part we were saying earlier that we like about Spring Web MVC.) You declare a Model parameter
E
, which means Spring will automatically pass you a Model object (essentially it functions as a Map, even though it doesn’t implement that interface). Anything you put on the Model will be available to the JSP as a JSP expression language (EL) variable. Here, you’ve placed the list of members on the ModelF
. When you place objects on the model, they’re stored as name/value pairs. Here, you haven’t explicitly assigned a name to the attribute, so Spring will automatically generate the name by convention. Here, because the type is List<Member>, the generated name is memberList. You’ll be able to access it from your JSP using ${memberList}.Maps requests
At
G
you can see another example of the flexible method signatures in action.This time you declare that you want to accept an HTTP parameter called id, and you want it to be automatically parsed into an Integer. That’s exactly what happens here.
(We told you it was cool.) And again, with the model you haven’t provided an explicit attribute name, so the name will be autogenerated based on the attribute type. In this case the name will be member, and it will be available to the JSP using ${member}.
You may have noticed that you haven’t explicitly specified any view names. How does DispatcherServlet know which view gets the request after a given handler method is finished with it. The answer is that you’re using a convention that automati-cally translates the request URL to a logical view name. Because the request URL for the list() method is /roster/list[.*], the view name according to the convention will be list. Similarly, because the request URL for the member() method is /roster/mem-ber[.*], the view name will be member. You’ll see more about this convention later when we discuss the DefaultRequestToViewNameTranslator.
Another thing to note is that you define a couple of different actions. You can define as many as you like, but for now you’re keeping it simple. Normally you would put closely related functionality together in a single controller as you do here.
Now let’s peek at the master and details JSPs.
3.2.4 Implementing the master and details views
The following listing shows list.jsp, which displays the entire roster.
<%-- Source project: sip03, branch: 01 (Maven Project) --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> expects as you saw in listing 3.4.
Listing 3.5 /WEB-INF/jsp/roster/list.jsp, a roster master page
Member list exposed
Next, here’s the details page, member.jsp.
<%-- Source project: sip03, branch: 01 (Maven Project) --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Member: ${member}</title>
</head>
<body>
<h1>Member: ${member}</h1>
<p><a href="list.do">Back</a></p>
</body>
</html>
Nothing much happening here. It’s another example of grabbing data from the Model and displaying it in the JSP
B
. Let’s stop at this point to admire the beauty of your work so far. Point your browser to http://localhost:8080/sip/main/roster/list.do. It won’t win any design awards, but it works.Let’s try adding a form.