本文转载自微信公众号「七哥聊编程」,作者七哥 。转载本文请联系七哥聊编程公众号。
大家好呀,我是七哥。
大家都知道,我们要用 Spring 创建一个RESTful 应用程序,是需要为框架做很多事情的,比如往项目的构建文件中添加各种库依赖,还要自己写各种配置文件,因为 Spring 虽然牛逼的提供了很多优秀的特性,但是我们还是需要配置告诉 Spring 该怎么做。
但是,现在都啥时代了,全人类都讲究自动化、智能化了,我们程序员开发任务为啥就不能来点自动化、智能化呢?这不, Spring Boot 横空出世了。它拥有的 起步依赖、自动配置 特性,让我们拒绝那些繁琐且千篇一律的配置,解放程序员的双手,集中精力开发应用程序即可。
今天这篇文章的目标,就是带大家用 Spring Boot 开发一个视频观看列表的应用程序,一起感受 Spring Boot 的强大。
1. 前言
我们今天要开发的这个视频观看列表应用程序,可以支持用户查找观看列表,输入自己想看的视频信息,删除已经看过的视频。
技术栈采用 Spring Boot 快速开发、Spring MVC 处理 Web 请求, Thymeleaf 来定义 Web 视图,Spring Data JPA 来持久化观看列表到数据化,数据库使用 H2。
2. 初始化项目
创建 Spring Boot 项目的几种方式我们之前已经讲过了,本质都是使用的 Spring Initializr,选择最合适的就好,但是要确保勾选了我们需要的依赖。
使用 IDEA 选择 Initializr 创建项目:
创建完成后,就得到了如下图的项目结构:
基本上 Spring Boot 项目初始化完成后的结构都是一样的,你可能已经着急想开发应用程序了,但是在进入下一步写代码之前,我们还是停下脚步来具体看下项目里的这些东西都是干啥的,我觉得这是很重要的!搞清楚了大概的原理在开发的时候就不容易踩坑了,免得你在实际工作中不明所以,改这些文件也是糊里糊涂的,这样容易出 bug 的~~
3. Spring Boot项目的结构
总体来说 Spring Boot 项目是遵循 Maven 或者 Gradle 的项目布局的,主要代码放在 src/main/java 路径下,配置文件放到 src/main/resources 路径下,测试代码放到 src/test/java 路径下,如果有测试配置就放在 src/test/resources 路径。
我们再来看下我们项目根目录中还有不少文件:
- build.gradle: Gradle 构建说明文件;
- SpringRoad02Application.java:应用程序的启动引导类,同时也是主要的 Spring 配置类;
- application.properties:用于配置应用程序和 Spring Boot 的属性;
- SpringRoad02ApplicationTests.java:基本的集成测试类;
启动引导Spring
SpringRoad02Application 它的作用是启动引导和配置。
- //开启组件扫描和自动配置
- @SpringBootApplication
- public class SpringRoad02Application {
- public static void main(String[] args) {
- // 启动引导应用程序
- SpringApplication.run(SpringRoad02Application.class, args);
- }
- }
@SpringBootApplication 这个注解是如何开启组件扫描和自动配置功能的呢?
这是因为 @SpringBootApplication 注解将三个有用的注解组合在了一起:
- @SpringBootConfiguration:标明该类是一个基于 Java 代码的 Spring 配置类;
- @ComponentScan:启用组件扫描,这样项目中我们写的控制器和其它服务类才会被 Spring 扫描注册为应用程序上下文中的 Bean;
- @EnableAutoConfiguration:这个注解最牛逼,就是它开启了 Spring Boot 的黑魔法,让我们不用写 Spring 那些繁琐的 xml 配置;
那要运行 Spring Boot 应用,除了传统的 WAR 包部署,启动类的 main 方法支持我们在命令行里把应用程序当成可执行的 JAR 包来运行。实际上就算我们一行代码都不写,这个项目已经是可运行的了,最简单的构建运行方法就是 Gradle 的 bootRun 任务,你可以执行下面的命令感受一下:
- gradle bootRun
启动成功截图:
测试Spring Boot应用程序
Initializr 还提供了一个测试类:SpringRoad02ApplicationTests ,可以基于它我们来为应用程序编写测试。
- @SpringBootTest // 通过 Spring Boot 加载上下文
- class SpringRoad02ApplicationTests {
- @Test
- void contextLoads() {
- }
- }
可以直接运行这个 contextLoads() 方法,不报错就说明加载应用程序上下文是没有问题的:
配置应用程序属性
Initializr 生成的项目中有一个空的 application.properties 文件,这个文件其实是可选的,你可以删掉,但是一般会保留,因为实际项目肯定都有很多的属性配置,比如数据库、mq、redis 等等。
我们可以给这个文件配置一个属性 sever.port=8000 试试看:
加上后,我们在启动程序,它内嵌的 Tomcat 监听端口就从默认的 8080 改为了 8000。
神奇的地方在于,我们完全不用告诉 Spring Boot 去加载 application.properties 文件,只要它在根目录存在就会自动被加载,Spring 和 应用程序就可以获取其中配置的属性。
4. 构建说明文件,起步依赖介绍
项目基本结构我们上面都已经介绍的差不多了,那接下里我们就来看看 Spring Boot 应用程序到底是如何构建的。比如 Spring Boot 为什么可以支持打 JAR 包部署?如果没有 Spring Boot 我们要使用 Spring MVC 需要添加哪个依赖,应该用哪个版本的 Spring Data JPA?这些包放在一起的兼容性如何?
上面这些问题,都是我们在开发 Spring 应用程序会遇到的问题,一般情况下项目需要依赖包我们都是直接从其它项目拷贝过来,因为现有项目经过了测试,依赖jar包之前的兼容性经过了测试,出现问题的情况就比较小。不过就算这样,这也是一项复杂繁琐的工作。
好在 Spring Boot 的出现解放了我们奋战在一线的程序员,因为它引入了起步依赖这个特性,简而言之就是可以根据功能来引入对应的依赖包。
比如我们要开发一个 Web 应用,它使用 Thymeleaf,通过 Spring Data JPA 来持久化数据,那我们的构建文件就只需要写上这些功能就可以了。
比如下面这样,就是我通过 Spring Initializr 创建 Spring Boot 应用程序后 Gradle 构建文件里的包依赖说明:
- dependencies {
- implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
- implementation 'org.springframework.boot:spring-boot-starter-web'
- compileOnly 'org.projectlombok:lombok'
- runtimeOnly 'com.h2database:h2'
- annotationProcessor 'org.projectlombok:lombok'
- testImplementation 'org.springframework.boot:spring-boot-starter-test'
- }
可以看到,构建文件中没有单独的库依赖,只需要添加对应的 起步依赖 就好了。这几个起步依赖等价于加了一大把独立的包,每个包的版本会根据 Spring Boot 的版本自动帮我们决定,都是经过大量的官方测试的,所以兼容性也可以放心。
如果你是一个小心谨慎的人,实在想看看自己用的相关包的版本,那么可以使用构建工具的命令查到你想看的:
- // gradle里使用的命令
- gradle dependencies
- // maven里使用的命令
- mvn dependency:tree
然后你就会得到一颗树状列表,查看每个起步依赖引入的那一大堆相关的包。
上面的这几个起步依赖,包括 Web、Thymeleaf、JPA等,只不过是 Spring Boot 众多起步依赖中的沧海一粟,它还提供了很多起步依赖,大家可以按需添加即可。
覆盖起步依赖引入的传递依赖
假如我们现在要替换起步依赖引入的其中一个包的版本,要怎么做呢?当然一般我们不需要手动去替换,但是方法我们还是需要掌握的,可以用在某个包出现了严重的bug,或者删除不需要使用的包给项目瘦身。
排除传递的依赖
gradle里面可以如下操作:
- // 排除掉 Jackson
- implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
- exclude group: 'com.fasterxml.jackson.core'
- }
maven里可以使用元素来排除传递依赖。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- <exclusions>
- <exclusion>
- <groupId>com.fasterxml.jsackson.core</groupId>
- </exclusion>
- </exclusions>
- </dependency>
替换传递的依赖
假如我们是需要将传递的 Jackson 包进行升级呢?这种情况一般多用在新版本的包升级了某个bug。
Maven 总是会用最近的依赖,也就是说重新添加就会覆盖:
- <dependency>
- <groupId>com.fasterxml.jsackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- <version>2.12.3</version>
- </dependency>
Gradle 也是一样的道理,不过如果你要替换的包版本比传递的包版本低 ,那么这块有点坑需要注意下:Gradle 默认使用最新版本的包,所以就需要先排除起步依赖引入的包,在加入对应版本的包。
- implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
- exclude group: 'com.fasterxml.jackson.core'
- }
- implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
当然替换包的版本时候一定要谨慎,毕竟起步依赖的传递都是经过大量的测试的。
5. 使用自动配置,代码开发
Spring Boot 采用的自动配置,可以节省我们手动写诸如 web.xml 等配置文件,下面直接给大家展示下我们开发一个视频观看列表程序所需要的代码。
数据库实体对象;
- @Entity
- @Data
- public class Video {
- // 主键
- @Id
- // 值是自动生成的
- @GeneratedValue(strategy = GenerationType.AUTO)
- private Long id;
- private String reader;
- private String isbn;
- private String title;
- private String author;
- private String description;
- }
@Entity 注解表示这是一个JPA实体对象,@GeneratedValue 表示 id 字段的值是自动生成的。
数据访问接口
- public interface ReadingListRepository extends JpaRepository<Video,Long> {
- List<Video> findByReader(String reader);
- }
你没看错,只需要继承 JpaRepository 接口即可,这个接口内置了18个方法,我们也无需写实现类, Spring Date 提供了非常智能的实现,应用程序启动后,就会帮我们自动生成实现。
Web 接口定义
有了数据库实体对象和持久化数据的接口,接下来我们就需要一个前端控制器,这块使用的当然就是 SpringMVC 了。
- @Controller
- public class ReadingListController {
- @Autowired
- private ReadingListRepository readingListRepository;
- @GetMapping(value = "/{viewer}")
- public String readersVideos(@PathVariable("viewer") String viewer, Model model) {
- List<Video> videoList = readingListRepository.findByReader(viewer);
- if (videoList != null) {
- model.addAttribute("videos", videoList);
- }
- return "readingList";
- }
- @PostMapping(value = "/{viewer}")
- public String addToReadingList(@PathVariable("viewer") String viewer, Video video) {
- video.setReader(viewer);
- readingListRepository.save(video);
- return "redirect:/{viewer}";
- }
- }
代码很简单,就是定义了两个接口,一个用于处理用户的 GET 请求,获取观看视频的列表,一个用于将用户提交的视频信息绑定到 Video 对象,然后通过数据访问接口落库。
由于 Spring Boot 提供的自动装配特性,因此我们无需配置视图解析器和模板引擎,就可以支持解析逻辑视图名然后重定向到页面了。
Thymeleaf模板
Spring Boot 项目我们的视图模板文件需要放到 src/main/resources/templates 目录下,文件名为:readingList.html 。
- <html>
- <head>
- <title>Reading List</title>
- <link rel="stylesheet" th:href="@{/style.css}"></link>
- </head>
- <body>
- <h2>Your Reading List</h2>
- <div th:unless="${#lists.isEmpty(videos)}">
- <dl th:each="video : ${videos}">
- <dt class="bookHeadline">
- <span th:text="${video.title}">Title</span> by
- <span th:text="${video.author}">Author</span>
- (ISBN: <span th:text="${video.isbn}">ISBN</span>)
- </dt>
- <dd class="bookDescription">
- <span th:if="${video.description}" th:text="${video.description}">Description</span>
- <span th:if="${video.description eq null}">No description available</span>
- </dd>
- </dl>
- </div>
- <div th:if="${#lists.isEmpty(videos)}">
- <p>You have no books in your book list</p>
- </div>
- <hr/>
- <h3>Add a video</h3>
- <form method="POST">
- <label for="title">Title:</label>
- <input type="text" name="title" size="50"></input><br/>
- <label for="author">Author:</label>
- <input type="text" name="author" size="50"></input><br/>
- <label for="isbn">ISBN:</label>
- <input type="text" name="isbn" size="15"></input><br/>
- <label for="description">Description:</label><br/>
- <textarea name="description" cols="80" rows="5"></textarea><br/>
- <input type="submit"></input>
- </form>
- </body>
- </html>
然后我们还提供了一个简单的样式文件,这个名为 style.css 的样式文件,我们需要放到 src/main/resources/static 静态资源目录下。
- body {
- background-color: #cccccc;
- font-family: arial,helvetica,sans-serif;
- }
- .bookHeadline {
- font-size: 12pt;
- font-weight: bold;
- }
- .bookDescription {
- font-size: 10pt;
- }
- label {
- font-weight: bold;
至此代码就写完了,虽然它什么配置都没有写,不过这确实已经是一个完整的 Spring 应用程序了。你是不是已经迫不及待的想运行了?
6. 运行测试
Spring Boot 应用程序,可以选择通过 Maven 或者 Gradle 来运行应用程序,比如文章前面我们使用的: gradle bootRun。
当然我们一般使用 IDEA 来开发,可以执行运行启动类的 main 方法来运行。
然后我们通过浏览器来访问:http://localhost:8000/sevenluo
可以尝试添加一些视频,然后就可以得到视频列表了。
结尾
那今天我们通过一个简单的视频观看列表应用程序,带大家手把手开发了一个完整的 Spring Boot 应用程序。
开发全程我们是没有任何配置的,之所以这么便利主要是用到了 Spring 4.0 引入的条件化配置新特性。
我们也可以在 Spring 里很方便的编写自己的条件,只要实现 Condition 接口即可,今天就不多逼逼了,如果你对于 Spring Boot 如何通过条件化配置实现自动装配感兴趣,可以留言告诉我,我们安排一章来详细介绍。