从零放弃学习 Spring - 第一个 Spring Boot 应用
Spring 项目
哥们最近突击学习了一下 Spring 这些个东西。
Spring 提供了一个网站 Sping Initializr 用来创建初始的 Spring 项目,这里按我需求选了 Maven 和 Java 17,然后顺手添加了 3 个依赖:Spring Data JPA
、MySQL Driver
和 Spring Web
,依赖在项目创建后随时都能修改。点击 Generate
生成项目代码。
然后使用 IDEA 加载项目,这部分最好是能够提前配置一个镜像加速 Maven 下载依赖,配置镜像可以不用修改 Maven 自带的 settings.xml,可以在 $HOME/.m2/settings.xml
直接添加一个针对当前用户的配置文件或者在 mvn 中使用 -s
选项指定配置文件路径,这里可能需要注意的是,Maven 默认阻止 http 访问(你可以在 Maven 自带的 settings.xml 里看到相关配置),尽量使用 https 镜像仓库(或者删掉阻止规则?)。
IDEA 加载项目后可以利用自带的 Maven 执行 Package
操作,完成后会在 target 目录下两个 jar 文件。一个是默认打出来(被重命名过)的 jar.original,如果没有 spring-boot-maven-plugin,那么这个是生成的目标文件,但是这个 jar 是没办法直接通过 java -jar
运行的,它只包含了项目代码中最基本的文件,不包含运行 jar 必要的元信息和依赖,你可以通过命令查看一个 jar 包中的文件列表,例如:
1 | jar tf demo-0.0.1-SNAPSHOT.jar.original |
spring-boot-maven-plugin 插件会对 jar 重新打包,重新打包后的 jar 文件里包含了必要的元信息和依赖 jar 文件。直接把 jar 文件打包进 jar 的做法叫 jar in jar,其实 Maven 有插件能够搜集所有依赖并解压,然后把所有 class 文件重新打包成一个 jar,这样打包结果会更加精简,打包过程中可以做一些适当的裁剪,但是会和一些机制产生冲突(以后会解释)。
Bean 和注解
软件工程的核心就是无尽的抽象,所以有这么一类 Java 对象叫 Bean。Spring 框架使用 IoC 管理 Bean,Bean 使用注解维护自身的作用和相互的关系,Spring 应用启动时会扫描 Java Package 下的 Bean,并且实例化。
注解为 Java 代码提供元数据,Spring 运行时根据注解提供的信息扫描和实例化 Bean,比较常见的有 Component、Service、Repository、Controller、RequestMapping、Autowired、Resource、Configuration、Value。Component 表示这个 Bean 是 Spring 运行时需要关注的,其他用于修饰类的注解都会隐含 Component,例如 Service、Repository、Controller、Configuration。Autowired、Resource 和 Value 用来自动装配 Bean。
Controller 和 路由
定义路由主要用到 Controller 和 RequestMapping,Controller 表示每个方法返回一个视图,RequestMapping 用于配置路由信息,例如
1 |
|
如果想要 Controller 直接返回方法返回值对象或字符串作为 http response body,那么需要给类或者方法加上 @ResponseBody
,可以使用 @RestController
注解,它等价于同时 @Controller
和 @ResponseBody
。RequestMapping 注解默认不限制 http 方法,如果要指定方法可以用 @RequestMapping(value = "/home", method = RequestMethod.GET)
的写法或者等价写法 @GetMapping("/home")
。你还可以在类上增加 RequestMapping 注解来配置公共前缀:
1 |
|
Service 和 Repository
通过一个书籍管理的例子来聊聊 Service 和 Repository 的使用。
首先在 MySQL 建一个表,语句是用 Bard 生成的:
1 | -- bard: create mysql table for book, unique key isbn, created_at and updated_at time, named t_book |
Entity
Entity 是 JPA 要用到的概念。根据 t_book 表创建对应的 Entity:
1 |
|
这里涉及到了几个注解我简单解释一下:
- Entity 用来标记这是一个实体类,它和数据库的表、字段存在映射关系
- Table 用来提供更多信息,默认情况下表名来自于类名,但是 Book 对应的表为 t_book,所以需要手动指定
- Id 用来表示字段为一个数据库主键
- GeneratedValue 用来描述主键数据的生成方式,你可以配置策略或指定 generator
- GenerationType.IDENTITY 表示主键的值来自于表的自增
- Column 可以指定映射的字段名和一些修饰属性,在例子中的 created_at 和 updated_at 字段内容由数据库维护,所以我们需要屏蔽这些字段
Service
项目中的 Service 用来实现业务,在 Java 里通常会定义一个 Service 接口例如 BookService,在 impl 包中定义 BookServiceImpl 类来实现接口,@Service
注解添加到 BookServiceImpl 上。BookController 通过下面方法访问 BookService
1 |
|
这里不用去指定 BookServiceImpl,框架会自动找到接口对应的实现。
Spring 框架会管理所有的 Bean,如果要访问这些 Bean,可以通过 Application context 的工厂去获取,但最简单的就是 Autowired 注解让 Spring 框架去注入对象,如果我没理解错的话,被管理的 Bean 不会被多次实例化。
Repository
Repository 是数据库访问的 Bean,在示例里我使用了 JPA 框架访问数据库,写法是
1 |
|
JpaRepository 中已经定义了大量基础的数据访问方法,但如果你需要增加额外的查询方式,例如代码中我需要根据 ISBN 查询书籍,只需要在接口里增加一个 findByIsbn
方法就行,JPA 会解析接口所有方法名,根据方法名(或者你的自定义注解)生成查询方法,创建对应的实例。
配置
在 Spring Boot 项目中使用配置非常方便,既有 Autowired 自动装配,又能直接通过 Value 引入某个值。
如果你需要自动装配,那么那个类需要有 Configuration 注解
1 |
|
application.properties 文件中的
1 | app.home.greeting=Hello |
就能够自动装配使用配置。
Value 相关的使用,我会在后面介绍 Nacos 部分一起提到。
日志
Spring Boot 能自动适配主流日志库,默认情况下 spring-boot-starter-logging 使用 SLF4j 和 logback。
SLF4j 是一个常用的日志门面库,并且会结合 lombok 提供的注解 @Slf4j
一起使用,有这个注解的类可以直接使用 log 变量访问日志接口:
1 |
|
在 Spring Boot 项目中配置 logback 需要在 resources 底下创建一个 logback-spring.xml 文件进行配置。聊聊输出 JSON 格式日志到文件。
如果要用 JSON 格式一种就是使用 logback 的 LayoutWrappingEncoder,另一种是直接使用 logstash-logback-encoder。前者首先需要引入三个依赖
1 | <dependency> |
然后就是添加 logback-spring.xml 配置,encoder 使用 ch.qos.logback.core.encoder.LayoutWrappingEncoder
,layout 使用 ch.qos.logback.contrib.json.classic.JsonLayout
,formatter 使用 ch.qos.logback.contrib.jackson.JacksonJsonFormatter
,完整配置查看 Github 上的代码。
Docker CI 和部署
Docker CI 和部署其实不复杂,唯一让我这个外行迷惑的是为什么会有这么多个版本的 JDK。不过根据我观察,eclipse-temurin 是目前比较好的版本,所以也是我优先原则的构建和运行环境。打包选择了 maven:3.9-eclipse-temurin-17
作为 builder stage,运行使用 eclipse-temurin:17-jre-alpine
,162MB,算是比较小的选择。
我在构建命令里指定了 settings.xml,会使用阿里云作为 maven mirror,只是为了作为一个示例。另外为了方便打包结果交付,最好还是配置一下 build.finalName。
最后就是启动运行。打包时会把 resources 中的文件也合入 jar 包,但是线上环境必然不通,所以在启动时可以通过指定参数 --spring.config.location=path/to/application.properties
来指定一个外部的配置文件。
总结
因为机缘巧合,这次有机会简单尝试了 Java 的这套 Spring 框架,做为非 Java 开发者,所有内容都是我从网上东拼西凑的,在没有学习过的前提下只能理解这么多东西,难免内容有错误,我是真的不懂软件工程。相关代码我放 Github 上了:传送门。
你说的对,但这就是软件工程,明明什么都没说,就能水这么多字,一运行内存啊啊啊啊啊啊啊。
参考
- https://blog.51cto.com/u_15896157/5896000
- https://juejin.cn/post/7114681674402627598
- https://spring.io/guides/gs/accessing-data-jpa/
- https://segmentfault.com/a/1190000012322314
- https://juejin.cn/post/7084879248980181005
- https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.logging