《Spring in Action》第2章-创建一个Web应用

开发Web应用

建立Domain

Domain:一个应用的Domain就是这个应用处理的主题领域,Domain类就是对这个主题领域的抽象。在TacoCloud应用中,我们希望提供一个用户自己设计Taco的功能,那么Taco的成分类就属于该应用的Domain类。

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
    }
}

上面定义了一个简单的Ingredient类,使用了@Data注解,这个注解由Lombok提供,我们需要在pom中添加lombok的依赖,并在IED中添加lombok插件。使用lombok,我们就不必再为普通的pojo手动的添加getter和setter方法以及一些必要的构造器了。在程序运行时,Lombok会自动的为我们加上这些方法。Lombok文档

创建一个Controller

@Controller
@Slf4j
@RequestMapping("/design")
 public String showDesignForm(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));
	    }
	    model.addAttribute("design",new Taco());
	    return "design";
	}

	List<Ingredient> filterByType(List<Ingredient> ingredients, Type type){
	    List<Ingredient> result = new ArrayList<>();
	    for (int i = 0; i < ingredients.size(); i++) {
	        Ingredient ingredient = ingredients.get(i);
	        if (type == ingredient.getType()) {
	            result.add(ingredient);
	        }
	    }
	    return result;
	}

上面的代码中,@Slf4j注解是由Lombok提供的,在运行时自动生成一个SLF4J@RequestMapping("/design")注解在class层,这个类中的方法将会处理以/design开头的请求。Model用于在Controller和view之间传递数据。放入Model的数据会被复制到Servlet响应参数中,视图使用的是Servlet响应参数的数据。

设计视图

<!DOCTYPE html>
<!-- 将表单数据存入对象中 -->
<form method="post" th:object="${design}">
    <div class="grid">
        <div class="ingredient-group" id="wraps">
            <h3>Design your wrap:</h3>
			<!-- 遍历wrap集合 -->
            <div th:each="ingredient : ${wrap}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}">
                <span th:text="${ingredient.name}">INGREDIENT</span>
                <br>
            </div>
        </div>
    </div>
    <div>
        <h3>Name your taco creation:</h3>
        <input type="text" th:field="*{name}">
        <br>
        <button>Submit your taco</button>
    </div>
</form>
</body>
</html>

视图使用Thymeleaf模板。我们在html文件中加入th命名空间:<html xmlns:th="http://www.thymeleaf.org">,然后开始使用Thymeleaf模板。

Thymeleaf的标准表达语法

  • 基本语法
  1. 变量表达式: ${...}

    变量表达式实际上是OGNL,在Spring MVC 应用中,OGNL被替换为SpringEL(语法与OGNL十分相似)。

  2. 选择表达式: *{...}

    选择表达式与th:object=${objectName}一起使用,在上面的html中,<input type="text" th:field="*{name}"> 等价于<input type="text" th:field="${design.name}">

  3. 消息表达式: #{...}

    使用消息表达式让国际化变得简单,我们可以引用外部的.properties文件中定义的值来替换html页面内容。在Spring中使用消息表达式,需要注册org.springframework.context.support.ResourceBundleMessageSourcebean:

    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("message");//这里设置properties文件名的前缀
        return messageSource;
    }
    

    我们在classpath下创建名为message_en.properties,message_cn_ZH.properties,message.properties的资源文件,并在其中定义消息。

    在SpringBoot中,我们只需要直接创建前缀为messages的properties文件即可(spring.messages.basename值默认为messages),也可以在application.properties中配置消息资源文件的前缀:

    spring.messages.basename=application
    
  4. URL表达式: @{...}

    使用URL表达式来表达一个URL,那么在URL中我们就可以嵌入其它的表达式

  5. 片段表达式: ~{...}

    片段表达式让我们加强对HTML块的重用,我们在templates目录下创建一个fragment.html,并在其中定义一个名为header的fragment:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    			<meta charset="UTF-8">
    			<title>Title</title>
    </head>
    <body>
    <div th:fragment="header">
    			<h1 th:text="#{header.msg}"></h1>
    			<img th:src="@{/images/TacosCloud.jpg}">
    </div>
    
    </body>
    </html>
    

    然后我们以前页面中的标题和图片就可以使用片段表达式来替换:

    <div th:insert="~{fragment::header}"></div>
    <!-- 第一个值为html文件名,第二个值为文件中fragment的名字 -->
    

(未完待续。。。)

处理表单提交

前面的html表单中,method属性为post,没有设置action属性,那么我们提交表单时,浏览器将会收集表单数据并将其提交给一个HTTP POST 请求,这个请求的路径是/design。我们需要在Controller中添加一个处理Post请求的方法。

@PostMapping
public String processDesign(Taco taco){
    //do something with taco

    return "redirect:/orders/current";
}

processDesign方法也返回一个字符串,但是这个字符串有一个前缀redirect:,这表明这是一个重定向视图。当用户设计了自己的Taco并提交后,浏览器会重定向到/orders/current

表单输入验证

Spring支持Java’s Bean Validation API(JSR-303) ,这个API使得定义校验规则变得简单,我们不需要通过编码来实现校验。

在Spring Boot应用中,不需要添加任何的依赖包。因为JSR-303 API和Hibernate实现的校验API在应用启动时被自动加入到项目中。

在Spring MVC中使用这个API,我们需要:

  • 在需要被校验的类中声明校验规则
  • 指定控制器方法来执行验证
  • 修改视图的表单来展示验证的错误结果

JSR-303 API提供了几个注解,用来声明校验规则,Hibernate实现的API提供了更多的接口。

添加校验规则

import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data
public class Taco {
	   //不为空,长度不少于5个字符
    @NotNull
    @Size(min=5,message = "name must be at least  5 characters long")
    private String name;
				//至少有一个元素
    @Size(min=1,message="you must choose at least 1 ingredient")
    private List<String> ingredients;
}

import lombok.Data;
import org.hibernate.validator.constraints.CreditCardNumber;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
public class Order {
		  //不能为空,也不能只含空格
    @NotBlank(message = "Name is required")
    private String name;
    @NotBlank(message = "Street is required")
    private String street;
    @NotBlank(message = "City is required")
    private String city;
    @NotBlank(message = "State is required")
    private String state;
    @NotBlank(message = "Zip is required")
    private String zip;
	//这是一个有Hibernate提供的注解,将检验ccNumber是否是合法的信用卡号码
    @CreditCardNumber(message = "Not a valid credit card number")
    private String ccNumber;
	//提供一个正则表达式,校验值是否与表达式匹配
    @Pattern(regexp = "(0[1-9]|1[0-2])[\\\\/]([1-9][0-9])",message = "Must be formatted MM/YY")
    private String ccExpiration;
	//值必须是数字
    @Digits(integer = 3,fraction = 0,message = "Invalid CVV")
    private String ccCVV;
}

参考文档:jakartaHibernate

表单绑定时完成校验

除了对domain class 添加注解集验证规则外,Controller中也需要一点修改:

@PostMapping
public String processDesing(@Valid Taco design, Errors errors){
    if (errors.hasErrors()){
        return "/design";
    }
    return "redirect:/orders/current";
}

@PostMapping
public String processOrder(@Valid Order order, Errors errors){
    if (errors.hasErrors()){
        return "orderForm";
    }
    log.info("Order submit:" + order);
    return "redirect:/";
}

在控制器方法中,我们对表单数据绑定对象添加了@Valid注解,并新增了一个org.springframework.validation.Errors实例的新入参。@Valid注解告诉Spring MVC在tacoorder对象绑定了表单提交的数据之后,控制器方法被调用之前对表单数据进行验证。errors可得到校验结果。

展示校验结果

在Thymeleaf模板中,使用fieldth:errors可以轻松访问Error对象。

<form method="post" th:object="${taco}">
	<label for="name">Name: </label>
	<input type="text" th:field="*{name}"/>
	<span class="validationError" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Num Error</span>
	<br/>
</form>

${#fields}就是对控制器中errors对象的引用。 th:if控制是否显示这个span,th:errors用于指定使用哪个字段上的message来展示错误信息。

值得注意的是:在页面上th:object对应的对象变量名应该是类名,或者是类名的驼峰命名,这样验证结果才会在页面显示。如上面processDesing方法我们是这样注解的:@Valid Taco design,spring运行时的入参名称将不再是design,而是与类名相关的一个名称。所以在页面上绑定对象时,应该使用如"taco"这样的对象变量名称。

使用视图控制器

到目前为止,我们都是在Controller中定义一个方法来实现对HTTP GET请求的处理。我们可以使用视图控制器来达到同样的效果。

@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");//请求路径为/,跳转页面的视图逻辑名为home
    }
}

WebMvcConfigurer虽然是一个接口,但是它所定义的方法都有默认的实现,我们只需要覆盖addViewController方法即可。该方法提供了一个org.springframework.web.servlet.config.annotation.ViewControllerRegistry对象,我们可以通过它注册多个视图控制器。

选择视图模板

视图模板
我们可以选择上面的视图模板,只需要加入其依赖即可。当应用启动之后,Spring Boot自动配置将探测到我们所选择的模板,并自动的配置模板bean。我们只需要在src/main/resources/templates目录下编写模板即可。

Jsp不需要任何依赖,因为Web容器(默认为Tomcat)自身已经实现了对JSP的支持。但是如果我们的项目被打包成jar包,那么Jsp将不适合,因为容器总是在/WEB-INF目录下寻找jsp页面,然而spring boot项目并没有这个目录。所以Jsp模板只在当我们将项目打包成war包,并部署在传统web容器上时才能使用。

模板缓存设置


模板缓存默认开启,在application.properties文件中,加上spring.thymeleaf.cache=false,将关闭模板缓存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值