概述
前面两篇介绍了模型2架构的优势以及如何构建一个模型2应用。但是Spring MVC框架可以帮助我们快速的开发MVC应用。
Spring MVC体系概述
若基于某个框架来开发一个模型2的应用程序,我们要负责编写一个Dispatcher servlet和控制类。 其中Dispatcher servlet必须能够做到如下事情:
- 根据URI调用对应的action
- 实例化正确的控制器类
- 根据请求参数来构造表单bean
- 调用控制器对象的相应方法
- 转向一个视图
Spring MVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet负责截获请求并分派给相应的处理器处理。 SpringMVC框架包括注解驱动控制器、请求及响应的信息处理、视图解析、本地化解析、上传文件解析、异常处理及表单标签绑定等内容。
由于SpringMVC是基于Model2实现的框架,所以它底层的机制也是MVC,我们来看下SpringMVC的整体架构
从接收请求到返回相应,Spring MVC框架的众多组件有条不紊的完成内部的分工,在整个框架中,DispatcherServlet处于核心的位置,负责协调和组织不同组件以完成请求处理并返回响应的工作。
下面我们来分步解析下SpringMVC处理请求的整体过程
- 整个过程始于客户端发出的一个HTTP请求,Web应用服务器收到这个请求后,如果匹配DispatcherServlet的请求映射路径(web.xml中指定),则web容器将该请求交给DispatcherServlet处理
- DispatcherServlet接收到这请求后,将根据请求的信息(包括url,HTTP方法、请求报文头、请求参数、Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。 可将HandlerMapping看做是路由控制器,将Handler看做目标主机。值得注意的是,SpringMVC中并没有定义一个Handler接口,实际上任何一个Object都可以称为请求处理器。
- 当DispatcherServlet根据HandlerMapping得到对应请求的Handler后,通过HandlerAdpapter对Handler进行封装再以统一的适配器接口调用Handler.
- 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息
- ModelAndView并非真正的视图对象,DispatcherServlet通过ViewResolver完成逻辑视图和真实视图对象的解析工作
- 当得到真实的视图对象View后,DispatcherServlet就使用这个View对ModelAndView中的模型数据进行视图渲染
- 最终客户端得到相应消息可能是一个普通的html页面,也可能是要给XML或者JSON串,甚至是一张图片或者PDF文档等不同的媒体形式。
Spring MVC的DispatcherServlet
我们在前面两篇博文的例子中,servlet需要我们自己编写,基于Spring MVC ,则无需如此。 SpringMVC中自带了一个开箱即用的DispatcherServlet,全限定名为org.springframework.web.servlet.DispatcherServlet
使用DispatcherServlet
要想使用这个servlet,同样的也需要把它配置在部署描述符(web.xml)、应用servlet和servlet-mapping。如下所示
代码语言:javascript复制<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>springmvcservlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
servlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
- servlet元素内的on-startup是可选项,如果它存在,则它将在应用程序启动时装载servlet并调用他的init方法,若不存在,则该servlet的第一个请求时加载。
- DispatcherServlet将使用Spring MVC诸多默认的组件,此外,初始化的时候,它会寻找一个在应用程序的WEB-INF目录下的配置文件,该配置文件的命名规则 servletName-servlet.xml 。 其中servletName是在部署描述中的DispatcherServlet的名称,比如我们上述的配置文件
springmvc
,则在WEB-INF下对应的文件为springmvc-servlet.xml. - 此外,也可以把SpringMVC的配置文件放在应用程序目录中的任何地方,用servlet定义的init-param元素,以便DispatcherServlet加载到该文件。 init-param元素拥有一个contextConfigLocation的param-name元素,其param-value元素则包含配置文件的路径。如下所示
<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>
Controller接口
Spring2.5之前,开发一个控制器的唯一方法是实现org.springframework.web.servlet.mvc.Controller接口,该接口中有一个方法
代码语言:javascript复制ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
其实现类可以访问对应请求的HttpServletRequest 和HttpServletResponse ,同时还必须返回包含视图路径或者视图路径和模的ModelAndView 对象。
Controller接口实现类只能处理一个单一动作,而一个基于注解的控制器可以同时支持多个请求处理动作,并且无需实现任何接口(后续介绍这种主流的开发方式,这里先演示下实现接口的方式)
简单示例(实现接口的方式)
在这里我们只是简单的演示一下这种方式的用法,实际开发中并不推荐这样做。基于注解的方式后续介绍。
maven工程结构如下:
pom.xml添加依赖
代码语言:javascript复制<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.artisangroupId>
<artifactId>chapter03aartifactId>
<packaging>warpackaging>
<version>0.0.1-SNAPSHOTversion>
<name>chapter03a Maven Webappname>
<url>http://maven.apache.orgurl>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.3.9.RELEASEversion>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
<build>
<finalName>chapter03afinalName>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>2.5.1version>
<configuration>
<source>1.7source>
<target>1.7target>
<compilerArgument>-Xlint:allcompilerArgument>
<showWarnings>trueshowWarnings>
<showDeprecation>trueshowDeprecation>
configuration>
plugin>
plugins>
build>
project>
部署描述文件和Spring MVC 的配置文件
部署描述文件web.xml
代码语言:javascript复制<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>springmvcservlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
servlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/config/artisan-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>springmvcservlet-name>
<url-pattern>*.actionurl-pattern>
servlet-mapping>
web-app>
这里告诉Servlet/JSP容器,我们将使用SpringMVC的DispatcherServlet,并通过配置url-pattern原始来匹配.action结尾的URL映射到该servlet。 并通过init-param元素,加载特定目录下的Spring MVC 配置文件人,如果不配置的话,则SpringMVC的配置文件将在默认的/WEB-INF目录下,并且按照约定的命名规范。
Spring MVC配置文件
代码语言:javascript复制<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="com.artisan.springmvc.controller.InputProductController"/>
<bean name="/product_save.action" class="com.artisan.springmvc.controller.SaveProductController"/>
beans>
这里声明两个控制器类InputProductController和SaveProductController,并分别映射到/product_input.action和/product_save.action。
Controller
下面来编写“传统”风格的控制器,分别实现Spring提供的Controller接口
代码语言:javascript复制package com.artisan.springmvc.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
*
* @author Mr.Yang
* @Desc 实现Spring自身的Controller
*
*/
public class InputProductController implements Controller{
private static final Logger logger = Logger.getLogger(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页面
代码语言:javascript复制package com.artisan.springmvc.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import com.artisan.springmvc.form.ProductForm;
import com.artisan.springmvc.model.Product;
/**
*
* @author Mr.Yang
* @Desc 实现Spring自身的Controller
*
*/
public class SaveProductController implements Controller{
private static final Logger logger = Logger.getLogger(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
return new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp", "product",
product);
}
}
SaveProductController类的handleRequest方法中,首先用请求创建了一个ProductForm对象,然后它根据ProductForm对象创建Product对象 。 由于ProductForm的price是一个字符串,而在Product类中是一个float类型,因此需要转型(后面会介绍通过数据绑定省去ProductForm,这里暂不讲解)
SaveProductController的handleRequest方法返回最后的ModelAndView模型包含了视图的路径、模型名称和模型(Product对象),该模型将提供给目标视图,用于界面显示。
视图
两个JSP,绑定了CSS样式。 我们在web.xml配置url-pattern来匹配.action ,没有配置 / (所有请求)是因为如果配置了/,而没有配置静态资源过滤,这个CSS也会被拦截,因此这里暂时配置了拦截所有action结尾的请求。
ProductForm.jsp
代码语言:javascript复制<html>
<head>
<title>Add Product Formtitle>
<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 productlegend>
<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>
ProductDetails.jsp
代码语言:javascript复制<html>
<head>
<title>Save Producttitle>
<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>
ProductDetail.jsp页面通过模型属性名“product”来访问由SaveProductController传入的Product对象。这里用JSP表达式来显示Product对象的各种属性,后续会详解JSP 的EL表达式。
测试应用
输入URL: http://localhost:8080/chapter03a/product_input.action
输入相应的数据
提交数据后,url跳转到 http://localhost:8080/chapter03a/product_save.action
View Resolver
上个案例中,页面的跳转通过指定页面的路径来完成的,比如
代码语言:javascript复制new ModelAndView("/WEB-INF/jsp/ProductDetails.jsp", "product",
product);
其实,Spring MVC为我们提供了视图解析器,负责解析视图,现在我们来改造下。
在SpringMVC的配置文件中配置视图解析器
代码语言:javascript复制 id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
如上视图解析器前缀和后缀两个属性,这样一来,view的路径就缩短了。 比如仅需要ProductDetails,而无需设置/WEB-INF/jsp/ProductDetails.jsp,视图解析器就会自动增加前缀和后缀。
紧接着我们调整下控制器中的页面跳转
InputProductController 修改为
代码语言:javascript复制 return new ModelAndView("ProductForm");
SaveProductController修改为
代码语言:javascript复制 return new ModelAndView("ProductDetails", "product",
product);
重新运行测试,结果同上。
总结
本博文是Spring MVC的入门介绍,我们知道了使用SpringMVC,我们无需编写自己的DispatcherServlet,其传统风格的控制器开发方式是实现控制器接口。 从Spring2.5版本开始,Spring提供了基于注解的方式开发控制器,下篇博文介绍。
源码
代码已提交到github
https://github.com/yangshangwei/SpringMvcTutorialArtisan