模型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方式完成的。