前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot 入门

Spring Boot 入门

作者头像
RendaZhang
发布2020-11-11 10:45:23
2.2K0
发布2020-11-11 10:45:23
举报
文章被收录于专栏:Renda

SpringBoot 基本应用

约定优于配置

约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式。

本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置。比如说模型中有一个名为 User 的类,那么数据库中对应的表就会默认命名为 User。只有在偏离这一个约定的时候,例如想要将该表命名为 person,才需要写有关这个名字的配置。

比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进行开发编码测试之类的,这样就加强了开发效率与审查代码效率。所以说写代码的时候就需要按要求命名,这样统一规范的代码就有良好的可读性与维护性了。

约定优于配置简单来理解,就是遵循约定。

Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了尽可能快的跑起来 Spring 应用程序并且尽可能减少配置文件。

SpringBoot 概念
Spring 优缺点分析

优点:Spring 是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise Java Bean - EJB,Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

缺点:虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配 置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式 XML 配置。Spring 3.0 引入 了基于 Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。

所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring 实用,但与此同时它要求的回报也不少。

除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。SSM 整合:Spring、Spring MVC、Mybatis、Spring-Mybatis 整合包、数据库驱动,引入依赖的数量繁多、容易存在版本冲突。

Spring Boot 解决上述 Spring 的问题

SpringBoot 对上述 Spring 的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

起步依赖:

代码语言:javascript
复制
起步依赖本质上是一个 Maven 项目对象模型 (Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。 
简单的说,起步依赖就是将具备某种功能的依赖坐标打包到一起,并提供一些默认的功能。

自动配置:

代码语言:javascript
复制
springboot 的自动配置,指的是 springboot 会自动将一些配置类的 bean 注册进 ioc 容器,在需要的地方使用 @autowired 或者 @resource 等注解来使用它。
“自动”的表现形式就是只需要引想用功能的包,相关的配置完全不用管,springboot 会自动注入这些配置 bean,直接使用这些 bean 即可。

Springboot 可以简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。

SpringBoot 入门案例

案例需求:请求 Controller 中的方法,并将返回值响应到页面。

1) 依赖管理

pom.xml

代码语言:javascript
复制
<!--
    所用的 springBoot 项目都会直接或者间接的继承 spring-boot-starter-parent
    1.指定项目的编码格式为 UTF-8
    2.指定 JDK 版本为 1.8
    3.对项目依赖的版本进行管理,当前项目再引入其他常用的依赖时就需要再指定版本号,避免版本冲突的问题
    4.默认的资源过滤和插件管理
-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>

<dependencies>
    <!-- 引入 Spring Web 及 Spring MVC 相关的依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<!-- 可以将 project 打包为一个可以执行的 jar -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
2) 启动类

com.renda.SpringBootDemo1Application

代码语言:javascript
复制
/**
 * SpringBoot 的启动类通常放在二级包中,
 * 比如:com.renda.SpringBootDemo1Application。
 * 因为 SpringBoot 项目在做包扫描,
 * 会扫描启动类所在的包及其子包下的所有内容。
 *
 * @author Renda Zhang
 * @since 2020-10-28 23:11
 */
@SpringBootApplication // 标识当前类为 SpringBoot 项目的启动类
public class SpringBootDemo1Application {

    public static void main(String[] args) {
        // 样板代码
        SpringApplication.run(SpringBootDemo1Application.class, args);
    }

}
3) Controller

com.renda.controller.HelloController

代码语言:javascript
复制
@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String helloSpringBoot() {
        return "Hello Spring Boot";
    }

}
SpringBoot 快速构建

案例需求:请求 Controller 中的方法,并将返回值响应到页面。

1)使用 Spring Initializr 方式构建 Spring Boot 项目

本质上说,Spring Initializr 是一个 Web 应用,它提供了一个基本的项目结构,能够快速构建一个基础的 Spring Boot 项目。

注意使用快速方式创建 Spring Boot 项目时,所在主机须在联网状态下;本质上是在开发工具执行各项参数后,由 Spring 提供的 URL 所对应的服务器生成, IDEA 将服务器生成的 SpringBoot 项目下载到本地的工作空间中。

Project SDK 用于设置创建项目使用的 JDK 版本,这里,使用之前初始化设置好的 JDK 版本即可;在 Choose Initializr Service URL 下使用默认的初始化服务地址 https://start.spring.io 进行 Spring Boot 项目创建。

创建完成后的 pom 文件

代码语言:javascript
复制
<?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.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.renda</groupId>
    <artifactId>springbootdemo2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootdemo2</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

创建完成后,可以删除自动生成的 .mvn.gitignoreHELP.mdmvnwmvnw.cmd

创建好的 Spring Boot 项目结构如图:

代码语言:javascript
复制
/src/main/java/
    com.renda.Springbootdemo2Application - 项目主程序启动类
/src/main/resources/
    static - 静态资源文件夹
    templates - 模板页面文件夹
    application.properties - 全网配置文件
/src/test/java/
    com.renda.Springbootdemo2ApplicationTests - 项目测试类

使用 Spring Initializr 方式构建的 Spring Boot 项目会默认生成项目启动类、存放前端静态资源和页面的文件夹、编写项目配置的配置文件以及进行项目单元测试的测试类。

2)创建一个用于 Web 访问的 Controller

注意:确保项目启动类 Springbootdemo2Application 在 com.renda 包下。

com.renda.controller.HelloController

代码语言:javascript
复制
@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String hello() {
        return "What's up! Spring Boot!";
    }

}
3) 运行项目

application.properties 中修改 tomcat 的默认端口号:

代码语言:javascript
复制
server.port=8888

运行主程序启动类 Springbootdemo2Application,项目启动成功后,在控制台上会发现 Spring Boot 项目默认启动的端口号为 8888,此时,可以在浏览器上访问 http://localhost:8888/hello/boot。

页面输出的内容是 “What's up! Spring Boot!”,至此,构建 Spring Boot 项目就完成了。

单元测试与热部署
单元测试

开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。

1)添加 spring-boot-starter-test 测试依赖启动器,在项目的 pom.xml 文件中添加 spring-boot-starter-test 测试依赖启动器,示例代码如下 :

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

注意:使用 Spring Initializr 方式搭建的 Spring Boot 项目,会自动加入 spring-boot-starter-test 测试依赖启动器,无需再手动添加。

2)编写单元测试类和测试方法:

代码语言:javascript
复制
/**
 *  SpringJUnit4ClassRunner.class: Spring 运行环境
 *  JUnit4.class: JUnit 运行环境
 *  SpringRunner.class: Spring Boot 运行环境
 */
@RunWith(SpringRunner.class) // @RunWith: 运行器
@SpringBootTest // 标记为当前类为 SpringBoot 测试类,加载项目的 ApplicationContext 上下文环境
class Springbootdemo2ApplicationTests {

    @Autowired
    private HelloController helloController;

    @Test
    void contextLoads() {
        String result = helloController.hello();
        System.out.println(result);
    }

}

上述代码中,先使用 @Autowired 注解注入了 HelloController 实例对象,然后在 contextLoads() 方法中调用了 HelloController 类中对应的请求控制方法 hello(),并输出打印结果。

热部署

在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,Spring Boot 框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目 。

热部署:在修改完代码之后,不需要重新启动容器,就可以实现更新。

使用步骤:

1)添加 spring-boot-devtools 热部署依赖启动器。

在 Spring Boot 项目进行热部署测试之前,需要先在项目的 pom.xml 文件中添加 spring-boot-devtools 热部署依赖启动器:

代码语言:javascript
复制
<!-- 引入热部署依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

由于使用的是 IDEA 开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对 IDEA 开发工具进行热部署相关的功能设置。

2)开启 IDEA 的自动编译。

代码语言:javascript
复制
选择 IDEA 工具界面的 【File】->【Settings】 选项,打开 Compiler 面板设置页面。

选择 Build 下的 Compiler 选项,在右侧勾选 “Build project automatically” 选项将项目设置为自动编译,单击 【Apply】->【OK】 按钮保存设置。

3)开启 IDEA 的在项目运行中自动编译的功能。

代码语言:javascript
复制
在项目任意页面中使用组合快捷键 “Ctrl+Shift+Alt+/” 打开 Maintenance 选项框,选中并打开 Registry 页面。

列表中找到 “compiler.automake.allow.when.app.running”,将该选项后的 Value 值勾选,用于指定 IDEA 工具在程序运行过程中自动编译,最后单击 【Close】 按钮完成设置。
热部署效果测试

启动 Springbootdemo2Application http://localhost:8888/hello/boot

页面原始输出的内容是 “What's up! Spring Boot!”。

为了测试配置的热部署是否有效,接下来,在不关闭当前项目的情况下,将 HelloController 类中的请求处理方法 hello() 的返回值修改为 “Hello! Spring Boot!” 并保存,查看控制台信息会发现项目能够自动构建和编译,说明项目热部署生效。

再次刷新浏览器,输出了 “Hello! Spring Boot!”,说明项目热部署配置成功。

全局配置文件

全局配置文件能够对一些默认配置值进行修改。Spring Boot 使用一个 application.properties 或者 application.yaml 的文件作为全局配置文件,该文件存放在 src/main/resource 目录或者类路径的 /config,一般会选择 resource 目录。

Spring Boot 配置文件的命名及其格式:

  • application.properties
  • application.yaml
  • application.yml
`application.properties` 配置文件

引入相关依赖:

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

使用 Spring Initializr 方式构建 Spring Boot 项目时,会在 resource 目录下自动生成一个空的 application.properties 文件,Spring Boot 项目启动时会自动加载 application.properties 文件。

代码语言:javascript
复制
# 修改 tomcat 的版本号
server.port=8080
# 定义数据库的连接信息 JdbcTemplate
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/renda01
spring.datasource.username=root
spring.datasource.password=password

可以在 application.properties 文件中定义 Spring Boot 项目的相关属性,当然,这些相关属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置。

预先准备了两个实体类文件,后续会演示将 application.properties 配置文件中的自定义配置属性注入到 Person 实体类的对应属性中。

1)先在项目的 com.renda 包下创建一个 pojo 包,并在该包下创建两个实体类 Pet 和 Person。

代码语言:javascript
复制
/**
 * 宠物类
 *
 * @author Renda Zhang
 * @since 2020-10-29 1:03
 */
public class Pet {
    // 品种
    private String type;
    // 名称
    private String name;
    // setter and getter, toString ...
}

代码语言:javascript
复制
/**
 * 人类
 *
 * @author Renda Zhang
 * @since 2020-10-29 1:04
 */
@Component
/**
 * 将配置文件中所有以 person 开头的配置信息注入当前类中
 * 前提 1:必须保证配置文件中 person.xx 与当前 Person 类的属性名一致
 * 前提 2:必须保证当前 Person 中的属性都具有 set 方法
 */
@ConfigurationProperties(prefix = "person")
public class Person {
    // id
    private int id;
    // 名称
    private String name;
    // 爱好
    private List hobby;
    // 家庭成员
    private String[] family;
    private Map map;
    // 宠物
    private Pet pet;
    // setter and getter, toString ...
}

@ConfigurationProperties(prefix = "person") 注解的作用是将配置文件中以 person 开头的属性值通过 setXX() 方法注入到实体类对应属性中。

@Component 注解的作用是将当前注入属性值的 Person 类对象作为 Bean 组件放到 Spring 容器中,只有这样才能被 @ConfigurationProperties 注解进行赋值。

2)打开项目的 resources 目录下的 application.properties 配置文件,在该配置文件中编写需要对 Person 类设置的配置属性。

代码语言:javascript
复制
# 自定义配置信息注入到 Person 对象中
person.id=100
person.name=张人大
## list
person.hobby=看书,写代码,吃饭
## String[]
person.family=兄弟,父母
## map
person.map.k1=v1
person.map.k2=v2
## pet 对象
person.pet.type=dog
person.pet.name=旺财

3)查看 application.properties 配置文件是否正确,同时查看属性配置效果,打开通过 IDEA 工具创建的项目测试类,在该测试类中引入 Person 实体类 Bean,并进行输出测试。

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Person person;

    @Test
    void showPersonInfo() {
        System.out.println(person);
    }

}

可以看出,测试方法 showPersonInfo() 运行成功,同时正确打印出了 Person 实体类对象。至此,说明 application.properties 配置文件属性配置正确,并通过相关注解自动完成了属性注入。

4)解决浏览器请求出现中文乱码问题。

调整文件编码格式:Settings -> Editor -> File Encodings -> 确保 Global Encoding 和 Project Encoding 为 UTF-8,修改 Default ecoding for properties files 为 UTF-8 并勾选 Transparent native-to-ascii conversion。

application.properties 文件中增加配置:

代码语言:javascript
复制
# 解决中文乱码
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
`application.yaml` 配置文件

YAML 文件格式是 Spring Boot 支持的一种 JSON 文件格式,相较于传统的 Properties 配置文件,YAML 文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。application.yaml 配置文件的工作原理和 application.properties 是一样的,只不过 yaml 格式配置文件看起来更简洁一些。

  • YAML 文件的扩展名可以使用 .yml 或者 .yaml
  • application.yml 文件使用 key: value 格式配置属性,使用缩进控制层级关系。

SpringBoot 的三种配置文件是可以共存的:application.propertiesapplication.yamlapplication.yml

1)value 值为普通数据类型(例如数字、字符串、布尔等)

当 YAML 配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值,同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下:

代码语言:javascript
复制
server:
  port: 8080
  servlet:
    context-path: /hello

2)value 值为数组和单列集合

当 YAML 配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法。

其中,缩进式写法还有两种表示形式,示例代码如下:

代码语言:javascript
复制
person: 
  hobby: 
    - play
    - read
    - sleep 

或者使用如下示例形式:

代码语言:javascript
复制
person:
  hobby:
    play,
    read,
    sleep

上述代码中,在 YAML 配置文件中通过两种缩进式写法对 person 对象的单列集合(或数组)类型的爱好 hobby 赋值为 play、read 和 sleep。其中一种形式为 “- 属性值”,另一种形式为多个属性值之前加英文逗号分隔;注意,最后一个属性值后不要加逗号。

代码语言:javascript
复制
person: 
  hobby: [play,read,sleep]

通过上述示例对比发现,YAML 配置文件的行内式写法更加简明、方便。另外,包含属性值的中括号 “[]” 还可以进一步省略,在进行属性赋值时,程序会自动匹配和校对。

3)value 值为 Map 集合和对象

当 YAML 配置文件中配置的属性值为 Map 集合或对象类型时,YAML 配置文件格式同样可以分为两种书写方式 - 缩进式写法和行内式写法。

其中,缩进式写法的示例代码如下:

代码语言:javascript
复制
person: 
  map: 
    k1: v1
    k2: v2

对应的行内式写法示例代码如下:

代码语言:javascript
复制
person:
  map: {k1: v1,k2: v2}

在YAML配置文件中,配置的属性值为Map集合或对象类型时,缩进式写法的形式按照YAML文件格式编写即可,而行内式写法的属性值要用大括号“{}”包含。

接下来,在 Properties 配置文件演示案例基础上,注释掉 Properties 中跟 Person 相关的注入,然后通过配置 application.yaml 配置文件对 Person 对象进行赋值,具体使用如下。

在项目的 resources 目录下,新建一个 application.yaml 配置文件,在该配置文件中编写为 Person 类设置的配置属性:

代码语言:javascript
复制
person:
  id: 1000
  name: 张人大
  hobby:
    - 跑步
    - 瑜伽
    - 游泳
  family:
    - 张小明
    - 李小红
  map:
    k1: 这是 k1 对应的 value
    k2: 这是 k2 对应的 value
  pet:
    type: dog
    name: 金毛

再次执行测试。

可以看出,测试方法 showPersonInfo() 同样运行成功,并正确打印出了 Person 实体类对象。

需要说明的是,本次使用 application.yaml 配置文件进行测试时需要提前将 application.properties 配置文件中编写的配置注释,这是因为 application.properties 配置文件会覆盖 application.yaml 配置文件。

配置文件属性值的注入

spring-boot-starter-parent 的 pom 文件可以知道配置文件的优先级从低到高如下:

代码语言:javascript
复制
<includes>
    <include>**/application*.yml</include>
    <include>**/application*.yaml</include>
    <include>**/application*.properties</include>
</includes>

使用 Spring Boot 全局配置文件设置属性时:

代码语言:javascript
复制
如果配置属性是 Spring Boot 已有属性,例如服务端口 server.port,那么 Spring Boot 内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性。

如果配置的属性是用户自定义属性,例如刚刚自定义的 Person 实体类属性,还必须在程序中注入这些配置属性方可生效。

Spring Boot 支持多种注入配置文件属性的方式,下面来介绍如何使用注解 @ConfigurationProperties@Value 注入属性。

使用 `@ConfigurationProperties` 注入属性

Spring Boot 提供的 @ConfigurationProperties 注解用来快速、方便地将配置文件中的自定义属性值批量注入到某个 Bean 对象的多个对应属性中。假设现在有一个配置文件,如果使用 @ConfigurationProperties 注入配置文件的属性,示例代码如下:

代码语言:javascript
复制
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private int id;
    private String name;
    private List hobby;
    private String[] family;
    private Map map;
    private Pet pet;
    // setter and getter, toString ...
}

上述代码使用 @Component@ConfigurationProperties(prefix = “person”) 将配置文件中的每个属性映射到 person 类组件中。

使用 `@Value` 注入属性

@Value 注解是 Spring 框架提供的,用来读取配置文件中的属性值并逐个注入到 Bean 对象的对应属性中,Spring Boot 框架从 Spring 框架中对 @Value 注解进行了默认继承,所以在 Spring Boot 框架中还可以使用该注解读取和注入配置文件属性值。使用 @Value 注入属性的示例代码如下:

代码语言:javascript
复制
@Component
public class Student {
    @Value("${person.id}")
    private int number;
    // getter setter toString ...
}

上述代码中,使用 @Component@Value 注入 Person 实体类的id属性。其中,@Value 不仅可以将配置文件的属性注入 Person 的 id 属性,还可以直接给 id 属性赋值,这点是@ConfigurationProperties 不支持的。

1)在 com.renda.pojo 包下新创建一个实体类 Student,并使用 @Value 注解注入属性:

代码语言:javascript
复制
@Component
public class Student {

    @Value("${person.id}")
    private String number;

    @Value("${person.name}")
    private String name;

    // getter setter toString ...
}

Student 类使用 @Value 注解将配置文件的属性值读取和注入。

从上述示例代码可以看出,使用 @Value 注解方式需要对每一个属性注入设置,同时又免去了属性的 setXX() 方法。

2)再次打开测试类进行测试:

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Student student;

    @Test
    public void showStudentInfo(){
        System.out.println(student);
    }

}

可以看出,测试方法 showStudentInfo() 运行成功,同时正确打印出了 Student 实体类对象。需要说明的是,本示例中只是使用 @Value 注解对实例中 Student 对象的普通类型属性进行了赋值演示,而 @Value 注解对于包含 Map 集合、对象以及 YAML 文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误。

自定义配置

Spring Boot 免除了项目中大部分的手动配置,对于一些特定情况,可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在 application.yml 文件中,Spring Boot 会自动加载全局配置文件从而免除手动加载的烦恼。但是,如果自定义配置文件,Spring Boot 是无法识别这些配置文件的,此时就需要手动加载。

使用 `@PropertySource` 加载配置文件

对于这种加载自定义配置文件的需求,可以使用 @PropertySource 注解来实现。@PropertySource 注解用于指定自定义配置文件的具体位置和名称。

如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用 @ConfigurationProperties 或者 @Value 注解进行属性值注入。

1)打开 Spring Boot 项目的 resources 目录,在项目的类路径下新建一个 my.properties 自定义配置文件,在该配置文件中编写需要设置的配置属性。

代码语言:javascript
复制
# 对实体类对象 Product 进行属性配置
product.id=99
product.name=小米

2)在 com.renda.pojo 包下新创建一个配置类 Product,提供 my.properties 自定义配置文件中对应的属性,并根据 @PropertySource 注解的使用进行相关配置。

代码语言:javascript
复制
@Component
@PropertySource("classpath:my.properties")
@ConfigurationProperties(prefix = "product")
public class Product {
    private int id;
    private String name;
    // getter setter toString ...
}

主要是一个自定义配置类,通过相关注解引入了自定义的配置文件,并完成了自定义属性值的注入。针对示例中的几个注解,具体说明如下:

  • @PropertySource("classpath:my.properties") 注解指定了自定义配置文件的位置和名称,此示例表示自定义配置文件为 classpath 类路径下的 my.properties 文件。
  • @ConfigurationProperties(prefix = "product") 注解将上述自定义配置文件 my.properties 中以 product 开头的属性值注入到该配置类属性中。

3)进行测试。

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Product product;

    @Test
    public void showProductInfo() {
        System.out.println(product);
    }

}
使用 `@Configuration` 编写自定义配置类

在 Spring Boot 框架中,推荐使用配置类的方式向容器中添加和配置组件。

在 Spring Boot 框架中,通常使用 @Configuration 注解定义一个配置类,Spring Boot 会自动扫描和识别配置类,从而替换传统 Spring 框架中的 XML 配置文件。

当定义一个配置类后,还需要在类中的方法上使用 @Bean 注解进行组件配置,将方法的返回对象注入到 Spring 容器中,并且组件名称默认使用的是方法名,当然也可以使用 @Bean 注解的 name 或 value 属性自定义组件的名称。

1)在项目下新建一个 com.renda.config 包,并在该包下新创建一个类 MyService,该类中不需要编写任何代码:

代码语言:javascript
复制
package com.renda.service;
public class MyService {
}

创建了一个空的 MyService 类,而该类目前没有添加任何配置和注解,因此还无法正常被 Spring Boot 扫描和识别。

2)在项目的 com.renda.config 包下,新建一个类 MyConfig,并使用 @Configuration 注解将该类声明一个配置类,内容如下:

代码语言:javascript
复制
@Configuration // 标识当前类是一个配置类,SpringBoot 会扫描该类,将所有标识 @Bean 注解的方法的返回值注入的容器中
public class MyConfig {

    @Bean // 注入的名称就是方法的名称,注入的类型就是返回值的类型
    public MyService myService(){
        return new MyService();
    }

    @Bean("service_")
    public MyService myService2(){
        return new MyService();
    }

}

MyConfig 是 @Configuration 注解声明的配置类(类似于声明了一个 XML 配置文件),该配置类会被 Spring Boot 自动扫描识别;使用 @Bean 注解的 myService() 方法,其返回值对象会作为组件添加到了 Spring 容器中(类似于 XML 配置文件中的标签配置),并且该组件的 id 默认是方法名 myService

3)测试类:

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testConfig(){
        System.out.println(applicationContext.containsBean("myService"));
        System.out.println(applicationContext.containsBean("service_"));
    }

}

上述代码中,先通过 @Autowired 注解引入了 Spring 容器实例 ApplicationContext,然后在测试方法 testConfig() 中测试查看该容器中是否包括 id 为 myServiceservice_ 的组件。

从测试结果可以看出,测试方法 testConfig() 运行成功,显示运行结果为 true,表示 Spirng 的 IOC 容器中也已经包含了 id 为 myServiceservice_的实例对象组件,说明使用自定义配置类的形式完成了向 Spring 容器进行组件的添加和配置。

SpringBoot 原理深入及源码剖析

依赖管理

在 Spring Boot 入门程序中,项目 pom.xml 文件有两个核心依赖,分别是 spring-boot-starter- parent 和 spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:

spring-boot-starter-parent 依赖
代码语言:javascript
复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/>
</parent>

上述代码中,将 spring-boot-starter-parent 依赖作为 Spring Boot 项目的统一父项目依赖管理,并将项目版本号统一为 2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的。

使用 “Ctrl+鼠标左键” 进入并查看 spring-boot-starter-parent 底层源文件,发现 spring-boot- starter-parent 的底层有一个父依赖 spring-boot-dependencies,核心代码具体如下

代码语言:javascript
复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

继续查看 spring-boot-dependencies 底层源文件,核心代码具体如下:

代码语言:javascript
复制
<properties>
    <activemq.version>5.15.11</activemq.version>
    ...
    <solr.version>8.2.0</solr.version>
    <spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
    <spring-batch.version>4.2.1.RELEASE</spring-batch.version>
    <spring-cloud-connectors.version>2.0.7.RELEASE</spring-cloud-connectors.version>
    <spring-data-releasetrain.version>Moore-SR3</spring-data-releasetrain.version>
    <spring-framework.version>5.2.2.RELEASE</spring-framework.version>
    <spring-hateoas.version>1.0.2.RELEASE</spring-hateoas.version>
    <spring-integration.version>5.2.2.RELEASE</spring-integration.version>
    <spring-kafka.version>2.3.4.RELEASE</spring-kafka.version>
    <spring-ldap.version>2.3.2.RELEASE</spring-ldap.version>
    <spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
    <spring-retry.version>1.2.4.RELEASE</spring-retry.version>
    <spring-security.version>5.2.1.RELEASE</spring-security.version>
    <spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
    <spring-ws.version>3.0.8.RELEASE</spring-ws.version>
    <sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
    <sun-mail.version>${jakarta-mail.version}</sun-mail.version>
    <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
    <thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
    ...
</properties>

从 spring-boot-dependencies 底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如 activemq、spring、tomcat 等,都有与 Spring Boot 2.2.2 版本相匹配的版本,这也是 pom.xml 引入依赖文件不需要标注依赖文件版本号的原因。

需要说明的是,如果 pom.xml 引入的依赖文件不是 spring-boot-starter-parent 管理的,那么在 pom.xml 引入依赖文件时,需要使用标签指定依赖文件的版本号。

spring-boot-starter-web 依赖

spring-boot-starter-parent 父依赖启动器的主要作用是进行版本统一管理;项目运行依赖的 JAR 包是从 Spring Boot Starters 中导入。

查看 spring-boot-starter-web 依赖文件源码,核心代码具体如下:

代码语言:javascript
复制
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
        <exclusions>
            <exclusion>
                <artifactId>tomcat-embed-el</artifactId>
                <groupId>org.apache.tomcat.embed</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

从上述代码可以发现,spring-boot-starter-web 依赖启动器的主要作用是提供 Web 开发场景所需的底层所有依赖。

正是如此,在 pom.xml 中引入 spring-boot-starter-web 依赖启动器时,就可以实现 Web 场景开发,而不需要额外导入 Tomcat 服务器以及其他 Web 依赖文件等。当然,这些引入的依赖文件的版本号还是由 spring-boot-starter-parent 父依赖进行的统一管理。

Spring Boot Starters:

https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE/spring-boot-project/spring-boot-starters

https://mvnrepository.com/search?q=starter

Spring Boot 除了提供有上述介绍的 Web 依赖启动器外,还提供了其他许多开发场景的相关依赖,可以打开 Spring Boot 官方文档,搜索 “Starters” 关键字查询场景依赖启动器。

需要说明的是,Spring Boot 官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架 MyBatis、阿里巴巴的 Druid 数据源等,Spring Boot 官方就没有提供对应的依赖启动器。为了充分利用 Spring Boot 框架的优势,在 Spring Boot 官方没有整合这些技术框架的情况下,MyBatis、Druid 等技术框架所在的开发团队主动与 Spring Boot 框架进行了整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter 等。在 pom.xml 文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。

自动配置

概念:能够在添加 jar 包依赖的时候,自动配置一些组件的相关配置,无需手动配置或者只需要少量手动配置就能运行编写的项目。

Spring Boot 应用的启动入口是 @SpringBootApplication 注解标注类中的 main() 方法。

@SpringBootApplication:SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。

进入到 @SpringBootApplication 内:

代码语言:javascript
复制
// 注解的适用范围, Type 表示注解可以描述在类、接口、注解或枚举中
@Target(ElementType.TYPE)
// 表示注解的生命周期,Runtime 运行时
@Retention(RetentionPolicy.RUNTIME)
// 表示注解可以记录在 javadoc 中
@Documented
// 表示可以被子类继承该注解
@Inherited
// 标明该类为 Spring Boot 配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 根据 class 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是 class 类型。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    // 根据 classname 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是class的全类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 扫描特定的包,参数类似是 Class 类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    ...   
}

从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,前面 4 个是注解的元数据信息, 主要看后面 3 个注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 三个核心注解。

@SpringBootConfiguration 注解

@SpringBootConfiguration: SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot 的配置类。

查看 @SpringBootConfiguration 注解源码:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    ...
}

从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解 @Configuration,该注解是 Spring 框架提供的,表示当前类为一个配置类(XML 配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。

`@EnableAutoConfiguration` 注解

@EnableAutoConfiguration:开启自动配置功能,以前需要手动配置的东西,现在由 SpringBoot 自动配置,这个注解就是 Springboot 能实现自动配置的关键。

查看该注解内部查看源码信息,核心代码具体如下:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// Spring 的底层注解 @Import,给容器中导入一个组件;导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)
// 告诉 SpringBoot 开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};

}

可以发现它是一个组合注解, Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器。@EnableAutoConfiguration 就是借助 @Import 来收集所有符合自动配置条件的 bean 定义,并加载到 IoC 容器。

@AutoConfigurationPackage 注解

查看 @AutoConfigurationPackage 注解内部源码信息,核心代码具体如下:

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解实现的,它是 spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法,这个方法就是导入组件类的具体实现:

代码语言:javascript
复制
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

从上述源码可以看出,在 Registrar 类中有一个 registerBeanDefinitions() 方法,使用 Debug 模式启动项目, 可以看到选中的部分就是 com.renda。也就是说,@AutoConfigurationPackage 注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到 spring 容器中。

因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。

@Import({AutoConfigurationImportSelector.class}) 注解

AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器 ApplicationContext 中。

继续研究 AutoConfigurationImportSelector 这个类,通过源码分析这个类中是通过selectImports 这个方法告诉 springboot 都需要导入那些组件:

代码语言:javascript
复制
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 获得自动配置元信息,需要传入 beanClassLoader 这个类加载器
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

深入研究 loadMetadata 方法 :

代码语言:javascript
复制
// 文件中为需要加载的配置类的类路径
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // 读取 spring-boot-autoconfigure-2.1.5.RELEASE.jar 包中的 spring-autoconfigure-metadata.properties 的信息生成 url
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        // 解析 urls 枚举对象中的信息封装成 properties 对象并加载
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        // 根据封装好的 properties 对象生成 AutoConfigurationMetadata 对象并返回
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

AutoConfigurationImportSelectorgetAutoConfigurationEntry 方法:

代码语言:javascript
复制
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 判断 EnabledAutoConfiguration 注解有没有开启, 默认开启
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获得注解的属性信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取默认支持的自动配置类列表
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重
    configurations = removeDuplicates(configurations);
    // 去除一些多余的配置类,根据 @EnabledAutoConfiguration 的 exclusions 属性进行排除
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 根据 pom 文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
    configurations = filter(configurations, autoConfigurationMetadata);
    // 触发自动配置导入监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

深入 getCandidateConfigurations 方法:

这个方法中有一个重要方法 loadFactoryNames,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。

代码语言:javascript
复制
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    /**
     * loadFactoryNames:
     * 这个方法需要传入两个参数 getSpringFactoriesLoaderFactoryClass() 和 
     * getBeanClassLoader()。
     * getSpringFactoriesLoaderFactoryClass() 这个方法返回的是
     * @EnableAutoConfiguration.class。
     * getBeanClassLoader() 这个方法返回的是 beanClassLoader 类加载器
     */
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

/**
 * Return the class used by {@link SpringFactoriesLoader} to load configuration
 * candidates.
 * @return the factory class
 */
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

...

@Override
public void setBeanClassLoader(ClassLoader classLoader) {
    this.beanClassLoader = classLoader;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}

继续点开 loadFactoryNames 方法:

代码语言:javascript
复制
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 获取出入的键
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 如果类加载器不为 null,则加载类路径下 spring.factories 文件,将其中设置的配置类的全路径信息封装为 Enumeration 类对象
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,加入方法 result 集合中
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

上面代码需要读取的 FACTORIES_RESOURCE_LOCATION 最终路径的长这样,而这个是 spring 提供的一个工具类:

代码语言:javascript
复制
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

它其实是去加载一个外部的文件,而这文件是在 spring-boot-autoconfigure-2.2.2.RELEASE.jar 包下的 META-INF/spring.factories,打开 spring.factories 文件:

代码语言:javascript
复制
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...

@EnableAutoConfiguration 就是从 classpath 中搜寻 META-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射 Java Refletion 实例化为对应的标注了 @Configuration 的 JavaConfig 形式的配置类,并加载到 IOC 容器中。

以入门项目为例,在项目中加入了 Web 环境依赖启动器,对应的 WebMvcAutoConfiguration 自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对 Spring MVC 运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC 校验器等。而这些自动配置类的本质是传统 Spring MVC 框架中对应的 XML 配置文件,只不过在 Spring Boot 中以自动配置类的形式进行了预先配置。因此,在 Spring Boot 项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,也可以对这些自动配置类中默认的配置进行更改。

总结:

代码语言:javascript
复制
因此 springboot 底层实现自动配置的步骤是:

. springboot 应用启动。

. @SpringBootApplication 起作用。

. @EnableAutoConfiguration。

. @AutoConfigurationPackage:这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通过将 Registrar 类导入到容器中,而 
Registrar 类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到 springboot 创建管理的容器中。

. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector 类导入到容器中;AutoConfigurationImportSelector 类作用是通过 selectImports 方法执行的过程中,会使用内部工具类 SpringFactoriesLoader,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。
@ComponentScan 注解

@ComponentScan 注解具体扫描的包的根路径由 Spring Boot 项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Spring Boot 项目主程序启动类所在包的具体位置。

总结
代码语言:javascript
复制
@SpringBootApplication 的注解的功能简单来说就是  个注解的组合注解:

|- @SpringBootConfiguration
   // 通过 javaConfig 的方式来添加组件到 IOC 容器中
   |- @Configuration
|- @EnableAutoConfiguration
   // 自动配置包,与 @ComponentScan 扫描到的添加到 IOC
   |- @AutoConfigurationPackage
   // 到 META-INF/spring.factories 中定义的 bean 添加到 IOC 容器中
   |- @Import(AutoConfigurationImportSelector.class) 
// 包扫描
|- @ComponentScan

SpringBoot 数据访问

Spring Boot 整合 MyBatis

MyBatis 是一款优秀的持久层框架,Spring Boot 官方虽然没有对 MyBatis 进行整合,但是 MyBatis 团队自行适配了对应的启动器,进一步简化了使用 MyBatis 进行数据的操作。

因为 Spring Boot 框架开发的便利性,所以实现 Spring Boot 与数据访问层框架(例如 MyBatis)的整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可。

基础环境搭建
1)数据准备

在 MySQL 中,先创建了一个数据库 springbootdata,然后创建了两个表 t_articlet_comment 并向表中插入数据。其中评论表 t_commenta_id 与文章表 t_article 的主键 id 相关联。

代码语言:javascript
复制
# 创建数据库
CREATE DATABASE IF NOT EXISTS springbootdata DEFAULT CHARACTER SET utf8;
# 选择使用数据库
USE springbootdata;

# 创建表 t_article 并插入相关数据
DROP TABLE IF EXISTS t_article;
CREATE TABLE t_article
(
    id      int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id',
    title   varchar(200) DEFAULT NULL COMMENT '文章标题',
    content longtext COMMENT '文章内容',
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET=utf8;

INSERT INTO t_article VALUES (1, 'Spring Boot 基础入门', '从入门到精通讲解...');
INSERT INTO t_article VALUES (2, 'Spring Cloud 基础入门', '从入门到精通讲解...');

# 创建表 t_comment 并插入相关数据
DROP TABLE IF EXISTS t_comment;
CREATE TABLE t_comment
(
    id      int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id',
    content longtext COMMENT '评论内容',
    author  varchar(200) DEFAULT NULL COMMENT '评论作者',
    a_id    int(20)      DEFAULT NULL COMMENT '关联的文章id',
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 3 DEFAULT CHARSET=utf8;
INSERT INTO t_comment VALUES (1, '很全、很详细', 'lucy', 1);
INSERT INTO t_comment VALUES (2, '赞一个', 'tom', 1);
INSERT INTO t_comment VALUES (3, '很详细', 'eric', 1);
INSERT INTO t_comment VALUES (4, '很好,非常详细', '张三', 1);
INSERT INTO t_comment VALUES (5, '很不错', '李四', 2);
2)创建项目,引入相应的启动器

使用 Spring Initializr 来初始化项目。

项目名:springbootmybatis

包名:com.renda

启动器:SQL 的 MyBatis Framework、MySQL Driver,Web 的 Spring Web

3)编写与数据库表 t_comment 和 t_article 对应的实体类 Comment 和 Article

com.renda.pojo.Comment

代码语言:javascript
复制
public class Comment {
    private Integer id;
    private String content;
    private String author;
    private Integer aId;
    // getter setter toString ...
}

com.renda.pojo.Article

代码语言:javascript
复制
public class Article {
    private Integer id;
    private String title;
    private String content;
    // getter setter toString ...
}
4)编写配置文件

application.properties 更名为 application.yml。在 application.properties 配置文件中进行数据库连接配置:

代码语言:javascript
复制
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=UTF-8
    username: root
    password: password
注解方式整合 Mybatis

需求:实现通过 ID 查询 Comment 信息。

1)创建一个对 t_comment 表数据操作的接口 CommentMapper

com.renda.mapper.CommentMapper

代码语言:javascript
复制
public interface CommentMapper {
    @Select("select * from t_comment where id = #{id}")
    Comment findById(Integer id);
}
2)在 Spring Boot 项目启动类上添加 @MapperScan("xxx") 注解

com.renda.SpringbootmybatisApplication

代码语言:javascript
复制
@SpringBootApplication
@MapperScan("com.renda.bootmybatis.mapper") //执行扫描mapper的包名
public class SpringbootmybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootmybatisApplication.class, args);
    }

}
3)编写测试方法

导入 Junit 的依赖,增加测试方法:

com.renda.SpringbootmybatisApplicationTests

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private CommentMapper commentMapper;

    @Test
    void findCommentById() {
        Comment comment = commentMapper.findById();
        System.out.println(comment);
    }

}

控制台中查询的 Comment 的 aId 属性值为 null,没有映射成功。这是因为编写的实体类 Comment 中使用了驼峰命名方式将 t_comment 表中的 a_id 字段设计成了 aId 属性,所以无法正确映射查询结果。

为了解决上述由于驼峰命名方式造成的表字段值无法正确映射到类属性的情况,可以在 Spring Boot 全局配置文件 application.yml 中添加开启驼峰命名匹配映射配置,示例代码如下:

代码语言:javascript
复制
mybatis:
  configuration:
    # 开启驼峰命名匹配映射
    map-underscore-to-camel-case: true
配置文件的方式整合 MyBatis

第一、二步骤使用 Free Mybatis plugin 插件生成:使用 IDEA 连接 Database,然后选中要自动生成代码的表,右键 -> mybatis-generator -> 按照需求输入信息,点击 ok。

1)创建一个用于对数据库表 t_article 数据操作的接口 ArticleMapper
代码语言:javascript
复制
public interface ArticleMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(Article record);

    int insertSelective(Article record);

    Article selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Article record);

    int updateByPrimaryKey(Article record);
}
2)创建 XML 映射文件

resources 目录下创建一个统一管理映射文件的包 mapper,并在该包下编写与 ArticleMapper 接口方应的映射文件 ArticleMapper.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.renda.mapper.ArticleMapper">
  <resultMap id="BaseResultMap" type="com.renda.pojo.Article">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="title" jdbcType="VARCHAR" property="title" />
    <result column="content" jdbcType="VARCHAR" property="content" />
  </resultMap>
  <sql id="Base_Column_List">
    id, title, content
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_article
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from t_article
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.Article" useGeneratedKeys="true">
    insert into t_article (title, content)
    values (#{title,jdbcType=VARCHAR}, #{content,jdbcType=VARCHAR})
  </insert>
  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.Article" useGeneratedKeys="true">
    insert into t_article
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="title != null">
        title,
      </if>
      <if test="content != null">
        content,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="title != null">
        #{title,jdbcType=VARCHAR},
      </if>
      <if test="content != null">
        #{content,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.renda.pojo.Article">
    update t_article
    <set>
      <if test="title != null">
        title = #{title,jdbcType=VARCHAR},
      </if>
      <if test="content != null">
        content = #{content,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.renda.pojo.Article">
    update t_article
    set title = #{title,jdbcType=VARCHAR},
      content = #{content,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>
3)配置 XML 映射文件路径

在项目中编写的 XML 映射文件,Spring Boot 并无从知晓,所以无法扫描到该自定义编写的 XML 配置文件,还必须在全局配置文件 application.yml 中添加 MyBatis 映射文件路径的配置,同时需要添加实体类别名映射路径,示例代码如下:

代码语言:javascript
复制
mybatis:
  configuration:
    # 开启驼峰命名匹配映射
    map-underscore-to-camel-case: true
  # 加载 resources/mapper 文件夹下的所有的 xml 文件
  mapper-locations: classpath:mapper/*.xml
  # 配置 XML 映射文件中指定的实体类别名路径
  type-aliases-package: com.renda.pojo
4)编写单元测试进行接口方法测试
代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    @Autowired
    private ArticleMapper articleMapper;

    @Test
    void findArticleById() {
        Article article = articleMapper.selectByPrimaryKey();
        System.out.println(article);
    }

}
Spring Boot 整合 Redis
1)添加Redis依赖包

在项目的 pom.xml 中添加如下:

代码语言:javascript
复制
<!-- redis 依赖包 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2)配置 Redis 数据库连接

在 application.yml 中配置redis数据库连接信息,如下:

代码语言:javascript
复制
spring:
  redis:
    # Redis 服务器地址
    host: 192.168.186.128
    # Redis 服务器连接端口
    port: 6379
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 18
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 3000
        # 连接池中的最大空闲连接
        max-idle: 20
        # 连接池中的最小空闲连接
        min-idle: 2
    # 连接超时时间(毫秒)
    timeout: 3000
    # Redis 数据库索引(默认为 0)
    database: 0
3)编写 Redis 操作工具类

将 RedisTemplate 实例包装成一个工具类,便于对 redis 进行数据操作。

代码语言:javascript
复制
package com.renda.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Renda Zhang
 * @since 2020-10-30 1:00
 */
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 读取缓存
     */
    public Object get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 写入缓存
     */
    public boolean set(String key, Object value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().set(key, value, , TimeUnit.DAYS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 更新缓存
     */
    public boolean getAndSet(final String key, String value) {
        boolean result = false;
        try {
            redisTemplate.opsForValue().getAndSet(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 删除缓存
     */
    public boolean delete(final String key) {
        boolean result = false;
        try {
            redisTemplate.delete(key);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
4)测试

写一个测试用例类来完成对 redis 的整合。

代码语言:javascript
复制
@RunWith(SpringRunner.class)
@SpringBootTest
class SpringbootmybatisApplicationTests {

    // 写入,key:1,value:mysql 数据库中 id 为 1 的 article 记录
    @Autowired
    private RedisUtils redisUtils;

    @Test
    void writeRedis() {
        redisUtils.set("1", articleMapper.selectByPrimaryKey());
        System.out.println("success");
    }

    @Test
    void readRedis() {
        Article article = (Article) redisUtils.get("1");
        System.out.println(article);
    }

}

SpringBoot 视图技术

支持的视图技术

前端模板引擎技术的出现,使前端开发人员无需关注后端业务的具体实现,只关注自己页面的呈现效果即可,并且解决了前端代码错综复杂的问题、实现了前后端分离开发。Spring Boot 框架对很多常用的模板引擎技术(如:FreeMarker、Thymeleaf、Mustache 等)提供了整合支持。

Spring Boot 不太支持常用的 JSP 模板,并且没有提供对应的整合配置,这是因为使用嵌入式 Servlet 容器的 Spring Boot 应用程序对于 JSP 模板存在一些限制 :

  • 在 Jetty 和 Tomcat 容器中,Spring Boot 应用被打包成 war 文件可以支持 JSP。但 Spring Boot 默认使用嵌入式 Servlet 容器以 JAR 包方式进行项目打包部署,这种 JAR 包方式不支持 JSP。
  • 如果使用 Undertow 嵌入式容器部署 Spring Boot 项目,也不支持 JSP 模板。(Undertow 是红帽公司开发的一款基于 NIO 的高性能 Web 嵌入式服务器)
  • Spring Boot 默认提供了一个处理请求路径 “/error” 的统一错误处理器,返回具体的异常信息。使用 JSP 模板时,无法对默认的错误处理器进行覆盖,只能根据 Spring Boot 要求在指定位置定制错误页面。
Thymeleaf

Thymeleaf 是一种现代的基于服务器端的 Java 模板引擎技术,也是一个优秀的面向 Java 的 XML、XHTML、HTML5 页面模板,它具有丰富的标签语言、函数和表达式,在使用 Spring Boot 框架进行页面设计时,一般会选择 Thymeleaf 模板。

Thymeleaf 语法

在 HTML 页面上使用 Thymeleaf 标签,Thymeleaf 标签能够动态地替换掉静态内容,使页面动态展示。

为了更直观的认识 Thymeleaf,下面展示一个在 HTML 文件中嵌入了 Thymeleaf 的页面文件,示例代码如下:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" type="text/css" media="all"
              href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
        <title>Thymeleaf</title>
    </head>
    <body>
        <p th:text="${hello}">Hello Thymeleaf</p>
    </body>
</html>

上述代码中,“xmlns:th="http://www.thymeleaf.org" 用于引入 Thymeleaf 模板引擎标签,使用关键字 th 标注标签是 Thymeleaf 模板提供的标签,其中,th:href="@{/css/gtvg.css}" 用于引入外联样式文件,th:text="${hello}" 用于动态显示标签文本内容。

常用标签:

  • th:insert - 布局标签,替换内容到引入的文件
  • th:replace - 页面片段包含(类似 JSP 中的 include 标签)
  • th:each - 元素遍历(类似 JSP 中的 c:forEach 标签)
  • th:if - 条件判断,如果为真
  • th:unless - 条件判断,如果为假
  • th:switch - 条件判断,进行选择性匹配
  • th:case - 条件判断,进行选择性匹配
  • th:value - 属性值修改,指定标签属性值
  • th:href - 用于设定链接地址
  • th:src - 用于设定链接地址
  • th:text - 用于指定标签显示的文本内容

标准表达式:

  • 变量表达式- ${…}
  • 选择变量表达式 - *{…}
  • 消息表达式 - #{…}
  • 链接 URL 表达式 - @{…}
  • 片段表达式 - ~{…}
变量表达式 `${…}`

变量表达式 ${...} 主要用于获取上下文中的变量值,示例代码如下:

代码语言:javascript
复制
<p th:text="${title}">这是标题</p>

示例使用了 Thymeleaf 模板的变量表达式 ${...} 用来动态获取 P 标签中的内容,如果当前程序没有启动或者当前上下文中不存在 title 变量,该片段会显示标签默认值“这是标题”;如果当前上下文中存在 title 变量并且程序已经启动,当前 P 标签中的默认文本内容将会被 title 变量的值所替换,从而达到模板引擎页面数据动态替换的效果。

同时,Thymeleaf 为变量所在域提供了一些内置对象,具体如下所示:

代码语言:javascript
复制
#ctx:上下文对象
#vars:上下文变量
#locale:上下文区域设置
#request:(仅限 Web Context)HttpServletRequest 对象
#response:(仅限 Web Context)HttpServletResponse 对象
#session:(仅限 Web Context)HttpSession 对象
#servletContext:(仅限 Web Context)ServletContext 对象

结合上述内置对象的说明,假设要在 Thymeleaf 模板引擎页面中动态获取当前国家信息,可以使用 #locale 内置对象,示例代码如下:

代码语言:javascript
复制
The locale country is: <span th:text="${#locale.country}">China</span>

上述代码中,使用 th:text="${#locale.country}" 动态获取当前用户所在国家信息,其中标签内默认内容为 China,程序启动后通过浏览器查看当前页面时,Thymeleaf 会通过浏览器语言设置来识别当前用户所在国家信息,从而实现动态替换。

选择变量表达式 `*{…}`

选择变量表达式和变量表达式用法类似,一般用于从被选定对象而不是上下文中获取属性值,如果没有选定对象,则和变量表达式一样,示例代码如下:

代码语言:javascript
复制
<div th:object="${book}">
    <p>titile: <span th:text="*{title}">标题</span>.</p>
</div>

*{title} 选择变量表达式获取当前指定对象 book 的 title 属性值。

消息表达式 `#{…}`

消息表达式 #{...} 主要用于 Thymeleaf 模板页面国际化内容的动态替换和展示,使用消息表达式 #{...} 进行国际化设置时,还需要提供一些国际化配置文件。

链接表达式 `@{…}`

链接表达式 @{...} 一般用于页面跳转或者资源的引入,在 Web 开发中占据着非常重要的地位,并且使用也非常频繁,示例代码如下:

代码语言:javascript
复制
<a th:href="@{http://localhost:8080/order/details(orderId=${o.id})}">view</a> 
<a th:href="@{/order/details(orderId=${o.id},pid=${p.id})}">view</a>

上述代码中,链接表达式 @{...} 分别编写了绝对链接地址和相对链接地址。在有参表达式中,需要按照 @{路径(参数名称=参数值,参数名称=参数值...)} 的形式编写,同时该参数的值可以使用变量表达式来传递动态参数值。

片段表达式 `~{…}`

片段表达式 ~{...} 用来标记一个片段模板,并根据需要移动或传递给其他模板。其中,最常见的用法是使用 th:insertth:replace 属性插入片段,示例代码如下:

代码语言:javascript
复制
<div th:insert="~{thymeleafDemo::title}"></div>

上述代码中,使用 th:insert 属性将 title 片段模板引用到该标签中。thymeleafDemo 为模板名称,Thymeleaf 会自动查找 /resources/templates/ 目录下的 thymeleafDemo 模板,title 为片段名称。

基本使用

1) Thymeleaf 模板基本配置

首先在 Springbootdemo2 项目中使用 Thymeleaf 模板,首先必须保证引入 Thymeleaf 依赖,示例代码如下:

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

其次,在全局配置文件中配置 Thymeleaf 模板的一些参数。一般 Web 项目都会使用下列配置,示例代码如:

代码语言:javascript
复制
spring:
  thymeleaf:
    # 在开发阶段,为了验证建议关闭缓存
    cache: true
    # 模板编码
    encoding: UTF-8
    # 应用于模板的模板模式
    mode: HTML5
    # 指定模板页面存放路径
    prefix: classpath:/templates/
    # 指定模板页面名称的后缀
    suffix: .html

上述配置中,spring.thymeleaf.cache 表示是否开启 Thymeleaf 模板缓存,默认为 true,在开发过程中通常会关闭缓存,保证项目调试过程中数据能够及时响应;spring.thymeleaf.prefix 指定了 Thymeleaf 模板页面的存放路径,默认为classpath:/templates/spring.thymeleaf.suffix 指定了 Thymeleaf 模板页面的名称后缀,默认为 .html

2)静态资源的访问

开发 Web 应用时,难免需要使用静态资源。Spring boot 默认设置了静态资源的访问路径。

使用 Spring Initializr 方式创建的 Spring Boot 项目,默认生成了一个 resources 目录,在 resources 目录中的 public、resources、static 三个子目录下,Spring boot 默认会挨个从public、resources、static 里面查找静态资源。

完成数据的页面展示

1)创建 Spring Boot 项目,引入 Thymeleaf 依赖。

2)编写配置文件。

编辑 application.yml 全局配置文件,在该文件中对 Thymeleaf 模板页面的数据缓存进行设置。

代码语言:javascript
复制
# thymeleaf 页面缓存设置(默认为 true),开发中方便调试应设置为 false,上线稳定后应保持默认 true
spring:
  thymeleaf:
    cache: false

使用 spring.thymeleaf.cache=false 将 Thymeleaf 默认开启的缓存设置为了 false,用来关闭模板页面缓存。

3)创建 web 控制类

在项目中创建名为 com.renda.controller 的包,并在该包下创建一个用于前端模板页面动态数据替换效果测试的访问实体类 LoginController

代码语言:javascript
复制
@Controller
public class LoginController {

    @RequestMapping("/toLogin")
    public String toLoginView(Model model){
        model.addAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
        return "login"; // resources/templates/login.html
    }

}

toLoginView() 方法用于向登录页面 login.html 跳转,同时携带了当前年份信息 currentYear

4)创建模板页面并引入静态资源文件。

classpath:/templates/ 目录下引入一个用户登录的模板页面 login.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1,shrink-to-fit=no">
    <title>用户登录界面</title>
    <link th:href="@{../static/css/bootstrap.min.css}" rel="stylesheet">
    <link th:href="@{../static/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<!--  用户登录 form 表单 -->
<form class="form-signin">
    <img class="mb-4" th:src="@{../static/img/login.png}" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal">请登录</h1>
    <input type="text" class="form-control"
           th:placeholder="用户名" required="" autofocus="">
    <input type="password" class="form-control"
           th:placeholder="密码" required="">
    <div class="checkbox mb-3">
        <label>
            <input type="checkbox" value="remember-me"> 记住我
        </label>
    </div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button>
    <p class="mt-5 mb-3 text-muted">© <span th:text="${currentYear}">2019</span>-<span th:text="${currentYear}+1">2020</span></p>
</form>
</body>
</html>

通过 xmlns:th="http://www.thymeleaf.org 引入了 Thymeleaf 模板标签;

使用 th:hrefth:src 分别引入了两个外联的样式文件和一个图片;

使用 th:text 引入了后台动态传递过来的当前年份 currentYear

5)效果测试

可以看出,登录页面 login.html 显示正常,在页面底部动态显示了当前日期 2020-2021,而不是文件中的静态数字 2019-2020。这进一步说明了 Spring Boot 与 Thymeleaf 整合成功,完成了静态资源的引入和动态数据的显示。

SpringBoot 实战演练

实战技能补充:lombok

代码语言:javascript
复制
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <!-- 编译期间生效 -->
    <scope>provided</scope>
</dependency>

需求:实现用户的 CRUD 功能

初始化数据库信息:

代码语言:javascript
复制
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`
(
    id int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
    username varchar(100) DEFAULT NULL COMMENT '用户名',
    password varchar(100) DEFAULT NULL COMMENT '密码',
    birthday varchar(100) DEFAULT NULL COMMENT '生日',
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES (1, 'zhangsan', '123', '2020-10-1');
INSERT INTO `user` VALUES (2, 'lisi', '123', '2020-10-2');
INSERT INTO `user` VALUES (3, 'wangwu', '123', '2020-10-10');
INSERT INTO `user` VALUES (4, 'yuanjing', '123', '2020-10-11');
1)创建 springboot 工程

使用 Spring Initializr 新建一个工程 springbootuser,选择依赖:Developer Tools -> Lombok,Web -> Spring Web,SQL -> [MyBatis Framework、MySQL Driver]。

2)编辑 pom.xml
代码语言:javascript
复制
<!-- 引入阿里巴巴数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.3</version>
</dependency>
3)User 实体类编写

使用 FreeMyBatis 生成实体类。

使用 FreeMyBatis 生成 UserMapper 相关的代码。

com.renda.pojo.User

代码语言:javascript
复制
@Data // Lombok 自动生成 getter 和 setter
public class User implements Serializable {
    /**
     * 用户id
     */
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 生日
     */
    private String birthday;

    private static final long serialVersionUID = 1L;
}
4)UserMapper 编写及 xml 文件

com.renda.mapper.UserMapper

代码语言:javascript
复制
public interface UserMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(User record);

    int insertSelective(User record);

    User selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);
}

src\main\resources\mapper\UserMapper.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.renda.mapper.UserMapper">
  <resultMap id="BaseResultMap" type="com.renda.pojo.User">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="birthday" jdbcType="VARCHAR" property="birthday" />
  </resultMap>
  <sql id="Base_Column_List">
    id, username, `password`, birthday
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from user
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from user
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.User" useGeneratedKeys="true">
    insert into user (username, `password`, birthday
      )
    values (#{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{birthday,jdbcType=VARCHAR}
      )
  </insert>
  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.renda.pojo.User" useGeneratedKeys="true">
    insert into user
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="username != null">
        username,
      </if>
      <if test="password != null">
        `password`,
      </if>
      <if test="birthday != null">
        birthday,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="username != null">
        #{username,jdbcType=VARCHAR},
      </if>
      <if test="password != null">
        #{password,jdbcType=VARCHAR},
      </if>
      <if test="birthday != null">
        #{birthday,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.renda.pojo.User">
    update user
    <set>
      <if test="username != null">
        username = #{username,jdbcType=VARCHAR},
      </if>
      <if test="password != null">
        `password` = #{password,jdbcType=VARCHAR},
      </if>
      <if test="birthday != null">
        birthday = #{birthday,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.renda.pojo.User">
    update user
    set username = #{username,jdbcType=VARCHAR},
      `password` = #{password,jdbcType=VARCHAR},
      birthday = #{birthday,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>
5)UserService 接口及实现类编写

com.renda.service.UserService

代码语言:javascript
复制
public interface UserService {

    /**
     * 查询所有
     */
    List<User> queryAll();

    /**
     * 通过 ID 查询
     */
    User findById(Integer id);

    /**
     * 新增
     */
    void insert(User user);

    /**
     * 通过 ID 删除
     */
    void deleteById(Integer id);

    /**
     * 修改
     */
    void update(User user);

}

com.renda.service.impl.UserServiceImpl

代码语言:javascript
复制
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> queryAll() {
        return userMapper.queryAll();
    }

    @Override
    public User findById(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }

    @Override
    public void insert(User user) {
        // 将除 id 外所有的列都拼接入 SQL 语句
        // userMapper.insert(user);
        // 只将不为空的列才拼接入 SQL 语句(优先使用,减少高并发下数据传输)
        userMapper.insertSelective(user);
    }

    @Override
    public void deleteById(Integer id) {
        userMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void update(User user) {
        userMapper.updateByPrimaryKeySelective(user);
    }

}
6)UserController 编写

com.renda.controller.UserController

代码语言:javascript
复制
/**
 * restful 格式进行访问
 * 查询:GET
 * 新增: POST
 * 更新:PUT
 * 删除: DELETE
 *
 * @author Renda Zhang
 * @since 2020-10-31 1:36
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 查询所有
     */
    @GetMapping("/query")
    public List<User> queryAll(){
        return userService.queryAll();
    }

    /**
     * 通过 ID 查询
     */
    @GetMapping("/query/{id}")
    public User queryById(@PathVariable Integer id){
        return userService.findById(id);
    }

    /**
     * 删除
     */
    @DeleteMapping("/delete/{id}")
    public String delete(@PathVariable Integer id){
        userService.deleteById(id);
        return "删除成功";
    }

    /**
     * 新增
     */
    @PostMapping("/insert")
    public String insert(User user){
        userService.insert(user);
        return "新增成功";
    }

    /**
     * 修改
     */
    @PutMapping("/update")
    public String update(User user){
        userService.update(user);
        return "修改成功";
    }

}
7)全局配置文件 application.yml

重命名 application.propertiesapplication.yml

src\main\resources\application.yml

代码语言:javascript
复制
# 服务器配置
server:
  port: 8090

spring:
  # 数据源配置
  datasource:
    name: druid
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3306/springbootdata?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: password

# 整合 MyBatis
mybatis:
  # 声明 MyBatis 文件所在的位置
  mapper-locations: classpath:mapper/*Mapper.xml
8)启动类

com.renda.SpringbootuserApplication

代码语言:javascript
复制
@SpringBootApplication
// 使用的 Mybatis, 扫描 com.renda.mapper
@MapperScan("com.renda.mapper")
public class SpringbootuserApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootuserApplication.class, args);
    }

}
10)使用 Postman 测试
  • GET http://localhost:8090/user/query
  • GET http://localhost:8090/user/query/1
  • POST http://localhost:8090/user/insert?username=renda&password=123456&birthday=1995-12-27
  • PUT http://localhost:8090/user/update?username=RendaZhang&password=00000&birthday=1997-12-27&id=5
  • DELETE http://localhost:8090/user/delete/5

Spring Boot 项目部署

需求:将 Spring Boot 项目使用 maven 指令打成 jar 包并运行测试。

分析

1)添加打包组件将项目中的资源、配置、依赖包打到一个 jar 包中;可以使用 maven 的 package 命令。

2)部署:java -jar 包名

步骤实现

确保 pom.xml 文件中有如下的打包组件:

代码语言:javascript
复制
<build>
    <plugins>
        <!-- 打 jar 包时如果不配置该插件,打出来的 jar 包没有清单文件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

部署运行:

代码语言:javascript
复制
java -jar springbootuser-0.0.1-SNAPSHOT.jar
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Renda 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot 基本应用
    • 约定优于配置
      • SpringBoot 概念
        • Spring 优缺点分析
        • Spring Boot 解决上述 Spring 的问题
      • SpringBoot 入门案例
        • 1) 依赖管理
        • 2) 启动类
        • 3) Controller
      • SpringBoot 快速构建
        • 1)使用 Spring Initializr 方式构建 Spring Boot 项目
        • 2)创建一个用于 Web 访问的 Controller
        • 3) 运行项目
      • 单元测试与热部署
        • 单元测试
        • 热部署
      • 全局配置文件
        • `application.properties` 配置文件
        • `application.yaml` 配置文件
      • 配置文件属性值的注入
        • 使用 `@ConfigurationProperties` 注入属性
        • 使用 `@Value` 注入属性
      • 自定义配置
        • 使用 `@PropertySource` 加载配置文件
        • 使用 `@Configuration` 编写自定义配置类
    • SpringBoot 原理深入及源码剖析
      • 依赖管理
        • spring-boot-starter-parent 依赖
        • spring-boot-starter-web 依赖
      • 自动配置
        • @SpringBootConfiguration 注解
        • `@EnableAutoConfiguration` 注解
    • SpringBoot 数据访问
      • Spring Boot 整合 MyBatis
        • 基础环境搭建
        • 注解方式整合 Mybatis
        • 配置文件的方式整合 MyBatis
      • Spring Boot 整合 Redis
        • 1)添加Redis依赖包
        • 2)配置 Redis 数据库连接
        • 3)编写 Redis 操作工具类
        • 4)测试
    • SpringBoot 视图技术
      • 支持的视图技术
        • Thymeleaf
          • Thymeleaf 语法
          • 基本使用
          • 完成数据的页面展示
      • SpringBoot 实战演练
        • 1)创建 springboot 工程
          • 2)编辑 pom.xml
            • 3)User 实体类编写
              • 4)UserMapper 编写及 xml 文件
                • 5)UserService 接口及实现类编写
                  • 6)UserController 编写
                    • 7)全局配置文件 application.yml
                      • 8)启动类
                        • 10)使用 Postman 测试
                        • Spring Boot 项目部署
                          • 分析
                            • 步骤实现
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档