Spring实战
第1章 Spring起步
什么是Spring
- 任何实际的应用程序都是由很多组件组成的,每个组件负责整个应用功能的一部分,这些组件需要与其他的应用元素进行协调以完成自己的任务。当应用程序运行时,需要以某种方式创建并引入这些组件。
- Spring的核心是提供了一些容器,通常称为Spring应用上下文,它们会创建和管理应用组件。这些组件也可以称为bean。
- 将bean装配到一起的行为是通过一种基于依赖注入(dependency injection,DI)的模式来实现的。此时,组件不会再去创建它所依赖的组件并管理他们的生命周期,使用依赖注入的应用依赖于单独的实体(容器)来创建和维护所有的组件,并将其注入到需要他们的bean中。通常,这是通过构造器参数和属性访问方法来实现的。
- 在核心容器之上,Spring及其一系列相关库提供了Web相关框架、各种持久化可选方案、安全框架、与其他系统集成、运行时监控、微服务支持、反应式编程以及众多现代应用开发所需的特性。
- 在历史上,指导Spring应用上下文将bean装配在一起的方式是使用一个或多个XML文件。例如以下的XML描述了两个bean,也就是InventoryService bean和ProductService bean,并且通过构造器参数将InventoryService bean装配到了ProductService中。
<bean id="inventoryService" class="com.example.InventoryService"/>
<bean id="productService" class="com.example.ProductService"/>
<constructor-arg ref="inventoryService"/>
</bean>
- 但是,在最近的Spring版本中,基于Java的配置更为常见。如下基于Java的配置类和XML配置等价。
@Configuration
public class ServiceConfiguration{
@Bean
public InventoryService inventoryService(){
return new InventoryService();
}
@Bean
public ProductService productService(){
return new ProductService(inventoryService());
}
}
- @Configuration注解会告知Spring这是一个配置类,会为Spring应用上下文提供bean。配置类的方法使用@Bean注解进行标注,表明这些方法所返回的对象会以bean的形式添加到Spring的应用上下文中。(默认情况下,这些bean所对应的bean ID与定义它们的方法名称是相同的)。
- Spring Boot是Spring框架的扩展,提供了很多增强生产效率的方法。最为大家熟知的是自动配置,Spring Boor能够基于类路径中的条目、环境变量和其他因素合理猜测需要配置的组件并将他们装配在一起。
- Spring Boor大幅度减少EL构建应用所需的显式配置的数量并极大地改善了Spring的开发。
初始化Spring应用
- 通过Spring Initializr初始化应用
检查Spring项目结构
- 项目加载到IDE后,将其展开,看一下其中都包含哪些内容。应用源代码放在了
src/main/java
中,测试代码放在了src/test/java
中,而非java的资源放在了src/main/resources
中。在这个项目结构中,需要注意以下几点:- mvnw和mvnw.cmd:这是Maven包装器(wrapper)脚本。借助这些脚本,即使机器上没有安装Maven也可以构建项目
- pom.xml:这是Maven构建规范,随后会深入介绍该文件
- TacoCloudApplication.java:这是Spring Boot主类,他会启动该项目。随后也会详细介绍该类
- application.properties:这个文件起初是空的,但是它为我们提供了指定配置属性的地方。在本章中,我们会稍微修改该文件。
- static:在这个文件夹下,可以存放任意为浏览器提供服务的静态内容(图片、样式表、JavaScript等),该文件夹初始为空。
- templates:这个文件夹中存放用来渲染内容到浏览器的模板文件。这个文件夹初始是空的,不过我们很快就会往里面添加Thymeleaf模板。
- TacoCloudApplicationTests.java:这是一个简单地测试类,他能确保Spring应用上下文可以成功加载。在开发应用的过程中,我们会将更多的测试添加进来。
- 随着Taco Cloud应用功能的增长,会不断使用Java代码、图片、样式表、测试以及其他附属内容来充实这个项目结构。不过在此之前,先来看一下Spring Initializr提供的几个条目。
探索构建规范
- 在填充Initializr表单的时候,我们声明了项目要使用Maven进行构建。因此,Spring Initializr所生成的pom.xml文件已经包含了我们所选择的以来。下面是初始的完整的Maven构建规范
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version> <!-- Spring Boot版本 -->
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>sia</groupId>
<artifactId>taco-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>taco-cloud</name>
<description>Taco Cloud Example</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies><!-- 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin> <!-- Spring Boot插件 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 可以看到几个依赖的artifactId上都有starter这个单词。Spring Boot Starter依赖的特别之处在于他们本身并不包含库代码,二是传递性的拉去其他的库。这种starter依赖主要有三个好处:
- 构建文件小并易于管理
- 可根据所提供的功能来思考依赖而不是根据库名称
- 不必担心库版本的问题
- 最后,构建规范还包含一个Spring Boot插件,这个插件提供了一些重要功能。
- 它提供了一个Maven goal,允许我们使用Maven来运行应用。
- 它会确保依赖的所有库都会包含在可执行JAR文件中,并且能够保证它们在运行时类路径下是可用的。
- 它会在JAR中生成一个manifest文件,将引导类(在我们的项目中也就是TacoCloudApplication)声明为可执行JAR的主类。
- 我们可以打开主类来看一下
引导应用
- JAR运行需要执行主类,同时还需要一个最小化的Spring配置,以引导该应用。这就是TacoCloudApplication类所做的事情。
package tacos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //Spring Boot应用
public class TacoCloudApplication {
public static void main(String[] args) {
SpringApplication.run(TacoCloudApplication.class, args);//运行应用
}
}
- @SpringBootApplication注解明确表明这是一个Spring Boot应用,但是该注解远比看上去更强大。他是一个组合注解,组合了3个其他的注解。
- @SpringBootConfiguration:将该类声明为配置类。尽管该类目前没有太多配置,但是后续我们可以按需添加基于Java的Spring框架配置。这个注解实际上是@Configuration注解的特殊形式。
- @EnableAutoConfiguration:启用Spring Boot的自动配置。它会告诉Spring Boot自动配置它认为我们会用到的组件。
- @ComponentScan:启用组件扫描。这样我们能过通过像@Component、@Controller、@Service这样的注解声明其他类,Spring会自动发现他们并将它们注册为Spring应用上下文中的组件。
- main()方法会调用SpringApplication中静态的run()方法,后者会真正执行应用的引导过程,也就是创建Spring的应用上下文。在传递给run()的两个参数中,一个是配置类,另一个就是命令行参数。尽管传递给run()的配置类不一定要和引导类相同,但这个就是最便利和最典型的做法。
- 可能并不需要修改引导类中任何内容。对于简单的应用程序来说,可能发现引导类中配置一两个组件是非常方便的,但是对于大多数应用来说,最好还是要为没有实现自动配置的功能创建一个单独的配置类。
测试应用
- 测试是软件开发的重要组成部分。鉴于此,Spring Initializr为我们提供了一个测试类作为起步。
package tacos;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest//Spring Boot测试
class TacoCloudApplicationTests {
@Test//测试方法
void contextLoads() {
}
}
- 该类内容并不多:只有一个空的测试方法。即便如此,这个测试类还是会执行必要的检查,确保Spring应用上下文嫩恩购成功加载。如果做出的变更导致Spring应用上下文无法创建,那么这个测试将会失败,就可以做出反应来解决相关问题了。
测试运行器的其他名称
- 可能会见过名为SpringJUnit4ClassRunner的测试运行器。SpringRunner是SpringJUnit4ClassRunner的别名,是在Spring 4.3中引入的,以便于移除对特定JUnit版本的关联。毫无疑问,这个别名更易于阅读和输入。
- @SpringBootTest会告诉JUnit在启动测试时要添加上Spring Boot功能。从现在开始,我们可以将这个测试类视为同为在main()方法中调用SpringApplication.run()。
编写Spring应用
- 因为是刚开始,所以我们首先为Taco Cloud做一些小的变更,但是这些变更会展现Spring的很多优点。在刚开始的时候,比较合适的做法是为Taco Cloud应用添加一个主页。在添加主页时,我们将会创建两个代码构件:
- 一个控制器类,用来处理主页相关的请求
- 一个视图模板,用来定义主页看起来的样子
- 测试是非常重要的,所以还会编写一个简单的测试类来测试主页。
处理Web请求
- Spring自带了一个强大的Web框架,名叫Spring MVC。Spring MVC核心是控制器的理念。控制器是处理请求并以某种方式进行信息响应的类。在面向浏览器的应用中,控制器会填充可选的数据模型并将请求传递给一个视图,以便于生成返回给浏览器的HTML。
package tacos;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
}
- 可以看到,这个类带有@Controller,它的主要目的就是让组件扫描将这个类识别为一个组件。Spring的组件扫描功能会自动发现他,并创建一个HomeController实例作为Spring应用上下文中的bean。
- home()是一个简单的控制器方法,带有@GetMapping注解,表明如果针对"/"发送Http Get请求,那么这个方法将会处理请求。该方法所做的只是返回String类型的home值。
- 这个值会被解析为视图逻辑名。视图如何实现取决于多个因素,但是因为Thymeleaf位于类路径中,所以我们可以使用Thymeleaf来定义模板。
为何使用Thymeleaf
- 为什么使用Thymeleaf作为模板引擎呢?为什么不使用JSP?为什么不使用FreeMaker?为什么不选其他的可选方案?
- 对于JSP来说,它是更加显而易见的选择,但是组合使用JSP和Spring Boot需要克服一些挑战。后面我们会看到其他的模板方案包括JSP。
- 模板名称是由逻辑视图名派生而来的,再加上"/templates/“前缀和”.html"后缀。最终形成的模板路径将是"templates/home.html"。所以,我们需要将模板放到目录的"src/main/resources/templates/home.html"目录中。
定义视图
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Taco Cloud</title>
</head>
<body>
<h1>Welcome to...</h1>
<img th:src="@{/images/TacoCloud.png}"/>
</body>
</html>
- 主要需要注意的是像图片这样的静态资源是放在"src/main/resources/static"文件中夹中的,而这个html本身放在隔壁的"src/main/resources/templates"
测试控制器
- 在测试Web应用时,对HTML网页的内容进行断言是比较困难的。幸好Spring对测试提供了大量的支持,这使得测试Web应用变得非常简单。
- 对于主页来说,我们所编写的测试在复杂性上与主页本身差不多。测试需要针对根路径
"/"
发送一个HTTP GET请求并期望得到成功结果,其中视图名称为home并且结果内容包含"Welcome to…"。
package tacos;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.containsString;
@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class) //针对HomeController的Web测试
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc; //注入MockMvc
@Test
public void testHomePage() throws Exception{
mockMvc.perform(get("/")) //发起对/的GET
.andExpect(status().isOk()) //期望得到HTTP200
.andExpect(view().name("home")) //期望得到home视图
.andExpect(content().string(containsString("Welcome to...")));//期望包含"Welcome to"
}
}
- 对于这个测试,我们首先可能注意到了它使用了与TacoCloudApplicationTests类不同的注解。HomeControllerTest没有使用@SpringBootTest标记,而是添加了@WebMvcTest注解。这是Spring Boot提供的一个特殊测试注解,它会让这个测试在Spring MVC应用的上下文中执行。更具体来讲,本例中,会将HomeController注册到Spring MVC中,这样的话,我们就可以向他发送请求了。
- @WebMvcTest同样会为测试Spring MVC应用提供Spring环境的支持。尽管我们可以启动一个服务器来进行测试,但是对于我们的场景来说,仿造一下Spring MVC的运行机制就可以。测试类被注入了一个MockMvc,能够让测试实现mockup。
- 通过testHomePage()方法,我们定义了针对主页想要执行的测试。首先使用MockMvc对象对
"/"
根路径发起HTTP GET请求。对于这个请求,我们设置了如下的预期:- 响应应该具备HTTP 200(OK)的状态;
- 视图的逻辑名称应该是home;
- 渲染后的视图应该包含文本"Welcome to…";
- 如果在MockMvc对象发送请求之后,这些期望有不满足的话,那么这个测试会失败。但是,我们的控制器和模板引擎在编写时都满足了这些预期,所以测试应该能够通过,并且带有成功的图标——至少能看到一些绿色背景,就表明测试通过了。
- 控制器已经编写好了,视图模板也已经创建完毕,而且我们还通过了测试,看上去我们已经成功实现了主页。尽管测试已经通过了,但是如果能在浏览器中看到结果会更加有成就感。
构建和运行应用
- 就像初始化Spring应用有很多方式一样,运行Spring应用也有多种方式,以下使用IDEA来运行Spring应用。
- 在启动应用的过程中,我们可以看到控制台输出了一条
2021-05-17 15:44:37.006 INFO 27705 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
这是非常有意思的,首先意味着此时我们可以打开Web浏览器访问http://localhost:8080
访问主页。其次,意味着我们的应用部署到了Tomcat中,后面会学习Tomcat是如何成为应用的一部分的。
了解Spring Boot DevTools
- 顾名思义,DevTools为Spring开发人员提供了一些便利的开发期工具,其中包括:
- 代码变更后应用会自动重启;
- 当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器;
- 自动禁用模板缓存;
- 如果使用H2数据库的话,内置了H2控制台。
- 需要注意,DevTools并不是IDE插件,他也不需要使用特定的IDE。因为它的目的仅仅用于开发,所以能够很智能的在生产环境中把自己禁用掉。现在主要关注其最有用的特性。
应用自动重启
- 当DevTools运行时,应用程序会被加载到JVM两个独立的类加载器中。其中一个类加载器会加载Java代码、属性文件以及项目中"src/main/"路径下几乎所有内容。这些条目很可能会经常发生变化。另外一个类加载器会加载依赖的库,这些库不太可能经常发生变化。
- 当探测到变更时,DevTools只会重新加载包含项目代码的类加载器,并重启Spring应用上下文,在这个过程中另一个类加载器和JVM原封不动。
浏览器自动刷新和禁用模板缓存
- 默认情况下像Thymeleaf和FreeMarker这样的模板方案在配置时会缓存模板解析结果。但是在开发期缓存模板就会使我们刷新浏览器时无法看到模板变更的效果。
- DevTools在运行时,会自动和应用程序一起,同时自动启动一个LiveReload服务器。LiveReload服务器本身并没有太大用处。但是当它与LiveReload浏览器插件结合起来的时候,就能够在模板、图片、样式表、JavaScript等(几乎涵盖为浏览器提供服务的所有内容)发生变化时自动刷新浏览器。
内置的H2控制台
- 虽然没有使用到数据库,但是如果使用H2数据库进行开发,DevTools会自动启用H2.这样的话,可以通过Web浏览器进行访问。只需要让浏览器访问
http://localhost:8080/h2-console
就能看到应用所使用的数据。
小结
- 我们执行了如下步骤:
- 使用Spring Initializr创建初始项目结构;
- 编写控制器类处理针对主页的请求;
- 定义了一个视图模板来渲染主页;
- 编写了一个简单的测试类来验证工作符合预期。
- 尽管测试类的大部分内容都使用了Spring对测试的支持,但是在测试的上下文中都没有那么具有侵略性(很少有Spring相关的代码)。可以视为感受不到框架的框架(frameworkless framework)。
- 在pom.xml文件中,我们声明了Web和Thymeleaf starter的依赖。这两项依赖会传递引入大量其他依赖,包括:
- Spring的MVC框架;
- 嵌入式Tomcat;
- Thymeleaf和Thymeleaf布局方言;
- 还引入了Spring Boot自动配置库。当应用启动时,Spring Boot自动配置将会探测到这些库,并自动完成如下功能:
- 在Spring应用上下文中配置bean以启用Spring MVC;
- 在Spring应用上下文中配置嵌入式的Tomcat服务器;
- 配置Thymeleaf视图解析器,以便于使用Thymeleaf模板渲染Spring MVC视图。
- 简而言之,自动配置功能完成了所有脏活累活让我们集中精力编写实现应用功能的代码。
俯瞰Spring风景线
- 要想了解Spring整体状况,只需查看完整版本的Spring Initializr Web表单上的一堆复选框列表即可。它列出了100多个可选的依赖项。这里会简单介绍一些重点项目。
Spring核心框架
- Spring核心框架是Spring领域中一切的基础。提供了核心容器和依赖注入框架,另外还提供了一些其他重要的特性。
- 其中一项时Spring MVC,也就是Spring的Web框架。已经看到了如何使用Spring MVC来编写控制器类以处理Web请求。但是,Spring MVC还能用来创建REST API,以生成非HTML的输出。
- Spring核心框架还提供了一些对数据持久化的基础支持,尤其是基于模板的JDBC支持。
Spring Boot
- 尽管已经看到了Spring Boot带来的很多收益,包括starter依赖和自动配置。我们会尽可能多地使用Spring Boot,以避免任何形式的显示配置,除非显示配置是绝对必要的。除了starter依赖和自动配置,Spring Boot还提供了大量其他有用的特性:
- Actuator能够洞察应用运行时的内部工作状况,包括指标、线程dump信息、应用的健康状况以及应用可用的环境属性;
- 灵活地环境属性规范;
- 在核心框架的测试辅助功能上还提供了对测试的额外支持。
- 除此之外,Spring Boot还提用了一个基于Groovy脚本的变成模型,称为Spring Boot命令行接口。
Spring Data
- 尽管Spring核心框架提供了基本的数据持久化支持,但是Spring Data提供了非常令人惊叹的功能:将应用程序的数据repository定义为简单的Java接口,在定义驱动存储和检索数据的方法时使用一种命名约定即可。
- 此外,Spring Data能够处理多种不同类型的数据库,包括关系型数据库(JPA)、文档数据库(Mongo)、图数据库(Neo4j)等。
Spring Security
- 应用程序的安全性一直是一个重要的话题,而且正变得越来越重要。幸运的是,Spring有一个健壮的安全框架,名为Spring Security。
- 他解决了应用程序通用的安全性需求,包括身份验证、授权和API安全性。
Spring Integration和Spring Batch
- 大多数应用程序都需要与其他应用甚至本应用中的其他组件进行继承。Spring Integration解决了实时集成问题。相反,Spring Batch解决的则是批处理集成的问题,在此过程中数据可以收集一段时间知道某个触发器发出信号,表示该处理器批量数据了才会对数据进行处理。
Spring Cloud
- 微服务是一个热门话题,解决了开发期和运行期的一些实际问题。然而在这种过程中也面临一些调整,这些挑战将有Spring Cloud直面解决,Spring Cloud是使用Spring开发云原生应用程序的一组项目。