Spring03:展现模型数据、处理及校验表单

1 展现模型数据

1.1 图解过程

在上一节“Spring-02:构建并运行基础Spring应用”中,我们运行的基础应用所展现的视图home.html还未包含任何领域类的信息,如下图:

img

想要构建真实场景的web应用,我们就需要在前端页面展示后端的相关数据并能与之进行交互,这和我们之前学习的JavaWeb是一样的:Javaweb总结-目前开发Javaweb的套路梳理。那么使用Spring是如何构建的?它和之前我们学习的JavaWeb有什么区别?如果你认真学习并构建过Javaweb项目,经过本节学习,Spring的构建过程就能够非常清晰地理解了,并能更深刻地体会到Spring的强大之处。之前的Javaweb项目的请求流:

img

img

典型的Spring MVC请求流

image-20220930230048131

上图可以看出,之前我们构建Javaweb应用需要进行以下内容:

  • 构建中央控制器DispatcherServlet;

  • 构建不同业务对应的Controller;

  • 构建每个Controller对应的方法,并在方法中调用service、DAO层;

  • 然后将他们之前的关系手动配置到xml文件中;

  • 需要手动构建web.xml,配置里面对应的前端页面、thymeleaf视图模板的前后缀;

  • ......

简单回顾了一下我们之前构建应用的过程,是不是很复杂?再看到“典型的Spring MVC请求流图”,整个过程感觉非常简洁,它具体是怎样的过程呢?接下来通过一个简单的web应用来具体了解。

1.2 构建组件

1.2.1 视图

①home.html

<a th:href="@{/design}" id="design">Design a taco</a>

在原先的home页面我们需要加上一个超链接,来跳转到我们处理业务的页面。(通常home页面是登录页面,·需要将用户账户密码等信息进行校验,此处我们不进行复杂的处理,直接跳转。)

②design.html

头部

<head>
  <title>Taco Cloud</title>
  <link rel="stylesheet" th:href="@{/styles.css}" />
</head>

表单

<form method="POST" th:object="${design}">

 

<span class="validationError"
      th:if="${#fields.hasErrors('ingredients')}"
      th:errors="*{ingredients}">Ingredient Error</span>

业务内容(为你的taco选择

<div class="ingredient-group" id="wraps">
<!-- tag::designateWrap[] -->
      <h3>Designate your wrap:</h3>
      <div th:each="ingredient : ${wrap}">
        <input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
        <span th:text="${ingredient.name}">INGREDIENT</span><br/>
      </div>
<!-- end::designateWrap[] -->
      </div>

thymeleaf视图模板

之前的Javaweb项目我们了解过,它是一种服务器端模板技术,这里不过多介绍,只简单总结一下thymeleaf的语法,帮助大家更好的理解。

语法 描述
html xmlns:th="http://www.thymeleaf.org" thymeleaf名称空间,需要在页面html标签中指定
@{/...} 指定上下文路径,因为项目在不同环境部署时,Web应用的名字有可能发生变化,需要使用它来动态地获取
${…} 操作请求域、session 域和应用域的对象等值
#{…} 基本内置对象 p th:text="${#request.getClass().getName()}">这里显示#request对象的全类名</p
*{…} 获取上下文中对象的属性值
~{…} 片段表达式,jsp:include 作用,引入公共页面片段

● th:text :设置当前元素的文本内容,相同功能的还有 th:utext,两者的区别在于前者不会转义 html 标签,后者会。优先级不高:order=7

● th:value:设置当前元素的 value 值,类似修改指定属性的还有 th:src,th:href。优先 级不高:order=6

● th:each:遍历循环元素,和 th:text 或 th:value 一起使用。注意该属性修饰的标签位 置,详细往后看。优先级很高:order=2

● th:if:条件判断,类似的还有 th:unless,th:switch,th:case。优先级较高:order=3

● th:insert:代码块引入,类似的还有 th:replace,th:include,三者的区别较大,若使 用不恰当会破坏 html 结构,常用于公共代码块提取的场景。优先级最高:order=1

● th:fragment:定义代码块,方便被 th:insert 引用。优先级最低:order=8

● th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4

● th:attr:修改任意属性,实际开发中用的较少,因为有丰富的其他 th 属性帮忙,类 似的还有 th:attrappend,th:attrprepend。优先级一般:order=5 深入学习请参考:https://blog.csdn.net/qq_44981526/article/details/126211200

或者直接根据本文中的示例结合自己的项目来理解

2.1.2 领域类

package tacos;
​
import lombok.Data;
import lombok.RequiredArgsConstructor;
​
@Data
@RequiredArgsConstructor
public class Ingredient {
  
  private final String id;
  private final String name;
  private final Type type;
  
  public static enum Type {
    WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
  }
​
}

2.1.3 控制器类

DesignTacoController

@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {
​
​
//end::head[]
​
@ModelAttribute
public void addIngredientsToModel(Model model) {
   List<Ingredient> ingredients = Arrays.asList(
     new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
     new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
     new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
     new Ingredient("CARN", "Carnitas", Type.PROTEIN),
     new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
     new Ingredient("LETC", "Lettuce", Type.VEGGIES),
     new Ingredient("CHED", "Cheddar", Type.CHEESE),
     new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
     new Ingredient("SLSA", "Salsa", Type.SAUCE),
     new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
   );
   
   Type[] types = Ingredient.Type.values();
   for (Type type : types) {
     model.addAttribute(type.toString().toLowerCase(),
         filterByType(ingredients, type));
   }
}
   
//tag::showDesignForm[]
  @GetMapping
  public String showDesignForm(Model model) {
    model.addAttribute("design", new Taco());
    return "design";
  }
​
//end::showDesignForm[]
​
/*
//tag::processDesign[]
  @PostMapping
  public String processDesign(Design design) {
    // Save the taco design...
    // We'll do this in chapter 3
    log.info("Processing design: " + design);
​
    return "redirect:/orders/current";
  }
​
//end::processDesign[]
 */
​
//tag::processDesignValidated[]
  @PostMapping
  public String processDesign(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model) {
    if (errors.hasErrors()) {
      return "design";
    }
​
    // Save the taco design...
    // We'll do this in chapter 3
    log.info("Processing design: " + design);
​
    return "redirect:/orders/current";
  }
​
//end::processDesignValidated[]
​
//tag::filterByType[]
  private List<Ingredient> filterByType(
      List<Ingredient> ingredients, Type type) {
    return ingredients
              .stream()
              .filter(x -> x.getType().equals(type))
              .collect(Collectors.toList());
  }
​
//end::filterByType[]
// tag::foot[]
}

OrderController

import javax.validation.Valid;
​
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
//end::baseClass[]
import org.springframework.web.bind.annotation.PostMapping;
//tag::baseClass[]
import org.springframework.web.bind.annotation.RequestMapping;
​
import lombok.extern.slf4j.Slf4j;
import tacos.Order;
​
@Slf4j
@Controller
@RequestMapping("/orders")
public class    OrderController {
  
//end::baseClass[]
//tag::orderForm[]
  @GetMapping("/current")
  public String orderForm(Model model) {
    model.addAttribute("order", new Order());
    return "orderForm";
  }
//end::orderForm[]
​
/*
//tag::handlePost[]
  @PostMapping
  public String processOrder(Order order) {
    log.info("Order submitted: " + order);
    return "redirect:/";
  }
//end::handlePost[]
*/
  
//tag::handlePostWithValidation[]
  @PostMapping
  public String processOrder(@Valid Order order, Errors errors) {
    if (errors.hasErrors()) {
      return "orderForm";
    }
    
    log.info("Order submitted: " + order);
    return "redirect:/";
  }
//end::handlePostWithValidation[]
  
//tag::baseClass[]
  
}
//end::baseClass[]

2 处理请求

2.2.1 Get

前端:

①发送get请求,访问http://localhost:8080/design

控制器:

//tag::showDesignForm[]
  @GetMapping
  public String showDesignForm(Model model) {
    model.addAttribute("design", new Taco());
    return "design";
  }

②Spring通过@Controller注解,发现DesignTacoController这个控制器,会自动创建他的实例,作为上下文中的bean;

③通过@RequestMapping("/design")注解,DesignTacoController处理"/design"开头的请求;

④通过@GetMapping注解,调用showDesignForm,创建一个taco对象,将配料类型列表作为属性添加到Model对象中(Model对象负责在控制器和展现数据的视图之间传递数据)

⑤通过我们定义的thymeleaf视图,将我们添加到model中的对象的属性渲染展现在页面上。

2.2.2 Post

前端

<form method="POST" th:object="${design}">

①在页面填写好表单后,点击submit按钮提交该表单发送post请求;

  public String processDesign(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model) {
    if (errors.hasErrors()) {
      return "design";
    }
​
    // Save the taco design...
    // We'll do this in chapter 3
    log.info("Processing design: " + design);
    
    return "redirect:/orders/current";
​
  }

②和@GetMapping类似,通过@PostMapping来处理POST请求,

③再通过@ModelAttribute注解,调用addIngredientsToModel(Model model)方法,读取我们填写的Ingredient属性值;

④重定向"redirect:/orders/current",跳转http://localhost:8080/orders/current页面(对应OrderController控制器)

3 校验表单

在processDesign方法参数(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model)中我们看到了@Valid注释,它表示我们要对在post请求的表单中提交的Taco对象进行验证。

3.1 Validation注解

Spring支持java的Bean校验API-JSR 380定义了一些注解用于做数据校验,这些注解可以直接设置在Bean属性上

  • @NotNull不允许外null对象

  • @AssertTrue是否为true

  • @Size约定字符串长度

  • @Min字符串的最小长度

  • @Max字符串的最大长度

  • @Email是否为邮箱格式

  • @NotEmpty不允许为null或者空,可以用于判断字符串、集合,比如Map,数组,List

  • @NotBlank不允许为null和空格

本案例应用如下:

@Data
public class Taco {
​
  // end::allButValidation[]
  @NotNull
  @Size(min=5, message="Name must be at least 5 characters long")
  // tag::allButValidation[]
  private String name;
  // end::allButValidation[]
  @Size(min=1, message="You must choose at least 1 ingredient")
  // tag::allButValidation[]
  private List<String> ingredients;
​
}

3.2 展现表单错误

<span class="validationError"
      th:if="${#fields.hasErrors('ingredients')}"
      th:errors="*{ingredients}">Ingredient Error</span>

这是由thymeleaf提供的便捷访问Error对象的方法,如果ingredients输入域存在错误,那么它会将<span>元素的占位符替换为校验信息。

效果如下:


参考资料:

《Spring实战第5版》

posted @ 2022-10-01 12:46  Fancy[love]  阅读(65)  评论(0编辑  收藏  举报