《Spring MVC学习指南(第2版)》——2.5 校验器

简介: 在Web应用执行action时,很重要的一个步骤就是进行输入校验。校验的内容可以是简单的,如检查一个输入是否为空,也可以是复杂的,如校验信用卡号。实际上,因为校验工作如此重要,Java社区专门发布了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,将Java世界的输入检验进行标准化。

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

2.5 校验器

在Web应用执行action时,很重要的一个步骤就是进行输入校验。校验的内容可以是简单的,如检查一个输入是否为空,也可以是复杂的,如校验信用卡号。实际上,因为校验工作如此重要,Java社区专门发布了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,将Java世界的输入检验进行标准化。现代的MVC框架通常同时支持编程式和声明式两种校验方法。在编程式中,需要通过编码进行用户输入校验,而在声明式中,则需要提供包含校验规则的XML文档或者属性文件。

注意
 

即使您可以使用HTML5或JavaScript执行客户端输入验证,也不要依赖它,因为精明的用户可以轻松地绕过它。始终执行服务器端输入验证!
本节的新应用(appdesign3)扩展自appdesign1,但多了一个ProductValidator类(见清单2.8)。

清单2.8 ProductValidator类

package appdesign3.validator;
import java.util.ArrayList;
import java.util.List;
import appdesign3.form.ProductForm;

public class ProductValidator {
  public List<String> validate(ProductForm productForm) {
    List<String> errors = new ArrayList< >();
    String name = productForm.getName();
    if (name == null || name.trim().isEmpty()) {
      errors.add("Product must have a name");
    }
    String price = productForm.getPrice();
    if (price == null || price.trim().isEmpty()) {
      errors.add("Product must have a price");
    } else {
      try {
        Float.parseFloat(price);
      } catch (NumberFormatException e) {
        errors.add("Invalid price value");
      }
    }
    return errors;
  }
}

注意
 

ProductValidator类中有一个操作ProductForm对象的validate方法,确保产品的名字非空,其价格是一个合理的数字。validate方法返回一个包含错误信息的字符串列表,若返回一个空列表,则表示输入合法。
现在需要让控制器使用这个校验器了,清单2.9展示了一个更新后的ControllerServlet,注意黑体部分。

清单2.9 新版的ControllerServlet类

package appdesign3.controller;
import java.io.IOException;
import java.util.List;
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 appdesign3.action.SaveProductAction;
import appdesign3.form.ProductForm;
import appdesign3.model.Product;
import appdesign3.validator.ProductValidator;
import java.math.BigDecimal;

@WebServlet(name = "ControllerServlet", urlPatterns = {
    "/input-product", "/save-product"})
public class ControllerServlet extends HttpServlet {

  private static final long serialVersionUID = 98279L;
  @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 case 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);
    String dispatchUrl = null;

    if ("input-product".eauals(action)) {
      // no action class, Hrele is nathing to be done
      dispatchUrl = "/jsp/ProductForm.jsp";
    } else if ("save-product"-eaoals(action)) {
      // instantiatle action class
      ProductForm productForm = new ProductForm();
      // populate action properties
      productForm.setName(
          request.getParameter("name"));
      productForm.setDescription(
          request.getParameter("description"));
      productForm.setPrice(request.getParameter("price"));

      // validate ProductForm
      ProductValidator productValidator = new 
          ProductValidator();
      List< String> errors = 
          productValidator.validate(productForm);
      if(errors.isEmpty()){
        // create product from productForm
        Product product = new Product();
        product.setName(productForm.getName());
        product.setDescription(
            productForm.getDescription());
        product.setPrice(new BigDecimal (productForm.getPrice()));

        // no validation error 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"; 
      } else {
        request.setAttribute("errors", errors);
        request.setAttribute("form", productForm);
        dispatchUrl = "/jsp/ProductForm.jsp"; 
      } 
    }
    // forward to a new
    if (dispatchUrl != null) {
      RequestDispatcher rd = 
          request.getRequestDispatcher(dispatchUrl);
      rd.forward(request, response);
    }
  }
}

新版的ControllerServlet类添加了初始化ProductValidator类并调用其validate方法的代码。

// validate ProductForm
    ProductValidator productValidator = new
        ProductValidator();
    List<String> errors =
        productValidator.validate(productForm);

validate方法接受一个ProductForm参数,它封装了输入到HTML表单的产品信息。如果不用ProductForm,则应将ServletRequest传递给验证器。

如果验证成功,validate方法返回一个空列表,在这种情况下,将创建一个产品并传递给SaveProductAction,然后,控制器将Product存储在ServletContext中,并转发到ProductDetails.jsp页面,显示产品的详细信息。如果验证失败,控制器将错误列表和ProductForm存储在ServletContext中,并返回到ProductForm.jsp。

if (errors.isEmpty()) {
    // create Product from ProductForm
    Product product = new Product();
    product.setName(productForm.getName());
    product.setDescription(
        productForm.getDescription());
    product.setPrice(new BigDecimal(productForm.getPrice()));
    // no validation error, execute action method
    SaveProductAction saveProductAction = new
        SaveProductAction();
    saveProductAction.save(product);
    // store action in a scope variable for the view
    request.setAttribute("product", product);
    dispatchur1="/jsp/ProductDetails.jsp";
  } else {
    request.setAttribute("errors", errors);
    request.setAttribute("form", productForm);
    dispatchur1="/jsp/ProductForm.jsp";
  }

现在,需要修改appdesign3应用的ProductForm.jsp页面(见清单2.10),使其可以显示错误信息以及错误的输入。

清单2.10 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> 
  ${empty requestScope.errors? "" : "< p style='color:red'>"
   += "Error(s)!"
   += "< ul>"} 
  < !--${requestScope.errors.stream().map(
     x -> "-->< li>"+=x+="< /li>< !--").toList()}--> 
  ${empty requestScope.errors? "" : "< /ul>< /p>"} 
  < label>
    < span>Product Name :< /span>
    < input id="name" type="text" name="name" 
      placeholder="The complete product name" 
      value="${form.name}"/> 
  < /label>
  < label> 
    < span>Description :< /span>
    < input id="description" type="text" name="description" 
      placeholder="Product description" 
      value="${form.description}"/> 
  < /label>
  < label> 
    < span>Price :< /span>
    < input id="price" name="price" type="number" step="any" 
      placeholder="Product price in #.## format" 
      value="${form.price}"/> 
  < /label>
  < label> 
    < span> < /span> 
    < input type="submit"/>
  < /label> 
< /form>
< /body>
< /html>

现在访问input-product,测试appdesign3应用。

http://localhost:8080/appdesgin3/input-product
若产品表单提交了无效数据,页面将显示相应的错误信息。图2.6显示了包含两条错误信息的ProductForm页面。

screenshot

图2.6 包含两条错误信息的ProductForm页面

相关文章
|
2月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
9月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
534 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
9月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
833 0
|
9月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
523 0
|
9月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
462 0
|
9月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
391 0
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
373 0
|
5月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
169 0
|
5月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
282 0
|
11月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
618 29