模型2和MVC模式
java Web应用开发中有两种设计模式,为了方便,分别称为模型1和模型2。模型1是页面中心,适用于小应用开发。而模型2基于mvc模式,是java Web应用的推荐架构(简单类型的应用除外),将会讨论模型2,并展示3个不同示例应用。第一个应用是一个基于模型2应用,采用servlet作为控制器,第二个应用引入了控制,第三个应用引入了验证控件来校验用户的输入。
2.1 模型1 介绍
第一次学习jsp,通常通过连接方式进行jsp页面间的跳转,这种方式非常直接,但在中型和大型应用中,这种方式会带来维护上的问题。修改一个jsp页面的名字,会导致大量页面中的链接需要修正,因此,实践中并不推荐模型1(但仅2、3个页面的应用除外)。
2.2 模型2 介绍
模型2基于模型-视图-控制器(MVC)模式,该模式是Smalltalk-80用户交互的核心概念,那时还没有设计模式的说法,当时称之为mvc范式。
).不同与EJB等,POJO是一个普通对象。实践中会采用一个javabean来持有模型状态,并将业务逻辑放到一个action类中。一个javabean必须拥有一个无参的构造器。通过get/set方法来访问参数,同时支持持久化。
每个HTTP请求都发送给控制器,请求中的URI标识出对应的action。acation代表了应用可以执行的一个操作。一个提供了action的java对象成为action对象。一个action类可以支持多个action(在springmvc以及struts2中)或者一个action
看似简单的操作可能需要多个action。如,向数据库添加一个产品,需要2个action。
1、显示一个“添加产品”的表单,以便用户能输入产品信息。
2、将表单信息保存到数据库中。
如前述,我们需要通过URI方式告诉控制器执行相应的action。例如,通过发送类似如下URI,来显示“添加产品”表单。
http://domian/appName/product_input
通过类似如下URI,来保存产品
http://domian/appName/product_save
控制器会解析URI并调用相应的action,然后将模型对象放到视图可以访问的区域(以便服务端数据可以展示在浏览器上)。最后,控制器利用RequestDispatcher跳转到视图(JSP页面)。在JSP页面中,用表达式语言以及定制标签显示数据。PS:调用RequsetDispatcher.forward方法并不会停止执行剩余代码,因此,若forward方法不是最后一行代码,则应显式的返回。
2.3模型2值servlet控制器
示例应用名为app02a ,其功能设定为输入一个产品信息,具体为:用户填写产品表单,并提交,示例应用保存产品并展示一个完成页面。显示已保存的产品信息。
应用支持如下两个action。
1、展示“添加产品”表单。该action发送一个输入表单到浏览器上,其对应的RUI应包含字符串product_input
2、保存产品并返回完成页面,对应的RUI应包含字符串product_save
1、一个product类,作为product的领域对象。
2、一个productForm类,封装了HTML表单的输入项。
3、一个ControllerServlet类,本示例的控制器。
4、一个SaveProductAction类。
5、两个jsp页面(Product.jsp和ProductDetail.jsp)作为view
6、一个CSS文件,定义了两个jsp页面的显示风格。
所有jsp文件都放置在web-inf目录下,因此无法被直接访问。
2.3.1 product类
product实例是封装了产品信息的javabean,product类包含3个属性,productName、description和price
package cn.app02a.domain;
import java.io.Serializable;
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String description;
private float price;
@Override
public String toString() {
return "Product [name=" + name + ", description=" + description
+ ", price=" + 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;
}
}
product类实现了java.io.Serializable接口,其实例可以安全的将数据保存到HttpSession中,根据Serializable要求,Product实现了一个serialVersionUID熟悉。
2.3.2ProductFrom类
表单类与HTML表单相映射,是后则在服务端的代表。productForm类包含了一个产品的字符串值。productForm类看上去和product类相似,这就引出了一个问题:productForm类是否有存在的必要。
实际上,表单对象会传递ServletRequest给其他组件,类似Validator。而ServletRequest是一个servlet层的对象,不应当暴露给应用的其他层。另一个原因是,当数据校验失败时,表单对象将用于保存和展示用户在原始表单上的输入。
PS:大部分情况下,一个表单类不需要实现Serializable接口,因为表单对象很少保存在HttpSession中。
package cn.app02a.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;
}
}
2.3.3
ControllerServlet类
controllerServlet类继承自javax.servlet.http.HttpServlet类。其doGet和doPost方法最终调用process方法,该方法是整个servlet控制器的核心。可能有人好奇为何这个servlet控制器被命名为controllerServlet,实际上,这里遵从了一个约定,所有的servlet的类命名都带有servlet后缀。
package cn.app02a.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 cn.app02a.domain.Product;
import cn.app02a.form.ProductForm;
public class ControllerServlet extends HttpServlet {
private static final long serialVersionUID = -6714354047703169167L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
private void process(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String uri = request.getRequestURI();
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();
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 dispatchUri = null;
if (action.equals("product_input.action")) {
dispatchUri = "/WEB-INF/jsp/ProductForm.jsp";
} else if (action.equals("product_save..action")) {
dispatchUri = "/WEB-INF/jsp/ProductDetail.jsp";
}
if (dispatchUri != null) {
RequestDispatcher rd = request.getRequestDispatcher(dispatchUri);
rd.forward(request, response);
}
}
}
...
import javax.servlet.annotation.WebServlet;
...
@WebServlet(name="ControllerServlet",urlPatterns={
"/product_input","/product_save"})
public vlass ControllerServlet extends HttpServlet{
...
}
ControllerServlet 的 process 方法处理所有输入请求,首先获取请求uri和action名称。
string uri = request.getRequestURI();
int lastIndex = uri.lastIndexOf("/");
String action = uri.substring(lastIndex+1);
在本示例应用中,action值只会是product_input或product_save.接着,process方法执行如下步骤。
1、创建并根据请求参数构建一个表单对象。product_save操作涉及3个属性:name、description、price。然后创建一个领域对象,并通过表单对其持久化到数据库中。
2、执行针对领域对象的业务逻辑,包括将其持久化到数据库中。
3、转发请求到视图(jsp页面)
process方法中判断action‘的if代码
//execute an action
if(action.equal("product_input")){
//there is nothing to be done
}else if (action.equals("product_save")){
...//codeto save product
}
对于product_input,无需任何操作,而针对product_save,则创建一个productForm对象和product对象,并将前者的属性复制到后者。再次,process方法实例化SaveProductAction类,并调用器save方法。
// create form
ProductForm productForm = new ProductForm();
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);
然后,将product对象放入HttpServicetRequest对象中,以便对应的视图能访问到。
request.setAttribute("product", product);
最后,process方法转到试图,如果action是product_input,则赚到ProductFrom.jsp 页面,否则转到ProductDetails.jsp页面。
String dispatchUri = null;
if (action.equals("product_input.action")) {
dispatchUri = "/WEB-INF/jsp/ProductForm.jsp";
} else if (action.equals("product_save..action")) {
dispatchUri = "/WEB-INF/jsp/ProductDetail.jsp";
}
if (dispatchUri != null) {
RequestDispatcher rd = request.getRequestDispatcher(dispatchUri);
rd.forward(request, response);
}
2.3.4 视图
示例应用包含两个jsp页面。第一个页面productFrom.jsp对应与product_input 操作,第二个页面ProductDetails.jsp对应product_save操作。ProductForm.jsp以及Product.
<!DOCTYPE html>
<html>
<head>
<title>Save Product</title>
<style type="text/css">
@import url(css/main.css);
</style>
</head>
<body>
<div id="global">
<form action="product_save.action" method="post"></form>
<fieldset>
<legend>add a product</legend>
<p>
<label for="name">Porduct 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="buttions">
<input id="reset" type="reset" tabindex="4"> <input
id="submit" type="submit" tabindex="5" value="Add Product">
</p>
</fieldset>
</div>
</body>
</html>
<!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/>
Product Description :${product.description}<br/>
Product Price :${product.price}<br/>
</p>
</div>
</body>
</html>
本示例应用作为一个模型2的应用,可以通过如下集中方式避免用户通过浏览器直接访问jsp页面,
2.3.5 测试应用
假定实例应用运行在本机的8080端口上,则通过如下URL 访问应用,
http://localhost:8080/app02a/product_input.action
完成输入后,表单提交到如下服务端URL上:
http://localhost:8080/app02a/product_save.action
ps:可以将servlet控制器作为默认主页,这是一个非常重要的特性。使得浏览器地址栏仅输入域名就可以访问到servlet控制器。这是无法通过filter方式完成的。