《Spring MVC学习指南(第2版)》——2.3 模型2之Servlet控制器
本节书摘来自异步社区《Spring MVC学习指南(第2版)》一书中的第2章,第2.3节,作者:【美】Paul Deck著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.3 模型2之Servlet控制器为了便于对模型2有一个直观的了解,本节将展示一个简单模型2应用。实践中,模型2的应用非常复杂。
示例应用名为appdesign1,其功能设定为输入一个产品信息。具体为:用户填写产品表单(图2.2)并提交;示例应用保存产品并展示一个完成页面,显示已保存的产品信息(见图2.3)。
图2.2 产品表单
图2.3 产品详细页
示例应用支持如下两个action。
(1)展示“添加产品”表单。该action将图2.2中的输入表单发送到浏览器上,其对应的URI应包含字符串input-product。
(2)保存产品并返回如图2.3所示的完成页面,对应的URI必须包含字符串save-product。
示例应用由如下组件构成:
(1)一个Product类,作为product的领域对象。
(2)一个ProductForm类,封装了HTML表单的输入项。
(3)一个ControllerServlet类,本示例应用的控制器。
(4)一个SaveProductAction类。
(5)两个JSP页面(ProductForm.jsp和Product Detail.jsp)作为视图。
(6)一个CSS文件,定义了两个JSP页面的显示风格。
示例应用目录结构如图2.4所示。
图2.4 app02a目录结构
下面详细介绍示例应用的每个组件。
2.3.1 Product类
Product实例是一个封装了产品信息的JavaBean。Product类(见清单2.1)包含3个属性:productName、description和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.3.2 ProductForm类
表单类与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;
2.3.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 ("input-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 Bigoecimal(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); }
ontrollerServlet的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个属性:name、description和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 ("input-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);
2.3.4 Action类
这个应用中只有一个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方法是一个空实现。我们会在本章后续章节中实现它。
2.3.5 视图
示例应用包含两个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 nbsp: /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 h5 Details: /h5 Product Name: ${product.name} br/ Description: ${product.description} br/ Price: $${product.price} /div /body /html
ProductForm.jsp页面包含了一个HTML表单。ProductDetails.jsp页面通过表达式语言(EL)访问HttpServletRequest所包含的product对象。
作为模型2的一个应用,本示例应用可以通过如下几种方式避免用户通过浏览器直接访问JSP页面。
将JSP页面都放到WEB-INF目录下。WEB-INF目录下的任何文件或子目录都受保护,无法通过浏览器直接访问,但控制器依然可以转发请求到这些页面。
利用一个servlet filter过滤JSP页面。
在部署描述符中为JSP页面增加安全限制。这种方式相对容易些,无需编写filter代码。
2.3.6 测试应用
假定示例应用运行在本机的8080端口上,则可以通过如下URL访问应用:
http://localhost:8080/appdesign1/input-product
浏览器将显示图2.2的内容。
完成输入后,表单提交到如下服务端URL上:
http://localhost:8080/appdesign1/save-product
注意
可以将servlet控制器作为默认主页。这是一个非常重要的特性,使得在浏览器地址栏中仅输入域名(如http://example.com),就可以访问到该servlet控制器,这是无法通过filter方式完成的。
Spring MVC控制器的14个小窍门(重点!要考!) 在本文中,我将分享一些使用Spring MVC框架编写控制器类的基本技术和最佳实践。大佬认证,童叟无欺。小一万文字,建议点赞收藏,反复观看。
Spring MVC 前端控制器 (DispatcherServlet)处理流程 Spring MVC 请求处理流程 用户发起请求,到 DispatcherServlet; 然后到 HandlerMapping 返回处理器链(包含拦截器和具体处理的 Handler); 调用处理器链的适配器 HandlerAdapter 来处理; 执行具体的方法,比如 @RequestMapper修饰的逻辑处理方法; 返回结果的视图解析器; 最后进行视图解析和渲染返回结果给用户;
异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
阿里特邀专家徐雷Java Spring Boot开发实战系列课程(第18讲):制作Java Docker镜像与推送到DockerHub和阿里云Docker仓库 立即下载
相关文章
- 面试(4)-spring-Spring面试题和答案
- Spring整合MyBatis
- spring整合curator实现分布式锁
- javax.servlet.ServletException: Servlet.init() for servlet springmvc threw exception
- org.springframework.web.servlet.DispatcherServlet' is not assignable to javax.servlet.Servlet
- 《Servlet、JSP和Spring MVC初学指南》——1.11 使用部署描述符
- 《Servlet、JSP和Spring MVC初学指南》——2.3 Cookies
- Spring中通配符
- spring计划任务,springMvc计划任务,Spring@Scheduled,spring定时任务
- Spring Boot 2 实战: 自定义 Servlet Filter 的两种方式
- Spring 异常处理的各种姿势
- servlet规范--Servlet 规范其实就是对 HTTP 协议做面向对象的封装
- 读书笔记——spring cloud 中 HystrixCommand的四种执行方式简述
- spring框架漏洞整理(Spring Boot Actuator相关漏洞)
- Spring之c3p0连接池xml配置和使用举例
- [Java][Spring][scurity]同步session控制,防止一个用户多次登录
- 【spring mvc】Spring MVC 的参数解析器ArgumentResolver阐述
- SPRING-SECURITY安全Web框架配置
- Spring学习笔记(一)---Bean生命周期(源码浅析)