Spring MVC-01循序渐进之Model 2和MVC
概述
Java Web开发中有两种设计模式
模型1:页面中心,适合小应用的开发
模型2:基于MVC模式,是Java Web的推荐框架
本篇博文我们将通过3个不同的示例来介绍模型2和MVC模式
第一个示例为基本的模型2应用,使用Servlet控制
第二个示例引入了控制器
第三个示例则引入了验证控件来检验用户的输入
模型1和模型2
还记得初次学习JSP,通常通过链接的方式进行页面之间的跳转,非常直接,但是如果一个JSP页面修改了名称,在大中型项目中会带来很大的维护问题,因此在实际的运用中并不推荐使用模型1。
模型2基于 模型—视图—控制器(MVC)模式,该模式是Smalltalk-80用户交互的核心概念。
一个实现了MVC模式的应用包含模型、视图和控制器3个模块。
视图负责应用的展示
模型封装了应用的数据和业务逻辑
控制器负责接收用户的输入,改变模型以及调整视图的显示
模型2中,Servlet或者filter都可以充当控制器。 在Spring MVC 和 Struts1中送Servlet作为控制器,而Struts2中则使用一个Filter作为控制器。
大部分视图都采用JSP作为应用的视图,当然也有其他技术。
而模型则采用POJO(Plain Old Java Object), 是一个普通对象。
实践中会采用一个JavaBean来持有模型状态,并将业务逻辑放到一个Action类中。一个JavaBean必须拥有一个无参的构造函数,通过get/set来访问参数,同时支持持久化。
模型2架构图
我们来看下模型2应用的架构图
我们来分析一下:
每个HTTP请求都发给控制器,请求中的URI标识出对应的action。 action代表了应用可以执行的一个操作。
一个Action的Java对象称为Action对象,一个Action可以支持多个action(在Spring MVC以及Struts2中)或者一个action(Structs1 中)。
举个简单的例子: 添加产品,需要两个action
- 显示“添加产品”表单,以便用户输入信息
- 将表单信息保存到数据库中
如上所述,我们需要通过URI的方式告诉控制器来执行相应的action, 比如通过
http://domain/appName/product_input
- 1
来显示“添加产品”表单
通过如下URI
http://domain/appName/product_save
- 1
来保存产品。
控制器会解析URI并调用对应的Action,然后将模型对象放到视图可以访问的区域(以便服务端数据可以展示在浏览器上),最后,控制器利用RequestDispatcher跳转到视图(JSP页面),用表达式语言以及定制标签来显示数据。
注意:调用RequestDispatcher.forward方法并不会停止执行剩余的代码,因此,若forward方法不是最后一行代码,则应该显式的返回
模型2之Servlet控制器
为了便于对模型二有个直观的了解,我们展示一个简单的应用。实际中模型二非常复杂。
我们的demo如下所示
http://localhost:8080/chapter02a/product_input.action
demo支持如下的两个action
展示“添加产品”表单。该action发送如上图中输入表单到浏览器,其对应的URI应包含字符串product_input
保存产品并返回如下图所示的完成页面,对应的URI必须包含字符串product_save
http://localhost:8080/chapter02a/product_save.action
工程结构:
- 一个Product类,作为product的领域对象
- 一个ProductForm类,封装了HTML表单的输入项
- 一个ControllerServlet类,控制器
- 两个JSP页面作为View(都在WEB-INF下确保无法直接访问到,必须通过Servlet来跳转)
- 一个CSS文件,定义了页面的显示风格
Product类
Product实例是一个封装了产品信息的JavaBean.
包含3个属性,name、description、price
package com.artisan.learnmvc.model;
import java.io.Serializable;
public class Product implements Serializable {
private static final long serialVersionUID = 748392348L;
private String name;
private String description;
private float price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
- 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
Product类实现了Serializable接口,其实例可以安全的将数据保存到HTTPSession中。
ProductForm类
ProductForm表单类与HTML表单相映射,是后者在服务端的代表。
ProductForm用来保存HttPServletRequest类中传递过来的值,因为HttPServletRequest只能是字符串,所以ProductForm类中所有的属性都是String 。注意Product中的price是float,ProductForm是String类型
ProductForm类看上去和Product类相同,那么有没有存在的必要呢?
实际上,表单对象会传到ServletRequest给其他组件,类似Validator,而ServletRequest是一个Servlet层的对象不应该暴露给应用的其它层。
另外一个原因是:当数据校验失败时,表单对象将用于保存和展示在原始表单上的输入。
注意:大部分情况,一个表单类不需要实现Serializable接口,因为表单对象很少保存在HttPSession中
package com.artisan.learnmvc.form;
public class ProductForm {
private String name;
private String description;
private String price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
}
}
- 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
ControllerServlet类
ControllerServlet类继承自javax.servlet.http.HttpServlet. 其中doGet和doPOST方法都最终调用自定义的process方法,该方法是整个Servlet控制器的核心。
按照约定,所有的Servlet类名称都带有servlet后缀。
package com.artisan.learnmvc.servlet;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.artisan.learnmvc.form.ProductForm;
import com.artisan.learnmvc.model.Product;
/**
*
* @author Mr.Yang
* @Desc ControllerServlet类继承自javax.servlet.http.HttpServlet.
* 其中doGet和doPOST方法都最终调用自定义的process方法,该方法是整个Servlet控制器的核心。
*
*/
public class ControllerServlet extends HttpServlet {
private static final long serialVersionUID = 1579L;
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
process(request, response);
}
@Override
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
process(request, response);
}
private void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
String uri = request.getRequestURI();
/*
* uri is in this form: /contextName/resourceName,
* for example: /chapter02a/product_input.
* However, in the event of a default context, the
* context name is empty, and uri has this form
* /resourceName, e.g.: /product_input
*/
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
// execute an action
if (action.equals("product_input.action")) {
// no action class, there is nothing to be done
} else if (action.equals("product_save.action")) {
// create form
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) {
}
// code to save product
// store model in a scope variable for the view
request.setAttribute("product", product);
}
// forward to a view
String dispatchUrl = null;
if (action.equals("product_input.action")) {
dispatchUrl = "/WEB-INF/jsp/ProductForm.jsp";
} else if (action.equals("product_save.action")) {
dispatchUrl = "/WEB-INF/jsp/ProductDetails.jsp";
}
if (dispatchUrl != null) {
RequestDispatcher rd =
request.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
}
}
}
- 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
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
如果是Servlet3.0 ,可以使用注解的方式,比如
@WebServlet(name="ControllerServlet",urlPatterns = {"/product_input","/product_save"})
public class ControllerServlet extends HttpServlet {
.....
}
- 1
- 2
- 3
- 4
我们来分析下ControllerServlet的process方法处理所有的输入请求。
1. 首先是获取请求的URI和action名称
String uri = request.getRequestURI();
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
- 1
- 2
- 3
2. 紧接着,process方法执行如下步骤
创建并根据请求参数构造一个表单对象。product_save操作涉及3个属性 name description 和price,然后创建一个领域对象,并通过表单对象设置相应属性。
执行针对领域对象的业务逻辑,包括持久化到数据库中
转发请求到视图
详见代码部分。
web.xml中配置Servlet
<?xml version="1.0" encoding="ISO-8859-1"?>
<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_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>ControllerServlet</servlet-name>
<servlet-class>com.artisan.learnmvc.servlet.ControllerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ControllerServlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
</web-app>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
视图
两个视图
- ProductForm.jsp 对应product_input的操作
- ProductDetails.jsp对应product_save的操作,通过EL表达式语言访问HTTPServletRequest中的product对象。
都采用了位于css目录下的main.css中的CSS样式进行控制
<!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>
- 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
<!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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
测试应用
我们使用tomcat的8080端口,运行服务,访问
http://localhost:8080/chapter02a/product_input.action
完成输入后,表单提交到服务器URL上
http://localhost:8080/chapter02a/product_save.action
Maven配置文件
<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.0</modelVersion>
<groupId>com.artisan</groupId>
<artifactId>chapter02a</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>chapter02a Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
<!-- provided 依赖只有在当JDK 或者一个容器已提供该依赖之后才使用。
例如, 如果你开发了一个web 应用,你可能在编译 classpath
中需要可用的Servlet API 来编译一个servlet,但是你不会想要在打包好的WAR 中包含这个Servlet API;
这个Servlet API JAR 由你的应用服务器或者servlet 容器提供。
已提供范围的依赖在编译classpath (不是运行时)可用。它们不是传递性的,也不会被打包。 -->
</dependency>
</dependencies>
<build>
<finalName>chapter02a</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 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
- 45
- 46
- 47
- 48
源码
代码已提交到github
https://github.com/yangshangwei/SpringMvcTutorialArtisan
文章来源: artisan.blog.csdn.net,作者:小小工匠,版权归原作者所有,如需转载,请联系作者。
原文链接:artisan.blog.csdn.net/article/details/78867677
- 点赞
- 收藏
- 关注作者
评论(0)