16.3 模型2之Servlet控制器 为了便于对模型2有一个直观的了解,本节将展示一个简单模型2应用。实践中,模型2应用非常复杂。 示例应用名为app16a
,其功能设定为输入一个产品信息。具体为: 用户填写产品表单(见图16.2)并提交;示例应用保存产品并展示一个完成页面,显示已保存的产品信息(见图16.3)。 示例应用支持如下两个action
: (1)展示“添加产品”表单。该action
发送图16.2中的输入表单到浏览器上,其对应的URI
应包含字符串product_input
。 (2)保存产品并返回图16.3所示的完成页面,对应的URI
必须包含字符串product_save
。 图16.3 产品详细页 示例应用app16a
由如下组件构成: (1)一个Product
类,作为product
的领域对象。 (2)一个ProductForm
类,封装了HTML
表单的输入项。 (3)一个ControllerServlet
类,本示例应用的控制器。 (4)一个SaveProductAction
类。 (5)两个JSP
页面(ProductForm.jsp
和ProductDetail.jsp
)作为view
。 (6)一个CSS
文件,定义了两个JSP
页面的显示风格。app16a
结构如图16.4所示。
图16.4 app16a目录结构
所有的JSP
文件都放置在WEB-INF
目录下,因此无法被直接访问。下面详细介绍示例应用的每个组件。
16.3.1Product类 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 package app16a.domain;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; } }
Product
类实现了java.io.Serializable
接口 ,其实例可以安全地将数据保存到HttpSession
中 。根据Serializable
要求,Product
实现了一个serialVersionUID
属性。
表单类与HTML
表单相映射,是后者在服务端的代表。ProductForm
类(见清单2.2)包含了一个产品的字符串值。ProductForm
类看上去与Product
类相似,这就引出一个问题:ProductForm
类是否有存在的必要。 实际上,表单对象会传递ServletRequest
给其他组件,类似Validator
(本章后续段落会介绍)。而ServletRequest
是一个Servlet
层的对象,不应当暴露给应用的其他层。 另一个原因是,当数据校验失败时,表单对象将用于保存和展示用户在原始表单上的输入。16.5节将会详细介绍应如何处理。 注意: 大部分情况下,一个表单类不需要实现Serializable
接口,因为表单对象很少保存在HttpSession
中。
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 package app16a.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; } }
16.3.3 ControllerServlet类 ControllerServlet
类(见清单16.3)继承自javax.servlet.http.HttpServlet
类,其doGet
和doPost
方法最终调用process
方法,该方法是整个servlet
控制器的核心。 可能有人好奇为何这个Servlet
控制器被命名为ControllerServlet
,实际上,这里遵从了一个约定:所有Servlet
的类名称都带有Servlet
后缀。
ControllerServlet类 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 package app16a.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 app16a.domain.Product;import app16a.form.ProductForm;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(); int lastIndex = uri.lastIndexOf("/" ); String action = uri.substring(lastIndex + 1 ); if (action.equals("product_input.action" )) { } else if (action.equals("product_save.action" )) { ProductForm productForm = new ProductForm(); productForm.setName(request.getParameter("name" )); productForm.setDescription( request.getParameter("description" )); productForm.setPrice(request.getParameter("price" )); Product product = new Product(); product.setName(productForm.getName()); product.setDescription(productForm.getDescription()); try { product.setPrice(Float.parseFloat( productForm.getPrice())); } catch (NumberFormatException e) {} request.setAttribute("product" , product); } 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); } } }
若基于Servlet 3.0
规范,则可以采用注解的方式,而无须在部署描述符中进行映射:
1 2 3 4 5 6 7 8 ... import javax.servlet.annotation.WebServlet;... @WebServlet (name = "ControllerServlet" , urlPatterns = { "/product_input" , "/product_save" }) public class ControllerServlet extends HttpServlet { ... }
ControllerServlet
的process
方法处理所有输入请求。首先是获取请求URI
和action
名称:
1 2 3 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
代码块如下:
1 2 3 4 5 6 7 if (action.equals("product_input" )) { } else if (action.equals("product_save" )) { ... }
对于product_input
,无须任何操作,而针对product_save
,则创建一个ProductForm
对象和Product
对象,并将前者的属性值复制到后者。这个步骤中,针对空字符串的复制处理将留到稍后的“校验器”一节处理。 再次,process
方法实例化SaveProductAction
类,并调用其save
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ProductForm productForm = new ProductForm(); productForm.setName(request.getParameter("name" )); productForm.setDescription( request.getParameter("description" )); productForm.setPrice(request.getParameter("price" )); Product product = new Product(); product.setName(productForm.getName()); product.setDescription(product.getDescription()); try { product.setPrice(Float.parseFloat( productForm.getPrice())); } catch (NumberFormatException e) { } SaveProductAction saveProductAction = new SaveProductAction(); saveProductAction.save(product); request.setAttribute("product" , product);
然后,将Product
对象放入HttpServletRequest
对象中,以便对应的视图能访问到:
1 2 request.setAttribute("product" , product);
最后,process
方法转到视图,如果action
是product_input
,则转到ProductForm.jsp
页面,否则转到ProductDetails.jsp
页面:
1 2 3 4 5 6 7 8 9 10 11 12 String dispatchUrl = null ; if (action.equals("Product_input" )) { dispatchUrl = "/WEB-INF/jsp/ProductForm.jsp" ; } else if (action.equals("Product_save" )) { dispatchUrl = "/WEB-INF/jsp/ProductDetails.jsp" ; } if (dispatchUrl != null ) { RequestDispatcher rd = request.getRequestDispatcher(dispatchUrl); rd.forward(request, response); }
16.3.4 视图 示例应用包含两个JSP
页面。第一个页面ProductForm.jsp
对应于product_input
操作,第二个页面ProductDetails.jsp
对应于product_save
操作。ProductForm.jsp
以及ProductDetails.jsp
页面代码分别见清单16.4和清单16.5。
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 <!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>
ProductDetails.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <!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
表单。页面没有采用HTML
表格方式进行布局,而采用了位于css
目录下的main.css
中的CSS
样式表进行控制。ProductDetails.jsp
页面通过表达式语言(EL
)访问HttpServletRequest
所包含的product
对象。本书第8章“表达式语言”会详细介绍。 本示例应用作为一个模型2的应用,可以通过如下几种方式避免用户通过浏览器直接访问JSP
页面:
将JSP
页面都放到WEB-INF
目录下。WEB-INF
目录下的任何文件或子目录都受保护,无法通过浏览器直接访问,但控制器依然可以转发请求到这些页面。
利用一个servlet filter
过滤JSP
页面。
在部署描述符中为JSP
页面增加安全限制。这种方式相对容易些,无须编写 filter
代码。
16.3.5 测试应用 假定示例应用运行在本机的8080端口上,则可以通过如下URL
访问应用:http://localhost:8080/app16a/product_input.action 浏览器将显示图16.2的内容。 完成输入后,表单提交到如下服务端URL
上:
1 http://localhost:8080/app16a/product_save.action
注意: 可以将Servlet
控制器作为默认主页。这是一个非常重要的特性,使得在浏览器地址栏中仅输入域名(如http://example.com),就可以访问到该`Servlet`控制器,这是无法通过`filter`方式完成的。
原文链接: 16.3 模型2之Servlet控制器