18.2 编写请求处理方法

每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果。例如,如果在请求处理方法中需要访问HttpSession对象,则可以使用HttpSession作为方法的参数,Spring会将HttpSession对象正确地传递给方法:

1
2
3
4
5
6
@RequestMapping("/uri")
public String myMethod(HttpSession session) {
...
session.addAttribute(key, value);
...
}

或者,若需要访问客户端语言环境和HttpServletRequest对象,则可以在方法签名上包括这样的参数:

1
2
3
4
5
6
7
@RequestMapping("/uri")
public String myOtherMethod(HttpServletRequest request,
Locale locale) {
...
// access Locale and HttpServletRequest here
...
}

允许作为请求处理方法的参数的类型

下面是可以在请求处理方法中出现的参数类型:

  • javax.servlet.ServletRequestjavax.servlet.http.HttpServletRequest
  • javax.servlet.ServletResponsejavax.servlet.http.HttpServletResponse
  • javax.servlet.http.HttpSession
  • org.springframework.web.context.request.WebRequestorg.springframework.web.context.request.NativeWebRequest
  • java.util.Locale
  • java.io.InputStreamjava.io.Reader
  • java.io.OutputStreamjava.io.Writer
  • java.security.Principal
  • HttpEntity<?>
  • java.util.Map/
  • org.springframework.ui.Model/
  • org.springframework.ui.ModelMap
  • org.springframework.web.servlet.mvc.support.RedirectAttrib
  • org.springframework.validation.Errors/
  • org.springframework.validation.BindingResult

以及命令或表单对象:

  • org.springframework.web.bind.support.SessionStatus
  • org.springframework.web.util.UriComponentsBuilder
  • @PathVariable, @MatrixVariable注释的对象
  • @RequestParam, @RequestHeader, @RequestBody@RequestPart

特别重要的是org.springframework.ui.Model类型。这不是一个Servlet API类型,而是一个包含MapSpring MVC类型。每次调用请求处理方法时,Spring MVC都创建Model对象并将各种对象注入到Map中。

请求处理方法可以返回的类型

请求处理方法可以返回如下类型的对象:

  • ModelAndView
  • Model
  • Map包含模型的属性
  • View
  • 代表逻辑视图名的String
  • void
  • 提供对Servlet的访问,以响应HTTP头部和内容HttpEntityResponseEntity对象
  • Callable
  • DeferredResult
  • 其他任意类型,Spring将其视作输出给View的对象模型

本章后续会展示一个例子,进一步学习如何开发一个请求处理方法。

第18章 基于注解的控制器

在第17章中,我们创建了两个采用传统风格控制器的Spring MVC应用程序,其控制器是实现了Controller接口的类。Spring 2.5版本引入了一个新途径:通过使用控制器注释类型。本章介绍了基于注解的控制器,以及各种对应用程序有用的注解类型。

18.1 SpringMVC注解类型

基于注解的控制器的优点

使用基于注解的控制器有几个优点。其一,一个控制器类可以处理多个动作(而一个实现了Controller接口的控制器只能处理一个动作)。这就允许将相关的操作写在同一个控制器类中,从而减少应用程序中类的数量。
其二,基于注解的控制器的请求映射不需要存储在配置文件中。使用RequestMapping注解类型,可以对一个方法进行请求处理。

ControllerRequestMapping注解类型是SpringMVC API最重要的两个注解类型。本章重点介绍这两个,并简要介绍了一些其他不太流行的注解类型。

18.1.1 Controller注解类型

org.springframework.stereotype.Controller注解类型用于指示Spring类的实例是一个控制器。下面是一个带注解@Controller的例子:

1
2
3
4
5
6
7
package com.example.controller;
import org.springframework.stereotype;
...
@Controller
public class CustomerController {
// request-handling methods here
}

Spring使用扫描机制来找到应用程序中所有基于注解的控制器类。为了保证Spring能找到你的控制器,需要完成两件事情。首先,需要在Spring MVC的配置文件中声明spring- context,如下所示:

1
2
3
4
5
<beans
...
xmlns:context="http://www.springframework.org/schema/context"
...
>

当然这个自己肯定是背不出来的,安装来Spring Toos3后可以点击文件下方的Namespaces标签,然后勾选上context即可:
这里有一张图片

然后,需要应用<component-scan/>元素,如下所示:

1
<context:component-scan base-package="basePackage"/>

请在<component-scan/>元素中指定控制器类的基本包。例如,若所有的控制器类都在com.example.controller及其子包下,则需要写一个如下所示的<component-scan/>元素:

1
<context:component-scan base-package="com.example.controller"/>

现在,整个配置文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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">
<context:component-scan base-package="com.example.controller"/>
<!-- ... -->
</beans>

请确保所有控制器类都在基本包下,并且不要指定一个太广泛的基本包(例如不要指定大范围的com.example,而是指定小范围的com.example.controller),因为这会使得Spring MVC扫描了无关的包。

18.1.2 RequestMapping注解类型

现在,我们需要在控制类的内部为每一个动作开发相应的处理方法。要让Spring知道用哪一种方法来处理它的动作,需要使用org.springframework.web.bind.annotation.RequestMapping注释类型映射的URI与方法。

RequestMapping注解类型的作用就如同其名字所暗示的:映射一个请求和一种方法可以使用@RequestMapping注解一种方法
一个采用@RequestMapping注解的方法将成为一个请求处理方法,并由调度程序在接收到对应URL请求时调用。
下面是一个RequestMapping注解方法的控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
...
@Controller
public class CustomerController {
//设置该方法为请求处理方法,触发该方法的RUL为
///`customer_input`
@RequestMapping(value = "/customer_input")
public String inputCustomer() {
// do something here
return "CustomerForm";
}
}

使用RequestMapping注解的value属性将URI映射到方法。在上面的例子中,我们将customer_input映射到inputCustomer方法。这样,可以使用如下URL访问inputCustomer方法:

1
http://domain/context/customer_input

由于value属性是RequestMapping注解的默认属性,因此,若只有唯一的属性,则可以省略属性名称。换句话说,如下两个标注含义相同:

1
2
@RequestMapping(value = "/customer_input")
@RequestMapping("/customer_input")

但如果@RequestMapping有超过一个属性时,就必须写入value属性名称。
请求映射的值可以是一个空字符串,此时该方法被映射到以下网址:

1
http://domain/context

RequestMapping除了具有value属性外,还有其他属性。例如,method属性用来指示该方法仅处理哪些HTTP方法
例如,仅在使用HTTP POSTPUT方法时,才调用下面的ProcessOrder方法:

1
2
3
4
5
6
7
8
9
10
11
12
...
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
...
//只响应Post或put方法
@RequestMapping(value="/order_process",
method={RequestMethod.POST, RequestMethod.PUT})
public String processOrder() {
// do something here
return "OrderForm";
}

method属性只有一个HTTP方法值,则无需花括号。例如:

1
@RequestMapping(value="/order_process", method=RequestMethod.POST)

如果没有指定method属性值,则请求处理方法可以处理任意HTTP方法。
此外,RequestMapping注解类型也可以用来注解一个控制器类,如下所示:

1
2
3
4
5
6
7
8
import org.springframework.stereotype.Controller;
...
@Controller
//设置该控制器对应的URL
@RequestMapping(value="/customer")
public class CustomerController {
...
}

在这种情况下,所有的方法都将映射为相对于类级别的请求。例如下面的 deleteCustomer方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
...
@Controller
@RequestMapping("/customer")
public class CustomerController {
@RequestMapping(value="/delete",
method={RequestMethod.POST, RequestMethod.PUT})
public String deleteCustomer() {
// do something here
return ...;
}

由于控制器类的映射使用“/customer”,而deleteCustomer方法映射为“/delete”,则如下URL会映射到该方法上:

1
http://domain/context/customer/delete

17.6 小结

本章是Spring MVC的入门介绍。我们学习了如何开发一个类似第16章的简单应用。**在Spring MVC中,我们无须编写自己的DispatcherServlet**,其传统风格的控制器开发方式是实现控制器接口。从Spring 2.5版本开始,Spring提供了一个更好的开发控制器的方式,例如采用注解。第18章会深入介绍这种风格的控制器。

17.5 ViewResolver

Spring MVC中的视图解析器负责解析视图。可以通过在配置文件中定义一个ViewResolver(如下)来配置视图解析器:

1
2
3
4
5
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>

如上视图解析器设置了前缀后缀两个属性。这样可以缩短view的路径。例如,仅需提供myPage视图解析器将会自动增加前缀和后缀,这样的到的真实路径为:/WEB-INF/jsp/myPage.jsp

实例

app17b应用为例,该例子与app17a应用类似,但调整了配置文件的名称和路径。此外,它还配置了默认的视图解析器为所有视图路径添加前缀和后缀

目录结构

以下为所示为app17b的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
E:\workspace_web\app17b
├─pom.xml
└─src
├─main
│ ├─java
│ │ └─app17b
│ │ ├─controller
│ │ │ ├─InputProductController.java
│ │ │ └─SaveProductController.java
│ │ ├─domain
│ │ │ └─Product.java
│ │ └─form
│ │ └─ProductForm.java
│ ├─resources
│ └─webapp
│ ├─css
│ │ └─main.css
│ ├─index.jsp
│ ├─META-INF
│ │ └─MANIFEST.MF
│ └─WEB-INF
│ ├─config
│ │ └─springmvc-config.xml
│ ├─jsp
│ │ ├─ProductDetails.jsp
│ │ └─ProductForm.jsp
│ ├─lib
│ └─web.xml
└─test
└─java

项目详解

app17b项目中,Spring MVC的配置文件被重命名为springmvc-config.xml中并移动到/WEB-INF/config目录下。为了让Spring MVC可以正确加载到该配置文件,需要将文件路径配置到Spring MVCDispatcherservlet

app17b应用的部署描述符web.xml

下面显示了app17b应用的部署描述符(web.xml文件)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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_3_0.xsd">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 设置DispatcherServlet的初始化参数 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- springMVC配置文件的路径 -->
<param-value>/WEB-INF/config/springmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>

需要特别注意的是web.xml文件中的init-param元素。不要使用默认命名和路径的配置文件,要使用名为contextConfigLocationinit-param,其值应为配置文件在应用中的相对路径。

app17b的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean name="/product_input.action"
class="app17b.controller.InputProductController" />
<bean name="/product_save.action"
class="app17b.controller.SaveProductController" />
<!-- 定义视图解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>

控制器

InputProductController.java

使用视图解析器可以简化视图的路径,修改app17a之中的InputProductController.java代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package app17b.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class InputProductController
implements
Controller
{
private static final Log logger = LogFactory
.getLog(InputProductController.class);
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception
{
logger.info("InputProductController called");
// 只需要返回文件名即可,视图解析器会加上和。
// 前缀`/WEB-INF/jsp/`和后缀`.jsp`
// 形成`/WEB-INF/jsp/ProductForm.jsp`
return new ModelAndView("ProductForm");
}
}

注意最后一行代码return new ModelAndView("ProductForm");,相较于app17a对应的 return new ModelAndView("/WEB-INF/jsp/ProductForm.jsp");精简了好多.

SaveProductController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package app17b.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import app17b.domain.Product;
import app17b.form.ProductForm;
public class SaveProductController
implements
Controller
{
private static final Log logger = LogFactory
.getLog(SaveProductController.class);
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception
{
logger.info("SaveProductController called");
ProductForm productForm = new ProductForm();
// populate action properties
productForm.setName(
request.getParameter("name"));
productForm.setDescription(
request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// create model
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
try
{
product.setPrice(Float.parseFloat(
productForm.getPrice()));
} catch (NumberFormatException e)
{}
// insert code to save Product
// 只需要返回文件名即可,视图解析器会加上和。
// 前缀`/WEB-INF/jsp/`和后缀`.jsp`
// 形成`/WEB-INF/jsp//WEB-INF/jsp/ProductDetails.jsp`
return new ModelAndView("ProductDetails", "product", product);
}
}

其他代码

其他代码与app17a完全一致,这里不再列举。

运行效果

测试app17b应用,在浏览器中输入如下URL
http://localhost:8080/app17b/product_input.action
即可看到下图所示的表单页面。
这里有一张图片
填写并提交表单,显示效果如下:
这里有一张图片

17.4 第一个Spring MVC应用

本章的示例应用程序app17a展示了基本的Spring MVC应用。该应用程序同第16章学习的app16b应用非常相似,以便展示Spring MVC是如何工作的。app17a应用也有两个控制器是类似于app17b的控制器类。

17.4.1 目录结构

以下为app17a的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
E:\workspace_web\app17a
├─pom.xml
└─src
├─main
│ ├─java
│ │ └─app17a
│ │ ├─controller
│ │ │ ├─InputProductController.java
│ │ │ └─SaveProductController.java
│ │ ├─domain
│ │ │ └─Product.java
│ │ └─form
│ │ └─ProductForm.java
│ ├─resources
│ └─webapp
│ ├─css
│ │ └─main.css
│ ├─index.jsp
│ ├─META-INF
│ │ └─MANIFEST.MF
│ └─WEB-INF
│ ├─jsp
│ │ ├─ProductDetails.jsp
│ │ └─ProductForm.jsp
│ ├─lib
│ ├─springmvc-servlet.xml
│ └─web.xml
└─test
└─java

这是一个基于Maven的Java Web项目,依赖如下所示,注意,。特别需要注意的是spring-webmvc-x.y.z.jar文件,其中包含了DispatcherServlet的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!-- Spring MVC依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>

还要注意**Spring MVC依赖于Apache Commons Logging组件,没有它,SpringMVC应用程序将无法正常工作**。可以从以下网址下载这个组件:
http://commons.apache.org/proper/commons-loggins/download_logging.cgi
或者使用Maven引入,如下所示:

1
2
3
4
5
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>

本示例应用的所有JSP页面都存放在/WEB-INF/jsp目录下,确保无法被客户端直接访问。

17.4.2 部署描述符文件和Spring MVC配置文件

部署描述符(web.xml)文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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_3_0.xsd">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>

这里告诉了Servlet/JSP容器,我们将使用Spring MVCDispatcherServlet,并通过配置url-pattern元素值为“/”,将所有的URL映射到该servlet。由于servlet元素下没有init-param元素,所以Spring MVC的配置文件在/WEB-INF文件夹下,并按照通常的命名约定。
下面,我们来看一下清单17.2所示的Spring MVC配置文件(springmvc-servlet.xml)。

Spring MVC配置文件springmvc-servlet.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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.xsd">
<bean name="/product_input.action"
class="app17a.controller.InputProductController" />
<bean name="/product_save.action"
class="app17a.controller.SaveProductController" />
</beans>

这里声明了InputProductControllerSaveProductController两个控制器类,并分别映射到/product_input.action/product_save.action。两个控制器是将在下一节讨论。

17.4.3 Controller

app17a应用程序有InputProductControllerSaveProductController两个“传统”风格的控制器,分别实现了Controller接口。代码如下.

InputProductController类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package app17a.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class InputProductController
implements
Controller
{
private static final Log logger = LogFactory
.getLog(InputProductController.class);
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception
{
logger.info("InputProductController called");
//返回一个视图
return new ModelAndView("/WEB-INF/jsp/ProductForm.jsp");
}
}

InputProductController类的handleRequest方法只是返回一个ModelAndView,包含一个视图,且没有模型。因此,该请求将被转发到/WEB-INF/jsp/ProductForm.jsp页面。

SaveProductController类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package app17a.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import app17a.domain.Product;
import app17a.form.ProductForm;
public class SaveProductController
implements
Controller
{
private static final Log logger = LogFactory
.getLog(SaveProductController.class);
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception
{
logger.info("SaveProductController called");
ProductForm productForm = new ProductForm();
// populate action properties
productForm.setName(request.getParameter("name"));
productForm
.setDescription(request.getParameter("description"));
productForm.setPrice(request.getParameter("price"));
// create model
Product product = new Product();
product.setName(productForm.getName());
product.setDescription(productForm.getDescription());
try
{
product
.setPrice(Float.parseFloat(productForm.getPrice()));
} catch (NumberFormatException e)
{}
// insert code to save Product
//返回视图(jsp页面)的路径,模型的名称,模型(product对象)
return new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp",
"product",
product);
}
}

SaveProductController类的handleRequest方法中,首先用请求参数创建一个ProductForm对象;然后,它根据ProductForm对象创建Product对象。由于ProductFormprice属性是一个字符串,而其在Product类对应的是一个float,此处类型转换是必要的。第18章,我们将学习在Spring MVC中如何省去ProductForm对象,使编程更简单。
SaveProductControllerhandleRequest方法最后返回的ModelAndView模型包含了视图的路径模型名称以及模型product对象)。该模型将提供给目标视图,用于界面显示。

17.4.4 View

app17a应用程序中包含两个JSP页面:ProductForm.jsp页面和ProductDetails.jsp页面

ProductForm.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE HTML>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">
@import url(css/main.css);
</style>
</head>
<body>
<div id="global">
<form action="product_save.action" method="post">
<fieldset>
<legend>Add a product</legend>
<p>
<label for="name">Product Name: </label> <input
type="text" id="name" name="name" tabindex="1">
</p>
<p>
<label for="description">Description: </label> <input
type="text" id="description" name="description"
tabindex="2">
</p>
<p>
<label for="price">Price: </label> <input
type="text" id="price" name="price" tabindex="3">
</p>
<p id="buttons">
<input id="reset" type="reset" tabindex="4">
<input id="submit" type="submit" tabindex="5"
value="Add Product">
</p>
</fieldset>
</form>
</div>
</body>
</html>

此处不适合讨论HTMLCSS,但需要强调的是项目中的HTML是经过适当设计的,并且没有使用<table>来布局输入字段。

ProductDetails.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE HTML>
<html>
<head>
<title>Save Product</title>
<style type="text/css">
@import url(css/main.css);
</style>
</head>
<body>
<div id="global">
<h4>The product has been saved.</h4>
<p>
<h5>Details:</h5>
Product Name: ${product.name}<br /> Description:
${product.description}<br /> Price: $${product.price}
</p>
</div>
</body>
</html>

ProductDetails.jsp页面通过模型属性名“product”来访问由SaveProductController传入的Product对象。这里用JSP表达式语言来显示Product对象的各种属性。

17.4.5 测试应用

现在,在浏览器中输入如下URL来测试应用:
http://localhost:8080/app17a/product_input.action
会看到类似于下图所示的产品表单页面,在空字段中输入相应的值后单击Add Product(添加产品)按钮,
这里有一张图片
会在下一页中看到产品属性,如下图所示:
这里有一张图片

17.3 Controller接口

Spring 2.5版本前,开发一个控制器的唯一方法是实现org.springframework.web.servlet.mvc.Controller接口。这个接口公开了一个handleRequest方法。下面是该方法的签名:

1
2
ModelAndView handleRequest(HttpServletRequest request, 
HttpServletResponse response)

其实现类可以访问对应请求的HttpServletRequestHttpServletResponse,还必须返回一个包含视图路径或视图路径和模型的ModelAndView对象。
Controller接口的实现类只能处理一个单一动作Action),而一个基于注解的控制器可以同时支持多个请求处理动作并且无须实现任何接口。具体内容将在第18章中讨论。

17.2 Spring MVC的DispatcherServlet

回想一下,第16章建立了一个简单的MVC框架,包含一个充当调度员的Servlet。基于Spring MVC,则无须如此。Spring MVC中自带了一个开箱即用的DispatcherServlet,该Servlet的全名是org.springframework.web.servlet.DispatcherServlet
要使用这个Servlet,需要把它配置在部署描述符(web.xml文件)中,应用servletservlet-mapping元素,如下:

1
2
3
4
5
6
7
8
9
10
11
12
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- map all requests to the DispatcherServlet -->
<url-pattern>/</url-pattern>
</servlet-mapping>

servlet元素内的on-startup元素是可选的。如果它存在,则它将在应用程序启动时装载Servlet并调用该Servletinit方法。若它不存在,则在该servlet的第一个请求时加载。
DispatcherServlet将使用Spring MVC诸多默认的组件。此外,初始化时,它会寻找一个在应用程序的WEB-INF目录下的配置文件,该配置文件的命名规则如下:

1
servletName-servlet.xml

其中,servletName是在部署描述符中的DispatcherServlet的名称。如果这个DispatcherServlet的名字是SpringMVC,则在应用程序目录的WEB-INF下对应的文件是SpringMVC-servlet.xml

此外,也可以把Spring MVC的配置文件放在应用程序目录中的任何地方,你可以使用servlet定义的init-param元素,以便DispatcherServlet加载到该文件。init-param元素拥有一个值为contextConfigLocationparam-name元素,其param-value元素则包含配置文件的路径。例如,可以利用init-param元素更改默认的文件名和文件路径为WEB-INF/config/simple-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/simple-config.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

17.1 采用SpringMVC的好处

若基于某个框架来开发一个模型2的应用程序,我们要负责编写一个Dispatcher servlet和控制类。其中,Dispatcher servlet必须能够做如下事情:
(1)根据URI调用相应的action
(2)实例化正确的控制器类。
(3)根据请求参数值来构造表单bean
(4)调用控制器对象的相应方法。
(5)转发到一个视图(JSP页面)。

Spring MVC是一个包含了Dispatcher servletMVC框架。它调用控制器方法并转发到视图。这是使用Spring MVC的第一个好处:不需要编写Dispatcherservlet。以下是Spring MVC具有的能加速开发的功能列表:

  • Spring MVC中提供了一个Dispatcher Servlet,无须额外开发。
  • Spring MVC中使用基于XML的配置文件,可以编辑,而无须重新编译应用程序
  • Spring MVC实例化控制器,并根据用户输入来构造bean
  • Spring MVC可以自动绑定用户输入,并正确地转换数据类型。例如,Spring MVC能自动解析字符串,并设置floatdecimal类型的属性。
  • Spring MVC可以校验用户输入,若校验不通过,则重定向回输入表单。输入校验是可选的,支持编程以及声明方式。关于这一点,Spring MVC内置了常见的校验器。
  • Spring MVCSpring框架的一部分。可以利用Spring提供的其他能力。
  • Spring MVC支持国际化和本地化。支持根据用户区域显示多国语言。
  • Spring MVC支持多种视图技术。最常见的JSP技术以及其他技术包括VelocityFreeMarker

16.7 小结

本章,我们学习了基于MVC模式的模型2架构以及如何开发一个模型2应用。在模型2应用中,**JSP页面通常作为视图。当然,其他技术(如Apache VelocityFreeMarker)也可以作为视图。若采用JSP页面作为视图,则这些页面仅用来展示数据,并且没有其他脚本元素**。
本章,我们还构建了一个带校验器组件的简单MVC框架。

16.6 后端

app16aapp16bapp16c应用都演示了如何进行前端处理。那么,后端处理呢?我们当然需要处理数据库等。
应用MVC,可以在Controller类中调用后端业务逻辑。通常,需要若干封装了后端复杂逻辑的Service类。在Service类中,可以实例化一个DAO类来访问数据库。在Spring环境中,Service对象可以自动被注入到Controller实例中,而DAO对象可以自动被注入到Service对象中,后续章节将有演示。