【720科技SpringMVC】第二课:模式2和MVC模式

内容关键词:模式、数据、转换、校验、控制器

知识来源 720科技(张森鹏)

一、知识笔记

Java Web 应用开发中有两种设计模型,为了方便,分别称为模型 1 和模型 2。模型 1 是以页面中心,适合于小应用开发。而模型 2 基于 MVC 模式,是 Java Web 应用的推荐架构(简单类型的应用除外)。

模式1介绍:第一次学习 JSP,通常通过链接方式进行 JSP 页面间的跳转。这种方式非常直接,但在中型和大型应用中,这种方式会带来维护上的问题。修改一个 JSP 页面的名字,会导致页面中大量的链接需要修正。

模式2介绍:模型 2 基于模型—视图—控制器(MVC)模式,该模式是 Smalltalk-80 用户交互的核心概念,那时还没有设计模式的说法,当时称为 MVC 范式。一个实现 MVC 模式的应用包含模型、视图和控制器 3 个模块。视图负责应用的展示。模型封装了应用的数据和业务逻辑。控制器负责接收用户输入,改变模型以及调整视图的显示。

二、重要笔记

1.基本的模型 2 应用,采用 Servlet 作为控制器

示例应用名为 appdesign1,其功能设定为输入一个产品信息。具体为:用户填写产品表单(图2.2)并提交;示例应用保存产品并展示一个完成页面,显示已保存的产品信息(见图 2.3)。
示例应用支持如下两个
action
1)展示“添加产品”表单。该 action 将图 2.2 中的输入表单发送到浏览器上,其对应的URI 应包含字符串 input-product
2) 保存产品并返回如图 2.3 所示的完成页面,对应的 URI 必须包含字符串 save-product
2.2 产品表单:2.3 产品详细页:
示例应用由如下组件构成:

1)一个 Product 类,作为 product 的领域对象。

Product 实例是一个封装了产品信息的 JavaBeanProduct 类(见清单 2.1)包含 3 个属性:productNamedescription price
清单 2.1 Product
package appdesign1.model;
import java.io.Serializable;

import java.math.BigDecimal;
public class Product implements Serializable {
private static final long serialVersionUID = 748392348L;
private String name;
private String description;
private BigDecimal 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 BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
Product 类实现了 java.io.Serializable 接口,其实例可以安全地将数据保存到 HttpSession中。根据 Serializable 的要求, Product 实现了一个 serialVersionUID 属性。

2)一个 ProductForm 类,封装了 HTML 表单的输入项。

表单类与 HTML 表单相映射,是后者在服务端的代表。 ProductForm 类(见清单 2.2)包含了一个产品的字符串值。 ProductForm 类看上去同 Product 类相似,这就引出一个问题:ProductForm 类是否有存在的必要。
实际上,表单对象会传递
ServletRequest 给其他组件,类似 Validator(本章后面会介绍)。而 ServletRequest 是一个 Servlet 层的对象,不应当暴露给应用的其他层。
另一个原因是,当数据校验失败时,表单对象将用于保存和展示用户在原始表单上的输入。
2.5 节将会详细介绍应如何处理。
注意:
大部分情况下,一个表单类不需要实现 Serializable 接口,因为表单对象很少保存在HttpSession 中。
清单 2.2 ProductForm
package appdesign1.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;
}

} 

3)一个 ControllerServlet 类,本示例应用的控制器。

ControllerServlet 类(见清单 2.3)继承 自 javax.servlet.http.HttpServlet 类。其 doGet doPost方法最终调用 process 方法,该方法是整个 Servlet 控制器的核心。
可能有人好奇,为何这个
Servlet 控制器命名为 ControllerServlet,实际上,这里遵从了一个约定:所有 Servlet 的类名称都带有 Servlet 后缀。
清单 2.3 ControllerServlet
package appdesign1.controuer;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;
import appdesign1.action.SaveProductAction;
import appdesign1.form.Product Form;
import appdesign1.model.Product;
import java.math.BigDecimal;
@WebServlet(name = "ControllerServlet", urlPatterns = {
"/input-product", "/save-product"})
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: /appdesign1/input-product.
* However, in the event of a default context, the
* context name is empty, and uri has this form
* /resourceName, e.g.: /input-product
*/
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
// execute an action
String dispatchUrl = null;
if ("input-product".eauals(action)) {
// no action class, just forward
dispatchUrl = "/jsp/ProductForm.jsp";
} else if ("save-product".eauals(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(new Bigdecimal(productForm.getPrice()));
} catch (NumberFormatException e) {
}
// execute action method
SaveProductAction saveProductAction =
new SaveProductAction();
saveProductAction.save(product);
// store model in a scope variable for the view
request.setAttribute("product", product);
dispatchUrl = "/jsp/ProductDetails.jsp";
}
if (dispatchUrl != null) {
RequestDispatcher rd =
request.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
}
}
}
ControllerServlet process 方法处理所有输入请求。首先是获取请求 URI action 名称。
String uri = request.getRequestURI();
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
在本示例应用中, action 值只会是 input-product save-product
接着,
process 方法执行如下步骤。
1)创建并根据请求参数构建一个表单对象。 save-product 操作涉及 3 个属性: namedescription price。然后创建一个领域对象,并通过表单对象设置相应属性。
2)执行针对领域对象的业务逻辑。
3)转发请求到视图(JSP 页面)。
process 方法中判断 action if 代码块如下:
// execute an action
if ("input-product".eauals(action))) {
// no action class, just forward
dispatchUrl = "/jsp/ProductForm.jsp";
} else if ("save-product".eauals(action)) {
// instantiate action class

}
对于 input-product,无需任何操作,而针对 save-product,则创建一个 ProductForm 对象和 Product 对象,并将前者的属性值复制到后者。这个步骤中,针对空字符串的复制处理将留到稍后的 2.5 节处理。
再次,
process 方法实例化 SaveProductAction 类,并调用其 save 方法。
// 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(new BigDecimal(productForm.getPrice()));
} catch (NumberFormatException e) {
}
// execute action method
SaveProductAction saveProductAction =
new SaveProductAction();
saveProductAction.save(product);
然后,将 Product 对象放入 HttpServletRequest 对象中,以便对应的视图能访问到。
// store action in a scope variable for the view
request.setAttribute("product", product);
最后, process 方法转到视图,如果 action product_input,则转到 ProductForm.jsp 页面,否则转到 ProductDetails.jsp 页面。
// forward to a view
if (dispatchUrl != null) {
RequestDispatcher rd =
request.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
}

4)一个 SaveProductAction 类。

这个应用中只有一个 action 类, 负责将一个 product 持久化, 例如数据库。 这个 action 类名为 SaveProductAction(见清单 2.4)。
清单 2.4 SaveProductAction
package appdesign1.action;
public class SaveProductAction {
public void save(Product product) {
// insert Product to the database
}
}
在这个示例中, SaveProductAction 类的 save 方法是一个空实现。我们会在本章后续章节中实现它。

5)两个 JSP 页面(ProductForm.jsp ProductDetail.jsp)作为视图。

示例应用包含两个 JSP 页面。第一个页面 ProductForm.jsp 对应于 input-product 操作,第二个页面 ProductDetails.jsp 对应于 save-product操作。 ProductForm.jsp 以及 ProductDetails.jsp页面代码分别见清单 2.5 和清单 2.6

清单 2.5 ProductForm.jsp
<!DOCTYPE html>
<html>
<head>
<title>Add Product Form</title>
<style type="text/css">@import url(css/main.css);</style>
</head>
<body>
<form method="post" action="save-product ">
<h1> Add Product

<span>Please use this form to enter product details</span>
</h1>
<label>
<span>Product Name: </span>
<input id="name" type="text" name="name"
placeholder="The complete product name">
</label>
<label>
<span>Description: </span>
<input id="description" type="text" name="description"
placeholder="Product description">
</label>
<label>
<span>Price: </label>
<input id="price" name="price" type="number" step="any"
placeholder="Product price in #.## format">
</label>
<label>
<span> : </span>
<input type="submit">
</label>
</form>
</body>
</html>
注意
不要用 HTML Tabel 来布局表单, 用 CSS
注意
价格输入域的 step 属性要求浏览器允许输入小数数字。
清单 2.6 ProductDetails.jsp
<!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>
ProductForm.jsp页面包含了一个 HTML表单。ProductDetails.jsp页面通过表达式语言(EL)访问 HttpServletRequest 所包含的 product 对象。
作为模型
2 的一个应用,本示例应用可以通过如下几种方式避免用户通过浏览器直接访问 JSP 页面。
JSP页面都放到 WEB-INF目录下。WEB-INF目录下的任何文件或子目录都受保护,无法通过浏览器直接访问,但控制器依然可以转发请求到这些页面。
利用一个 servlet filter 过滤 JSP 页面。
在部署描述符中为 JSP 页面增加安全限制。这种方式相对容易些,无需编写 filter代码。

6)测试应用

假定示例应用运行在本机的 8080 端口上,则可以通过如下 URL 访问应用:
http://localhost:8080/appdesign1/input-product
浏览器将显示图 2.2 的内容。
完成输入后,表单提交到如下服务端
URL 上:
http://localhost:8080/appdesign1/save-product
注意
可以将 servlet 控制器作为默认主页。这是一个非常重要的特性,使得在浏览器地址栏中仅输入域名(如 http://example.com),就可以访问到该 servlet 控制器,这是无法通过 filter 方式完成的。

2.基本的模型 2 应用, 采用了 Filter 作为控制器

虽然 servlet 是模型 2 应用程序中最常见的控制器,但过滤器也可以充当控制器。但请注意, 过滤器没有作为欢迎页面的权限。仅输入域名时不会调用过滤器分派器。 Struts 2 使用过滤器作为控制器, 是因为该过滤器也用于提供静态内容。
下面的例子(
appdesign2) 是一个采用 filter 分发器的模型 2 应用, 目录结构如图 2.5 所示。
JSP 页面和 Product 类同 appdesign1 相同, 但没有采用 servlet作为控制器,而是使用了一个名为 FilterDispatcher的过滤器(见清单 2.7)。
清单 2.7 DispatcherFilter
package appdesign2.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import appdesign2.action.SaveProductAction;
import appdesign2.form.ProductForm;
import appdesign2.model.Product;
import java.math.BigDecimal;
@WebFilter(filterName = "DispatcherFilter",
urlPatterns = { "/*" })
public class DispatcherFilter implements Filter {
@Override
public void init(FilterConfig filterConfig)
throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String uri = req.getRequestURI();

/*
* uri is in this form: /contextName/resourceName, for
* example /appdesign2/input-product. However, in the
* case of a default context, the context name is empty,
* and uri has this form /resourceName, e.g.:
* /input-product
*/
// action processing
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex + 1);
String dispatchUrl = null;
if ("input-product".equals(action)) {
// do nothing
dispatchUrl = "/jsp/ProductForm.jsp";
}else if("save-product".equals(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(product.getDescription());
try {
product.setPrice(new BigDecimal(productForm.getPrice()));
} catch (NumberFormatException e) {
}
// execute action method
SaveProductAction saveProductAction =
new SaveProductAction();
saveProductAction.save(product);
// store model in a scope variable for the view
request.setAttribute("product", product);
dispatchUrl = "/jsp/ProductDetails.jsp";
}
// forward to a view
if (dispatchUrl != null) {
RequestDispatcher rd = request
.getRequestDispatcher(dispatchUrl);
rd.forward(request, response);
} else {
// let static contents pass
filterChain.doFilter(request, response);
}
}
}

doFilter 方法的内容同 appdesign1 process 方法。
由于过滤器的过滤目标是包括静态内容在内的所有网址,因此,若没有相应的
action 则需要调用 filterChain.doFilter()
} else {
// let static contents pass
filterChain.doFilter(request, response);
}
要测试应用,可以用浏览器访问如下 URL

http://localhost:8080/appdesign2/input-product 

三、学习指导

SpringMVC学习指南

猜你喜欢

转载自blog.csdn.net/qq_41950122/article/details/80024707