《Spring实战》第5版本书是一本经典而实用的畅销Spring学习指南。这本书就相当于是关于Spring的新华字典,第5版涵盖了Spring 5.0和Spring Boot 2.0里程碑式的更新。全书分为5个部分,共19章。第1部分(第1~5章)涵盖了构建Spring应用的基础话题。第2部分(第6~9章)讨论如何将Spring应用与其他应用进行集成。第3部分(第10~12章)探讨Spring对反应式编程提供的全新支持。第4部分(第13~15章)拆分单体应用模型,介绍Spring Cloud和微服务开发。第5部分(第16~19章)讨论如何为应用投入生产环境做准备以及如何进行部署。
Spring 5更新了哪些内容?
Spring 5的主要功能是对反应式编程的支持,包括Spring WebFlux。这是一个全新的反应式Web框架,借鉴了Spring MVC的编程模型,允许开发人员创建伸缩性更好且耗用更少线程的Web应用程序。至于Spring应用的后端,最新版本的Spring Data支持创建反应式、非阻塞的数据repository。所有这些都构建在Reactor项目之上,Reactor是一个用于处理反应式类型的Java库。
除了Spring 5新的反应式编程特性之外,Spring Boot 2提供了比以前更多的自动配置支持,以及一个完全重新设计的Actuator,用于探查和操作正在运行的应用。
更重要的是,当开发人员希望将单体应用拆分为分散的微服务时,Spring Cloud提供了一些工具,使配置和发现微服务变得容易,并增强了微服务的功能,使它们更能抵御失败。
针对学习者的建议
如果你是经验丰富的老手,《Spring实战(第5版)》可以作为指南,指导你去学习Spring提供的新功能;如果你是Spring新手,那么现在是行动起来的最佳时机,本书的前几章会让你快速上手!
《Spring实战》(第5版)学习路线图
本书分成了5个部分,共计19章。
第1部分涵盖构建Spring应用的基础话题。
- 第1章介绍Spring和Spring Boot以及如何初始化Spring项目。在本章中,我们迈出构建Spring应用的第一步,在本书后续各章中,我们会对这个应用进行扩展。
- 第2章讨论如何使用Spring MVC构建应用的Web层。在本章中,我们将会构建处理Web请求的控制器以及在浏览器中渲染信息的视图。
- 第3章会深入探讨Spring应用的后端,在这里数据会持久化到关系型数据库中。
- 在第4章中,我们会使用Spring Security认证用户并防止未认证的用户访问应用。
- 第5章介绍如何使用Spring Boot的配置属性功能来配置Spring应用。我们还会学习如何使用profile选择性地应用配置。
第2部分讨论如何将Spring应用与其他应用进行集成。
- 第6章延续第2章对Spring MVC的讨论,我们将会学习如何在Spring中编写REST API。
- 第7章讨论和第6章相对立的主题,展现Spring应用如何消费REST API。
- 第8章会讨论如何使用异步通信技术让Spring应用发送和接收消息,这里会用到Java Message Service、RabbitMQ或Kafka。
- 第9章讨论如何使用Spring Integration进行声明式的应用集成。
第3部分探讨Spring对反应式编程提供的全新支持。
- 第10章介绍Reactor项目。这是一个反应式编程库,支撑了Spring 5的反应式特性。
- 第11章重新探讨REST API开发,介绍全新的Web框架Spring WebFlux。该框架借用了很多Spring MVC的理念,但是为Web开发提供了新的反应式模型。
- 第12章将会看一下如何使用Spring Data编写反应式数据持久化,我们将会读取和写入Cassandra与Mongo数据库。
第4部分将会拆分单体应用模型,介绍Spring Cloud和微服务开发。
- 第13章会深入介绍服务发现,组合使用Spring和Netflix的注册中心实现Spring微服务的注册和发现。
- 第14章将展现如何在配置服务器中实现中心化的应用配置,从而实现跨微服务共享配置。
- 第15章会介绍Hystrix的断路器模式。它能够让微服务在面临失败时更有弹性。
在第5部分中,我们将会讨论如何做好将应用投入生产环境的准备,并看一下如何进行部署。
- 第16章会介绍Spring Boot Actuator。它是Spring Boot的一个扩展,通过REST端点的形式暴露Spring应用内部的运行状况。
- 第17章将会介绍如何使用Spring Boot Admin。它是构建在Actuator之上的一个用户友好的基于浏览器的管理应用。
- 第18章将会讨论如何将Spring bean暴露为JMX MBean以及如何消费它们。
- 在第19章中,我们会看到如何将Spring应用部署到各种生产环境中。
通常来讲,刚刚接触Spring的开发人员应该从第1章开始,并按顺序阅读每一章;经验丰富的Spring开发人员可能更愿意在任何感兴趣的时候参与进来。即便如此,每一章都是建立在前一章的基础上的,所以如果从中间开始阅读,那么可能会漏掉一些上下文信息。
样章试读:第2章 开发Web应用
本章内容:
在浏览器中展现模型数据
处理和校验表单输入
选择视图模板库
第一印象是非常重要的:Curb Appeal能够在购房者真正进门之前就将房子卖掉;如果一辆车喷成了樱桃色,那么它的油漆会比它的引擎更引人注目;文学作品中充满了一见钟情的故事。内在固然非常重要,但是外在的,也就是第一眼看到的东西同样非常重要。
我们使用Spring所构建的应用会完成各种各样的事情,包括处理数据、从数据库中读取信息以及与其他应用进行交互。但是,用户对应用程序的第一印象来源于用户界面。在很多应用中,UI是以浏览器中的Web应用的形式来展现的。
在第1章中,我们创建了第一个Spring MVC控制器来展现应用的主页。但是,Spring MVC能做很多的事情,并不局限于展现静态内容。在本章中,我们将会开发Taco Cloud的第一个主要功能:设计定制taco的能力。在这个过程中,我们将会深入研究Spring MVC并会看到如何展现模型数据和处理表单输入。
2.1 展现信息
从根本上来讲,Taco Cloud是一个可以在线订购taco的地方。但是,除此之外,Taco Cloud允许客户展现其创意,能够让他们通过丰富的配料(ingredient)设计自己的taco。
因此,Taco Cloud需要有一个页面为taco艺术家展现都可以选择哪些配料。可选的配料可能随时会发生变化,所以不能将它们硬编码到HTML页面中。我们应该从数据库中获取可用的配料并将其传递给页面,进而展现给客户。
在Spring Web应用中,获取和处理数据是控制器的任务,而将数据渲染到HTML中并在浏览器中展现则是视图的任务。为了支撑taco的创建页面,我们需要构建如下组件。
- 用来定义taco配料属性的领域类。
- 用来获取配料信息并将其传递至视图的Spring MVC控制器类。
- 用来在用户的浏览器中渲染配料列表的视图模板。
这些组件之间的关系如图2.1所示。
图2.1 典型的Spring MVC请求流
因为本章主要关注Spring的Web框架,所以我们会将数据库相关的内容放到第3章中进行讲解。现在的控制器只负责向视图提供配料。在第3章中,我们会重新改造这个控制器,让它能够与repository协作,从数据库中获取配料数据。
在编写控制器和视图之前,我们首先确定一下用来表示配料的领域类型,它会为我们开发Web组件奠定基础。
2.1.1 构建领域类
应用的领域指的是它所要解决的主题范围:也就是会影响到对应用理解的理念和概念[1]。在Tao Cloud应用中,领域对象包括taco设计、组成这些设计的配料、顾客以及顾客所下的订单。作为开始,我们首先关注taco的配料。
在我们的领域中,taco配料是非常简单的对象。每种配料都有一个名称和类型,以便于对其进行可视化的分类(蛋白质、奶酪、酱汁等)。每种配料还有一个ID,这样的话对它的引用就能非常容易和明确。如下的Ingredient类定义了我们所需的领域对象。
程序清单2.1 定义taco配料
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 } }
我们可以看到,这是一个非常普通的Java领域类,它定义了描述配料所需的3个属性。在程序清单2.1中,Ingredient类最不寻常的一点是它似乎缺少了常见的getter和setter方法,以及equals()、hashCode()、toString()等方法。
在程序清单中没有这些方法,部分原因是节省空间,另外还因为我们使用了名为Lombok的库(这是一个非常棒的库,它能够在运行时动态生成这些方法)。实际上,类级别的@Data注解就是由Lombok提供的,它会告诉Lombok生成所有缺失的方法,同时还会生成所有以final属性作为参数的构造器。通过使用Lombok,能够让Ingredient的代码简洁明了。
Lombok并不是Spring库,但是它非常有用,我发现如果没有它,开发工作将很难开展。当我需要在书中将代码示例编写得短小简洁时,它简直成了我的救星。
要使用Lombok,首先要将其作为依赖添加到项目中。如果你使用Spring Tool Suite,那么只需要用右键点击pom.xml,并从Spring上下文菜单选项中选择“Edit Starters”。在第1章中看到的选择依赖的对话框将会再次出现(见图1.4),这样的话我们就有机会添加依赖或修改已选择的依赖了。找到Lombok选项,并确保它处于已选中的状态,然后点击“OK”,Spring Tool Suite会自动将其添加到构建规范中。
另外,你也可以在pom.xml中通过如下条目进行手动添加:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
这个依赖将会在开发阶段为你提供Lombok注解(例如@Data),并且会在运行时进行自动化的方法生成。但是,我们还需要将Lombok作为扩展添加到IDE上,否则IDE将会报错,提示缺少方法和final属性没有赋值。参见Lombok项目页面,以查阅如何在你所选择的IDE上安装Lombok。
我相信你会发现Lombok非常有用,但你也要知道,它是可选的。在开发Spring应用的时候,它并不是必备的,所以如果你不想使用它的话,完全可以手动编写这些缺失的方法。当你完成之后,我们将会在应用中添加一些控制器,让它们来处理Web请求。
2.1.2 创建控制器类
在Spring MVC框架中,控制器是重要的参与者。它们的主要职责是处理HTTP请求,要么将请求传递给视图以便于渲染HTML(浏览器展现),要么直接将数据写入响应体(RESTful)。在本章中,我们将会关注使用视图来为Web浏览器生成内容的控制器。在第6章中,我们将会看到如何以REST API的形式编写控制器来处理请求。
对于Taco Cloud应用来说,我们需要一个简单的控制器,它要完成如下功能。
- 处理路径为“/design”的HTTP GET请求。
- 构建配料的列表。
- 处理请求,并将配料数据传递给要渲染为HTML的视图模板,发送给发起请求的Web浏览器。
程序清单2.2中的DesignTacoController类解决了这些需求。
程序清单2.2 初始的Spring控制器类
package tacos.web; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; 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; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import lombok.extern.slf4j.Slf4j; import tacos.Taco; import tacos.Ingredient; import tacos.Ingredient.Type; @Slf4j @Controller @RequestMapping("/design") public class DesignTacoController { @GetMapping 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"; } }
对于DesignTacoController,我们先要注意在类级别所应用的注解。首先是@Slf4j,这是Lombok所提供的注解,在运行时,它会在这个类中自动生成一个SLF4J(Simple Logging Facade for Java)Logger。这个简单的注解和在类中通过如下代码显式声明的效果是一样的:
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DesignTacoController.class);
随后,我们将会用到这个Logger。
DesignTacoController用到的下一个注解是@Controller。这个注解会将这个类识别为控制器,并且将其作为组件扫描的候选者,所以Spring会发现它并自动创建一个DesignTacoController实例,并将该实例作为Spring应用上下文中的bean。
DesignTacoController还带有@RequestMapping注解。当@RequestMapping注解用到类级别的时候,它能够指定该控制器所处理的请求类型。在本例中,它规定DesignTacoController将会处理路径以“/design”开头的请求。
处理GET请求
修饰showDesignForm()方法的@GetMapping注解对类级别的@RequestMapping进行了细化。@GetMapping结合类级别的@RequestMapping,指明当接收到对“/design”的HTTP GET请求时,将会调用showDesignForm()来处理请求。
@GetMapping是一个相对较新的注解,是在Spring 4.3引入的。在Spring 4.3之前,你可能需要使用方法级别的@RequestMapping注解作为替代:
@RequestMapping(method=RequestMethod.GET)
显然,@GetMapping更加简洁,并且指明了它的目标HTTP方法。@GetMapping只是诸多请求映射注解中的一个。表2.1列出了Spring MVC中所有可用的请求映射注解。
让正确的事情变得更容易
在为控制器方法声明请求映射时,越具体越好。这意味着至少要声明路径(或者从类级别的@RequestMapping继承一个路径)以及它所处理的HTTP方法。
但是更长的@RequestMapping(method=RequestMethod.GET)注解很容易让开发人员采取懒惰的方式,也就是忽略掉method属性。幸亏有了Spring 4.3的新注解,正确的事情变得更容易了,我们的输入变得更少了。
新的请求映射注解具有和@RequestMapping完全相同的属性,所以我们可以在使用@RequestMapping的任何地方使用它们。
通常,我喜欢只在类级别上使用@RequestMapping,以便于指定基本路径。在每个处理器方法上,我会使用更具体的@GetMapping、@PostMapping等注解。
现在,我们已经知道showDesignForm()方法会处理请求,接下来我们看一下方法体,看它都做了些什么工作。这个方法构建了一个Ingredient对象的列表。现在,这个列表是硬编码的。当我们学习第3章的时候,会从数据库中获取可用taco配料并将其放到列表中。
配料列表准备就绪之后,showDesignForm()方法接下来的几行代码会根据配料类型过滤列表。配料类型的列表会作为属性添加到Model对象上,这个对象是以参数的形式传递给showDesignForm()方法的。Model对象负责在控制器和展现数据的视图之间传递数据。实际上,放到Model属性中的数据将会复制到Servlet Response的属性中,这样视图就能在这里找到它们了。showDesignForm()方法最后返回“design”,这是视图的逻辑名称,会用来将模型渲染到视图上。
我们的DesignTacoController已经具备雏形了。如果你现在运行应用并在浏览器上访问“/design”路径,DesignTacoController的showDesignForm()将会被调用,它会从repository中获取数据并放到模型中,然后将请求传递给视图。但是,我们现在还没有定义视图,请求将会遇到很糟糕的问题,也就是HTTP 404(Not Found)。为了解决这个问题,我们将注意力切换到视图上,在这里数据将会使用HTML进行装饰,以便于在用户的Web浏览器中进行展现。
2.1.3 设计视图
在控制器完成它的工作之后,现在就该视图登场了。Spring提供了多种定义视图的方式,包括JavaServer Pages(JSP)、Thymeleaf、FreeMarker、Mustache和基于Groovy的模板。就现在来讲,我们会使用Thymeleaf,这也是我们在第1章开启这个项目时的选择。我们会在第2.5节考虑其他的可选方案。
为了使用Thymeleaf,我们需要添加另外一个依赖到项目构建中。如下的<dependency>条目使用了Spring Boot的Thymeleaf starter,从而能够让Thymeleaf 渲染我们将要创建的视图:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
在运行时,Spring Boot的自动配置功能会发现Thymeleaf在类路径中,因此会为Spring MVC创建支撑Thymeleaf视图的bean。
像Thymeleaf这样的视图库在设计时是与特定的Web框架解耦的。这样的话,它们无法感知Spring的模型抽象,因此无法与控制器放到Model中的数据协同工作。但是,它们可以与Servlet的request属性协作。所以,在Spring将请求转移到视图之前,它会把模型数据复制到request属性中,Thymeleaf和其他的视图模板方案就能访问到它们了。
Thymeleaf模板就是增加一些额外元素属性的HTML,这些属性能够指导模板如何渲染request数据。举例来说,如果有一个请求属性的key为“message”,我们想要使用Thymeleaf将其渲染到一个HTML <p>标签中,那么在Thymeleaf模板中我们可以这样写:
<p th:text="${message}">placeholder message</p>
当模板渲染成HTML的时候,<p>元素体将会被替换为Servlet request中key为“message”的属性值。“th:text”是Thymeleaf命名空间中的属性,它会执行这个替换过程。${}会告诉它要使用某个请求属性(在本例中,也就是“message”)中的值。
Thymeleaf还提供了一个属性“th:each”,它会迭代一个元素集合,为集合中的每个条目渲染HTML。在我们设计视图展现模型中的配料列表时,这就非常便利了。举例来说,如果只想渲染“wrap”配料的列表,我们可以使用如下的HTML片段:
<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>
在这里,我们在<div>标签中使用th:each属性,这样的话就能针对wrap request属性所对应集合中的每个元素重复渲染<div>了。在每次迭代的时候,配料元素都会绑定到一个名为ingredient的Thymeleaf变量上。
在<div>元素中,有一个<input>复选框元素,还有一个为复选框提供标签的<span>元素。复选框使用Thymeleaf的th:value来为渲染出的<input>元素设置value属性,这里会将其设置为所找到的ingredient的id属性。<span>元素使用th:text将“INGREDIENT”占位符文本替换为ingredient的name属性。
当用实际的模型数据进行渲染的时候,其中一个<div>迭代的渲染结果可能会如下所示:
<div> <input name="ingredients" type="checkbox" value="FLTO" /> <span>Flour Tortilla</span><br/> </div>
最终,上述的Thymeleaf片段会成为一大段HTML表单的一部分,我们taco艺术家用户会通过这个表单来提交其美味的作品。完整的Thymeleaf模板会包括所有的配料类型,表单如下所示:
程序清单2.3 设计taco的完整页面
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Taco Cloud</title> <link rel="stylesheet" th:href="@{/styles.css}" /> </head> <body> <h1>Design your taco!</h1> <img th:src="@{/images/TacoCloud.png}"/> <form method="POST" th:object="${design}"> <div class="grid"> <div class="ingredient-group" id="wraps"> <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> </div> <div class="ingredient-group" id="proteins"> <h3>Pick your protein:</h3> <div th:each="ingredient : ${protein}"> <input name="ingredients" type="checkbox" th:value="${ingredient.id}" /> <span th:text="${ingredient.name}">INGREDIENT</span><br/> </div> </div> <div class="ingredient-group" id="cheeses"> <h3>Choose your cheese:</h3> <div th:each="ingredient : ${cheese}"> <input name="ingredients" type="checkbox" th:value="${ingredient.id}" /> <span th:text="${ingredient.name}">INGREDIENT</span><br/> </div> </div> <div class="ingredient-group" id="veggies"> <h3>Determine your veggies:</h3> <div th:each="ingredient : ${veggies}"> <input name="ingredients" type="checkbox" th:value="${ingredient.id}" /> <span th:text="${ingredient.name}">INGREDIENT</span><br/> </div> </div> <div class="ingredient-group" id="sauces"> <h3>Select your sauce:</h3> <div th:each="ingredient : ${sauce}"> <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>
可以看到,我们会为各种类型的配料重复定义<div>片段。另外,我们还包含了一个Submit按钮,以及用户用来定义其作品名称的输入域。
值得注意的是,完整的模板包含了一个Taco Cloud的logo图片以及对样式表的<link>引用[2]。在这两个场景中,都使用了Thymeleaf的@{}操作符,用来生成一个相对上下文的路径,以便于引用我们需要的静态制件(artifact)。正如我们在第1章中所学到的,在Spring Boot应用中,静态内容要放到根路径的“/static”目录下。
我们的控制器和视图已经完成了,现在我们可以将应用启动起来,看一下我们的劳动成果。运行Spring Boot应用有很多种方式。在第1章中,我为你首先展示了如何将应用构建成一个可执行的JAR文件,然后通过java –jar命令来运行这个JAR。我还展示了如何直接通过mvn spring-boot:run构建命令来运行应用。
不管你采用哪种方式来启动Taco Cloud应用,在启动之后都可以通过http://localhost: 8080/design来进行访问。你将会看到类似于图2.2的一个页面。
图2.2 渲染之后的taco设计页面
这看上去非常不错!访问你站点的taco艺术家可以看到一个表单,这个表单中包含了各种taco配料,他们可以使用这些配料创建自己的杰作。但是当他们点击Submit Your Taco按钮的时候会发生什么呢?
我们的DesignTacoController还没有为接收创建taco的请求做好准备。如果提交设计表单,用户就会遇到一个错误。(具体来讲,将会是一个HTTP 405错误:Request Method “POST” Not Supported。)接下来,我们通过编写一些处理表单提交的控制器代码来修正这个错误。