《Spring MVC学习指南(第2版)》——2.3 模型2之Servlet控制器

简介: 表单类与HTML表单相映射,是后者在服务端的代表。ProductForm类(见清单 2.2)包含了一个产品的字符串值。ProductForm类看上去同Product类相似,这就引出一个问题:ProductForm类是否有存在的必要。

本节书摘来自异步社区《Spring MVC学习指南(第2版)》一书中的第2章,第2.3节,作者:【美】Paul Deck著,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.3 模型2之Servlet控制器

为了便于对模型2有一个直观的了解,本节将展示一个简单模型2应用。实践中,模型2的应用非常复杂。

示例应用名为appdesign1,其功能设定为输入一个产品信息。具体为:用户填写产品表单(图2.2)并提交;示例应用保存产品并展示一个完成页面,显示已保存的产品信息(见图2.3)。

screenshot

图2.2 产品表单

screenshot

图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所示。

screenshot

图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>
  <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代码。
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方式完成的。

相关文章
|
2月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
137 29
|
2月前
|
XML 监控 前端开发
Spring Boot中的WebFlux编程模型
Spring WebFlux 是 Spring Framework 5 引入的响应式编程模型,基于 Reactor 框架,支持非阻塞异步编程,适用于高并发和 I/O 密集型应用。本文介绍 WebFlux 的原理、优势及在 Spring Boot 中的应用,包括添加依赖、编写响应式控制器和服务层实现。WebFlux 提供高性能、快速响应和资源节省等优点,适合现代 Web 应用开发。
157 15
|
3月前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
73 4
|
4月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
289 2
|
5月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
6月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
|
5月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
84 2
|
5月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
368 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
6月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
7月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】