SpringBoot
SpringBoot基础回顾
约定优于配置
Build Anything with Spring Boot:Spring Boot is the starting point for
building all Spring-based applications. Spring Boot is designed to get you up
and running as quickly as possible, with minimal upfront configuration of
Spring.
上面是引自官网的一段话,大概是说: Spr ing Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。
SpringBoot概念
-
优缺点分析
优点: spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的 Enterprise JavaBean(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实用,但与此同时它要求 的回报也不少。 除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标, 而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题 就会严重阻碍项目的开发进度
SpringBoot解决上述spring问题,采用基于约定优于配置的思想,可以让开发人员不必再配置与逻辑业务之间进行思维切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期
-
起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依 赖,这些东西加在一起即支持某项功能。 简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
-
自动配置
springboot的自动配置,指的是springboot,会自动将一些配置类的bean注册进ioc容器,我们可以需 要的地方使用@autowired或者@resource等注解来使用它。 “自动”的表现形式就是我们只需要引我们想用功能的包,相关的配置我们完全不用管,springboot会自 动注入这些配置bean,我们直接使用这些bean即可 springboot: 简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效 率
SpringBoot案例实现
单元测试与热部署
(1)单元测试
开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。Spring Boot对项目的单元测试提供了很好的支持,在使用时,需要提前在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,可以通过相关注解实现单元测试
演示:
1.添加spring-boot-starter-test测试依赖启动器
在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,示例代码如下 :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.编写单元测试类和测试方法 使用Spring Initializr方式搭建的Spring Boot项目,会在src.test.java测试目录下自动创建与项目主程序 启动类对应的单元测试类
@RunWith(SpringRunner.class) // 测试启动器,并加载Spring Boot测试注解
@SpringBootTest // 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
class SpringbootDemoApplicationTests {
@Autowired
private DemoController demoController;
// 自动创建的单元测试方法实例
@Test
void contextLoads() {
String demo = demoController.demo();
System.out.println(demo);
}
}
(2)热部署
在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,SpringBoot框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目。
演示: 1.添加spring-boot-devtools热部署依赖启动器 在Spring Boot项目进行热部署测试之前,需要先在项目的pom.xml文件中添加spring-boot-devtools热 部署依赖启动器:
<!-- 引入热部署依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
由于使用的是IDEA开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对IDEA开发工具进行热部署相关的功能设置
-
IDEA工具热部署设置
选择IDEA工具界面的【File】->【Settings】选项,打开Compiler面板设置页面
选择Build下的Compiler选项,在右侧勾选“Build project automatically”选项将项目设置为自动编 译,单击【Apply】→【OK】按钮保存设置
在项目任意页面中使用组合快捷键“Ctrl+Shift+Alt+/”打开Maintenance选项框,选中并打开 Registry页面,具体如图所示
列表中找到“compiler.automake.allow.when.app.running”,将该选项后的Value值勾选,用于指 定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置。可以直接键盘输入搜索。
全局配置文件
全局配置文件能够对一些默认配置值进行修改。Spring Boot使用一个application.properties或者 application.yaml的文件作为全局配置文件,该文件存放在src/main/resource目录或者类路径 的/config,一般会选择resource目录。
application.properties配置文件
使用Spring Initializr方式构建Spring Boot项目时,会在resource目录下自动生成一个空的 application.properties文件,Spring Boot项目启动时会自动加载application.properties文件。 我们可以在application.properties文件中定义Spring Boot项目的相关属性,当然,这些相关属性可以 是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置
server.port=8081
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.config.additional-location=
spring.config.location=
spring.config.name=application
application.yaml配置文件
YAML文件格式是Spring Boot支持的一种JSON超集文件格式,相较于传统的Properties配置文件,YAML文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。application.yaml配置文件的工作原理和application.properties是一样的,只不过yaml格式配置文件看起来更简洁一些。YAML文件的扩展名可以使用.yml或者.yaml。application.yml文件使用 “key:(空格)value”格式配置属性,使用缩进控制层级关系。这里,针对不同数据类型的属性值,介绍一下YAML (1)value值为普通数据类型(例如数字、字符串、布尔等) 当YAML配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值,同时对于字符 串类型的属性值,不需要额外添加引号,示例代码如下
server:
port: 8081
path: /hello
(2)value值为数组和单列集合 当YAML配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法。其中,缩进式写法还有两种表示形式,示例代码如下
person:
hobby:
- play
- read
- sleep
#或者如下
person:
hobby:
play,
read,
sleep
上述代码中,在YAML配置文件中通过两种缩进式写法对person对象的单列集合(或数组)类型的爱好 hobby赋值为play、read和sleep。其中一种形式为“-(空格)属性值”,另一种形式为多个属性值之前 加英文逗号分隔(注意,最后一个属性值后不要加逗号)。
person:
hobby: [play,read,sleep]
通过上述示例对比发现,YAML配置文件的行内式写法更加简明、方便。另外,包含属性值的中括 号“[]”还可以进一步省略,在进行属性赋值时,程序会自动匹配和校对。
(3)value值为Map集合和对象 当YAML配置文件中配置的属性值为Map集合或对象类型时,YAML配置文件格式同样可以分为两种书写 方式:缩进式写法和行内式写法。 其中,缩进式写法的示例代码如下:
person:
map:
k1: v1
k2: v2
对应的行内式写法示例代码如下
person:
map: {k1: v1,k2: v2}
在YAML配置文件中,配置的属性值为Map集合或对象类型时,缩进式写法的形式按照YAML文件格式编 写即可,而行内式写法的属性值要用大括号“{}”包含。
需要说明的是,application.properties配置文件会覆盖application.yaml配置文件
配置文件属性值的注入
使用Spring Boot全局配置文件设置属性时: 如果配置属性是Spring Boot已有属性,例如服务端口server.port,那么Spring Boot内部会自动扫描并 读取这些配置文件中的属性值并覆盖默认属性。 如果配置的属性是用户自定义属性,例如刚刚自定义的Person实体类属性,还必须在程序中注入这些配 置属性方可生效。 Spring Boot支持多种注入配置文件属性的方式,下面来介绍如何使用注解@ConfigurationProperties 和@Value注入属性
-
@ConfigurationProperties
Spring Boot提供的@ConfigurationProperties注解用来快速、方便地将配置文件中的自定义属性值批 量注入到某个Bean对象的多个对应属性中。假设现在有一个配置文件,如果使用 @ConfigurationProperties注入配置文件的属性,示例代码如下:
@Component @ConfigurationProperties(prefix = "person") public class Person { private int id; // 属性的setXX()方法 public void setId(int id) { this.id = id; } }上述代码使用@Component和@ConfigurationProperties(prefix = “person”)将配置文件中的每个属性 映射到person类组件中。
@ConfigurationProperties(prefix = "person")注解的作用是将配置文件中以person开头的属性值通过 setXX()方法注入到实体类对应属性中 @Component注解的作用是将当前注入属性值的Person类对象作为Bean组件放到Spring容器中,只有 这样才能被@ConfigurationProperties注解进行赋值
编写application.properties配置文件时,由于要配置的Person对象属性是我们自定义的,Spring Boot无法自动识别,所以不会有任何书写提示。在实际开发中,为了出现代码提示的效果来方便配置, 在使用@ConfigurationProperties注解进行配置文件属性值注入时,可以在pom.xml文件中添加一个 Spring Boot提供的配置处理器依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>在pom.xml中添加上述配置依赖后,还需要重新运行项目启动类或者使用“Ctrl+F9”快捷键(即 Build Project)重构当前Spring Boot项目方可生效
-
@Value
@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性 中,Spring Boot框架从Spring框架中对@Value注解进行了默认继承,所以在Spring Boot框架中还可 以使用该注解读取和注入配置文件属性值。使用@Value注入属性的示例代码如下
@Component public class Person { @Value("${person.id}") private int id; } 上述代码中,使用@Component和@Value注入Person实体类的id属性。其中,@Value不仅可以 将配置文件的属性注入Person的id属性,还可以直接给id属性赋值,这点是@ConfigurationProperties 不支持的 。@Value注解对于包含Map集合、对象以及YAML文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误
自定义配置文件
spring Boot免除了项目中大部分的手动配置,对于一些特定情况,我们可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在application.peroperties文件中,SpringBoot会自动加载全局配置文件从而免除我们手动加载的烦恼。但是,如果我们自定义配置文件,SpringBoot是无法识别这些配置文件的,此时就需要我们手动加载。
-
@PropertySource
对于这种加载自定义配置文件的需求,可以使用@PropertySource注解结合@Configuration注解 配置类的方式来实现。@PropertySource注解用于指定自定义配置文件的具体位置和名称。同时,为了 保证Spring Boot能够扫描该注解,还需要类上添加@Configuration注解将实体类作为自定义配置类。 当然,如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用 @ConfigurationProperties或者@Value注解进行属性值注入
@Configuration // 自定义配置类 @PropertySource("classpath:test.properties") // 指定自定义配置文件位置和名称 @EnableConfigurationProperties(MyProperties.class) // 开启对应配置类的属性注入功能 @ConfigurationProperties(prefix = "test") // 指定配置文件注入属性前缀 public class MyProperties { private int id; private String name; // 省略属性getXX()和setXX()方法 // 省略toString()方法 }主要是一个自定义配置类,通过相关注解引入了自定义的配置文件,并完成了自定义属性值的注 入。针对示例中的几个注解,具体说明如下 @Configuration注解表示当前类是一个自定义配置类,并添加为Spring容器的组件,这里也可以 使用传统的@Component注解; @PropertySource("classpath:test.properties")注解指定了自定义配置文件的位置和名称,此示例 表示自定义配置文件为classpath类路径下的test.properties文件; @ConfigurationProperties(prefix = "test")注解将上述自定义配置文件test.properties中以test开 头的属性值注入到该配置类属性中。 如果配置类上使用的是@Component注解而非@Configuration注解,那么 @EnableConfigurationProperties注解还可以省略
-
使用@Configuration编写自定义配置类
在Spring Boot框架中,推荐使用配置类的方式向容器中添加和配置组件 在Spring Boot框架中,通常使用@Configuration注解定义一个配置类,Spring Boot会自动扫描和识别 配置类,从而替换传统Spring框架中的XML配置文件。 当定义一个配置类后,还需要在类中的方法上使用@Bean注解进行组件配置,将方法的返回对象注入到 Spring容器中,并且组件名称默认使用的是方法名,当然也可以使用@Bean注解的name或value属性 自定义组件的名称。
@Configuration // 定义该类是一个配置类 public class MyConfig { @Bean // 将返回值对象作为组件添加到Spring容器中,该组件id默认为方法名 public MyService myService(){ return new MyService(); } } -
随机数设置及参数间接引用
在Spring Boot配置文件中设置属性时,除了可以像前面示例中显示的配置属性值外,还可以使用 随机值和参数间引用对属性值进行设置。
随机值设置
在Spring Boot配置文件中,随机值设置使用到了Spring Boot内嵌的RandomValuePropertySource类,对一些隐秘属性值或者测试用例属性值进行随机值注入。
随机值设置的语法格式为${random.xx},xx表示需要指定生成的随机数类型和范围,它可以生成 随机的整数、uuid或字符串,示例代码如下 :
my.secret=${random.value} // 配置随机值 my.number=${random.int} // 配置随机整数 my.bignumber=${random.long} // 配置随机long类型数 my.uuid=${random.uuid} // 配置随机uuid类型数 my.number.less.than.ten=${random.int(10)} // 配置小于10的随机整数 my.number.in.range=${random.int[1024,65536]} // 配置范围在[1024,65536]之间的随机整数参数间引用
在Spring Boot配置文件中,配置文件的属性值还可以进行参数间的引用,也就是在后一个配置的 属性值中直接引用先前已经定义过的属性,这样可以直接解析其中的属性值了。 使用参数间引用的好处就是,在多个具有相互关联的配置属性中,只需要对其中一处属性预先配置,其 他地方都可以引用,省去了后续多处修改的麻烦 参数间引用的语法格式为${xx},xx表示先前在配置文件中已经配置过的属性名,示例代码如下
app.name=MyApp app.description=${app.name} is a Spring Boot application
SpringBoot原理深入及源码剖析
传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件 等,相较而言,Spring Boot显得更加方便、快捷和高效。那么,Spring Boot究竟如何做到这些的呢? 接下来分别针对Spring Boot框架的依赖管理、自动配置和执行流程进行深入分析
依赖管理
问题:(1)为什么导入dependency时不需要指定版本?
在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-starterparent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:
1.spring-boot-starter-parent依赖 在项目中的pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下:
<!-- Spring Boot父项目依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent<11./artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-bootstarter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下
<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底层源文件,核心代码具体如下:
<properties>
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.10.1</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.13.2</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.1</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.4</byte-buddy.version>
...
</properties>
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件
进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.2版本相匹配的 版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。 需要说明的是,如果pom.xml引入的依赖文件不是 spring-boot-starter-parent管理的,那么在 pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号。
(2)问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运 行依赖的JAR包是从何而来的?
-
spring-boot-starter-web依赖
查看spring-boot-starter-web依赖文件源码,核心代码具体如下
<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除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖, 我们可以打开Spring Boot官方文档,搜索“Starters”关键字查询场景依赖启动器:
列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时 只需要在pox.xml文件中导入对应的依赖启动器即可。 需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据 库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为 了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、 Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方 的依赖启动器时,切记要配置对应的版本号。注意命名方式区别是否为SpringBoot官方提供的Starter。
自动配置
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需 要少量配置就能运行编写的项目。
问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置? Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法, @SpringBootApplication能够扫描Spring组件并自动配置Spring Boot
查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) // 包扫描器
public @interface SpringBootApplication {
...
}
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据 信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、 @ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:
1.@SpringBootConfiguration注解
@SpringBootConfiguration注解表示Spring Boot配置类。查看@SpringBootConfiguration注解源
码,核心代码具体如下。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
...
}
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该 注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件 扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标 识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重 新封装命名而已。
2.@EnableAutoConfiguration注解
@EnableAutoConfiguration注解表示开启自动配置功能,该注解是Spring Boot框架最重要的注 解,也是实现自动化配置的注解。同样,查看该注解内部查看源码信息,核心代码具体如下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包
@Import(AutoConfigurationImportSelector.class) //自动配置类扫描导入
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
可以发现它是一个组合注解,Spring 中有很多以Enable开头的注解,其作用就是借助@Import来 收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfiguration就是借助@Import 来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
下面,对这两个核心注解分别讲解 : (1)@AutoConfigurationPackage注解 查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class) // 导入Registrar中注册的组件
public @interface AutoConfigurationPackage {
}
从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是 spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器 中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现 :
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
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));
}
}
从源码注释可以看到这个方法是为了存储导入配置中的基本包。new PackageImport(metadata).getPackageName(),解析包名为主程序所在包。@AutoConfigurationPackage注解的主要作用就是**添加该注解的类所在的package** 作为 **自动配置package** 进行管理。Spirng会将主程序类所在包及所有子包下的组件到扫描到spring容器中。
因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫 描 。
(2)@Import({AutoConfigurationImportSelector.class}):
将AutoConfigurationImportSelector这个类导入到spring容器中,AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这 个方法告诉springboot都需要导入那些组件:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
// 自动配置的类全名.条件=值
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
深入研究loadMetadata方法
final class AutoConfigurationMetadataLoader {
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";//文件中为需要加载的配置类的类路径的条件
private AutoConfigurationMetadataLoader() {
}
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
//重载方法
return loadMetadata(classLoader, PATH);
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
//1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
// 获得 PATH 对应的 URL 们
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
// 遍历 URL 数组,读取到 properties 中
Properties properties = new Properties();
//2.解析urls枚举对象中的信息封装成properties对象并加载
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象
//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
return loadMetadata(properties);
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
}
static AutoConfigurationMetadata loadMetadata(Properties properties) {
return new PropertiesAutoConfigurationMetadata(properties);
}
深入研究getAutoConfigurationEntry方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 1. 判断是否开启注解。如未开启,返回空串
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. 获得注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
configurations = removeDuplicates(configurations);
// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
checkExcludedClasses(configurations, exclusions);
// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
configurations.removeAll(exclusions);
// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
//@ConditionalOnMissingClass : classpath中不存在该类时起效
//@ConditionalOnBean : DI容器中存在该类型Bean时起效
//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
//@ConditionalOnExpression : SpEL表达式结果为true时
//@ConditionalOnProperty : 参数设置或者值一致时起效
//@ConditionalOnResource : 指定的文件存在时起效
//@ConditionalOnJndi : 指定的JNDI存在时起效
//@ConditionalOnJava : 指定的Java版本存在时起效
//@ConditionalOnWebApplication : Web应用环境下起效
//@ConditionalOnNotWebApplication : 非Web应用环境下起效
//总结一下判断是否要加载某个类的两种方式:
//根据spring-autoconfigure-metadata.properties进行判断。
//要判断@Conditional是否满足
// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
configurations = filter(configurations, autoConfigurationMetadata);
// 6. 将自动配置导入事件通知监听器
//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
// 并触发fireAutoConfigurationImportEvents事件。
fireAutoConfigurationImportEvents(configurations, exclusions);
// 7. 创建 AutoConfigurationEntry 对象
return new AutoConfigurationEntry(configurations, exclusions);
}
继续进入AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 让SpringFactoryLoader去加载一些组件的名字 对应spring.factories中的类名
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;
}
SpringFactoriesLoader#loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
SpringFactoriesLoader#loadSpringFactories
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//加载资源
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cache.CachesEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cassandra.CassandraReactiveHealthIndicatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.servlet.CloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.cloudfoundry.reactive.ReactiveCloudFoundryActuatorAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.condition.ConditionsReportEndpointAutoConfiguration,\...
@EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中。 以web项目为例,在项目中加入了Web环境依赖启动器,对应的WebMvcAutoConfiguration自动配 置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对Spring MVC运行 所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC校验器等。而这些自动配置类 的本质是传统Spring MVC框架中对应的XML配置文件,只不过在Spring Boot中以自动配置类的形式进 行了预先配置。因此,在Spring Boot项目中加入相关依赖启动器后,基本上不需要任何配置就可以运 行程序,当然,我们也可以对这些自动配置类中默认的配置进行更改。
总结 因此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加载器进行一系列的容器创建过程
3.@EnableAutoConfiguration注解
@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决 定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项 目主程序启动类所在包的具体位置
@SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:
|- @SpringBootConfiguration
|- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
|- @Import(AutoConfigurationImportSelector.class) //到METAINF/spring.factories 中定义的bean添加到IOC容器中
|- @ComponentScan //包扫描
自定义Starter
SpringBoot starter机制
SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也 是因为starter。 starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用 某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过 classpath路径下的类发现需要的Bean,并织入相应的Bean。 例如,你想使用Reids插件,那么可以使用spring-boot-starter-redis;如果想使用MongoDB,可以使 用spring-boot-starter-data-mongodb
为什么要自定义starter
开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配。
自定义starter的命名规则
SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的starter使用 xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供的starter
整个过程分为两部分:
- 自定义starter
- 使用starter
首先,先完成自定义starter (1)新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
(2)编写javaBean
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
private int id;
private String name;
...
}
(3)编写配置类MyAutoConfiguration
@Configuration
@ConditionalOnClass //@ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置
public class MyAutoConfiguration {
static {
System.out.println("MyAutoConfiguration init....");
}
@Bean
public SimpleBean simpleBean(){
return new SimpleBean();
}
}
(4)resources下创建/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAutoConfiguration
使用自定义starter
(1)导入自定义starter的依赖
<dependency>
<groupId>com.lagou</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency
(2)在全局配置文件中配置属性值
simplebean.id=1
simplebean.name=自定义starter
(3)编写测试方法
//测试自定义starter
@Autowired
private SimpleBean simpleBean;
@Test
public void zdyStarterTest(){
System.out.println(simpleBean);
}
执行原理
每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。 问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢? 下面我们查看run()方法内部的源码,核心代码具体如下:
//调用静态类,参数对应的就是SpringbootDemoApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
/**
* 运行 Spring 应用
*
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load 加载的主类的数组
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link }
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//SpringApplication的启动由两部分组成:
//1. 实例化SpringApplication对象
//2. run(args):调用run方法
return new SpringApplication(primarySources).run(args);
}
从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,分别是 SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下
1.SpringApplication实例的初始化创建
查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 设置初始化器(Initializer),最后会调用这些初始化器
//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。 (1)this.webApplicationType = WebApplicationType.deduceFromClasspath() 用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传 统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用) (2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)) 用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器 SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中 获取所有可用的应用初始化器类ApplicationContextInitializer。 (3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)) 用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样, 也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的 spring.factores文件中获取所有可用的监听器类ApplicationListener。 (4)this.mainApplicationClass = this.deduceMainApplicationClass() 用于推断并设置项目main()方法启动的主程序启动类
2.项目的初始化启动
分析完(new SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例
对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
// 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 初始化应用上下文和异常报告集合
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 配置 headless 属性
configureHeadlessProperty();
// (1)获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 创建 ApplicationArguments 对象 初始化默认应用参数类
// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//(2)项目运行环境Environment的预配置
// 创建并配置当前SpringBoot应用将要使用的Environment
// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
Banner printedBanner = printBanner(environment);
// (3)创建Spring容器
context = createApplicationContext();
// 获得异常报告器 SpringBootExceptionReporter 数组
//这一步的逻辑和实例化初始化器和监听器的一样,
// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// (4)Spring容器前置处理
//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// (5):刷新容器
refreshContext(context);
// (6):Spring容器后置处理
//扩展接口,设计模式中的模板方法,默认为空实现。
// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
afterRefresh(context, applicationArguments);
// 停止 StopWatch 统计时长
stopWatch.stop();
// 打印 Spring Boot 启动的时长日志。
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// (7)发出结束执行的事件通知
listeners.started(context);
// (8):执行Runners
//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
// (9)发布应用上下文就绪事件
//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
// 这样整个Spring Boot项目就正式启动完成了。
try {
listeners.running(context);
} catch (Throwable ex) {
// 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
//返回容器
return context;
}
SpringBoot数据访问
整合MyBatis
MyBatis 是一款优秀的持久层框架,Spring Boot官方虽然没有对MyBatis进行整合,但是MyBatis团队自行适配了对应的启动器,进一步简化了使用MyBatis进行数据的操作 因为Spring Boot框架开发的便利性,所以实现Spring Boot与数据访问层框架(例如MyBatis)的 整合非常简单,主要是引入对应的依赖启动器,并进行数据库相关参数设置即可
引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
编写配置文件
# MySQL数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?
serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
注解方式整合Mybatis
@Mapper
public interface CommentMapper {
@Select("SELECT * FROM t_comment WHERE id =#{id}")
public Comment findById(Integer id);
}
使用配置文件的方式整合MyBatis
@Mapper
public interface ArticleMapper {
public Article selectArticle(Integer id);
}
<?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.lagou.mapper.ArticleMapper">
<select id="selectArticle" resultType="Article">
select * from Article
</select>
</mapper>
#配置MyBatis的xml配置文件路径
mybatis.mapper-locations=classpath:mapper/*.xml
#配置XML映射文件中指定的实体类别名路径
mybatis.type-aliases-package=com.lagou.pojo
整合JPA
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
编写实体类
@Entity(name = "t_comment") // 设置ORM实体类,并指定映射的表名
public class Comment {
@Id // 表明映射对应的主键id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 设置主键自增策略
private Integer id;
private String content;
private String author;
@Column(name = "a_id") //指定映射的表字段名
private Integer aId;
// 省略属性getXX()和setXX()方法
// 省略toString()方法
}
编写Repository接口
public interface CommentRepository extends JpaRepository<Comment,Integer> {
}
@Autowired
private CommentRepository repository;
@Test
public void selectComment() {
Optional<Comment> optional = repository.findById(1);
if(optional.isPresent()){
System.out.println(optional.get());
}
System.out.println();
}
整合Redis
除了对关系型数据库的整合支持外,Spring Boot对非关系型数据库也提供了非常好的支持。Spring Boot与非关系型数据库Redis的整合使用
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
编写实体类
@RedisHash("persons") // 指定操作实体类对象在Redis数据库中的存储空间
public class Person {
@Id // 标识实体类主键
private String id;
@Indexed // 标识对应属性在Redis数据库中生成二级索引
private String firstname;
@Indexed
private String lastname;
private Address address;
// 省略属性getXX()和setXX()方法
// 省略有参和无参构造方法
// 省略toString()方法
}
public class Address {
@Indexed
private String city;
@Indexed
private String country;
// 省略属性getXX()和setXX()方法
// 省略有参和无参构造方法
// 省略toString()方法
}
实体类示例中,针对面向Redis数据库的数据操作设置了几个主要注解,这几个注解的说明如下 :
- @RedisHash("persons"):用于指定操作实体类对象在Redis数据库中的存储空间,此处表示针对 Person实体类的数据操作都存储在Redis数据库中名为persons的存储空间下。
- @Id:用于标识实体类主键。在Redis数据库中会默认生成字符串形式的HashKey表示唯一的实体 对象id,当然也可以在数据存储时手动指定id。
- @Indexed:用于标识对应属性在Redis数据库中生成二级索引。使用该注解后会在Redis数据库中 生成属性对应的二级索引,索引名称就是属性名,可以方便的进行数据条件查询。
编写Repository接口
public interface PersonRepository extends CrudRepository<Person,String> {
List<Person> findByAddress_City(String 北京);
}
- 需要说明的是,在操作Redis数据库时编写的Repository接口文件需要继承最底层的CrudRepository接口,而不是继承JpaRepository,这是因为JpaRepository是Spring Boot整合JPA特有的。当然,也可以在项目pom.xml文件中同时导入Spring Boot整合的JPA依赖和Redis依赖,这样就可以编写一个继承JpaRepository的接口操作Redis数据库
Redis数据库连接配置
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
编写单元测试进行接口方法测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTests {
@Autowired
private PersonRepository repository;
@Test
public void savePerson() {
Person person = new Person();
person.setFirstname("张");
person.setLastname("三");
Address address = new Address();
address.setCity("北京");
address.setCountry("中国");
person.setAddress(address);
// 向Redis数据库添加数据
Person save = repository.save(person);
}
@Test
public void selectPerson() {
List<Person> list = (List<Person>) repository.findByAddress_City("北
京");
for (Person person : list) {
System.out.println(person);
}
}
}