Spring Boot 学习笔记
基础回顾
约定优于配置
框架应该提供合理的默认值
两大特性
Spring 的优点
- IOC
- AOP
Spring 的缺点
- 配置是重量级的
- 依赖管理耗时耗力
解决方案(两大特性)
- 起步依赖:依赖管理,解决 jar 包版本问题
- 自动配置:将配置 bean 注册进 IOC 容器
案例实现
使用 Spring Initializr (start.spring.io) 创建项目
引入 spring-boot-starter-web 依赖
单元测试与热部署
单元测试
引入 spring-boot-starter-test 依赖
package com.chen4393.demo.controller;
@RestController // @Controller + @ResponseBody
public class DemoController {
@RequestMapping("/demo")
public String demo() {
return "你好, Spring Boot!";
}
}
package com.chen4393.demo;
@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {
@Autowired
private DemoController demoController;
@Test
void contextLoads() {
String demo = demoController.demo();
Assertions.assertEquals("你好, Spring Boot!", demo);
}
}
热部署
- 引入 spring-boot-devtools 依赖
- IDEA 中配置:
- 【File】-【Settings】-【Compiler】,勾选【build project automatically】
- 【shift】+【Alt】+【Ctrl】+【/】打开【Registry】菜单,勾选【compiler.automake.allow.when.app.runing】
全局配置文件
Properties 配置文件
application.properties
person.id=1
person.name=tom
person.hobby=吃饭,睡觉,看书
person.family=father,mother
person.map.k1=v1
person.map.k2=v2
person.pet.type=dog
person.pet.name=旺财
Yaml 配置文件
扩展名为 .yaml 或 .yml
application.yaml
person:
id: 1
hobby: [吃饭,睡觉,看书]
name: tom
family: [father, mother]
map: {k1: v1, k2: v2}
pet: {type: dog, name: 旺财}
application.properties 配置文件会覆盖 application.yaml 配置文件
利用配置文件注入属性值
使用 @ConfigurationProperties 注入属性值
@Component
@ConfigurationProperties(prefix = "person") // 将配置文件中以person开头的属性赋值
public class Person {
private int id;
private String name;
private List<String> hobby;
private String[] family;
private Map<String, Object> map;
private Pet pet;
// getters and setters(required)
// toString()
}
使用 @Value 注入属性值
@Component
public class Student {
@Value("3")
private int id;
@Value("${person.name}")
private String name;
}
自定义配置类
使用 @PropertySource 加载自定义配置文件
test.properties
test.id=101
test.name=chen4393
MyProperties.java
@Component
@PropertySource("classpath:test.properties")
@ConfigurationProperties(prefix = "test")
public class MyProperties {
private int id;
private String name;
// getters and setters
// toString()
}
package com.chen4393.demo;
@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {
@Autowired
private MyProperties myProperties;
@Test
void testMyProperties() {
System.out.println(myProperties);
}
}
使用 @Configuration 编写自定义配置类
package com.chen4393.demo.config;
public class MyService {
}
package com.chen4393.demo.config;
@Configuration
public class MyConfig {
@Bean("iService")
public MyService myService() {
return new MyService();
}
}
package com.chen4393.demo;
@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
void testIOC() {
Assertions.assertTrue(applicationContext.containsBean("iService"));
}
}
随机数设置及参数间引用
随机数设置
test.properties
test.id=${random.int[1,100]}
test.name=chen4393
参数间引用
application.properties
tom.age=${random.int[10,20]}
tom.description=tom的年龄可能是${tom.age}
package com.chen4393.demo;
@SpringBootTest // 标记为单元测试类,加载项目的ApplicationContext上下文环境
class DemoApplicationTests {
@Value("${tom.description}")
private String description;
@Test
void placeHolderTest() {
System.out.println(description);
}
}
源码剖析
依赖管理
为什么不需要指定版本号?
从 pom.xml 文件出发,找到它的 parent 名为 spring-boot-starter-parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
...
</dependencies>
进入 spring-boot-starter-parent 文件,发现还有一个 parent 名为 spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
spring-boot-dependencies 文件定义了需要哪些依赖及其版本号,对依赖进行统一管理
<properties>
<activemq.version>5.15.12</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.80</appengine-sdk.version>
<artemis.version>2.12.0</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.16.1</assertj.version>
...
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-amqp</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-blueprint</artifactId>
<version>${activemq.version}</version>
</dependency>
...
</dependencies>
</dependencyManagement>
注意:如果 pom.xml 引入的依赖不是由 spring-boot-starter-parent 管理的,则需要指定版本号
项目运行依赖的 jar 包是在哪里来的?
进入 spring-boot-starter-web 依赖,发现其中包含 web 开发需要的所有底层依赖
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.1.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<scope>compile</scope>
</dependency>
...
如何查找依赖启动器?
打开依赖启动器的官方文档,可以查看所有 starters
第三方依赖启动器:在 Spring Boot 官方没有整合的情况下,像 Mybatis 和 Druid 这样的第三方团队主动与其进行整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter 和 druid-spring-boot-starter 等依赖启动器
不过,导入第三方依赖启动器需要配置版本号!
自动配置(启动流程)
@SpringBootApplication 入口注解
@SpringBootConfiguration 表明该类为配置类
@EnableAutoConfiguration 开启自动配置
@AutoConfigurationPackage 扫描入口注解的类所在的包及其子包并添加到容器中
@Import(AutoConfigurationImportSelector.class) 添加 spring.factories 定义的 bean 到容器
@ComponentScan 包扫描
从启动类出发,进入 @SpringBootApplication 的定义,发现核心就是 @SpringBootConfiguration 和 @EnableAutoConfiguration 这两个注解
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE) // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented // 表示注解可以记录在javadoc中
@Inherited // 表示可以被子类继承该注解
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = { // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
@SpringBootConfiguration
本质是配置类
package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //配置类
public @interface SpringBootConfiguration {
}
@EnableAutoConfiguration 开启自动配置
实现自动配置的核心注解
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 获取@springbootApplication标注类所在的包名,并且对该包及其子包进行扫描,将组件添加到容器中
@AutoConfigurationPackage
// 向容器添加一些由AutoConfigurationImportSelector指定的组件
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 排除某些配置类
Class<?>[] exclude() default {};
// 排除某些配置文件
String[] excludeName() default {};
}
@AutoConfigurationPackage 实现扫描主配置类所在包及其子包
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 向容器中添加一些由Registrar指定的组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
Registrar 类的作用:注册 bean (@SpringBootApplication 标注的主配置类所在的包及其子包下所有组件),通过 debug 可以看到 new PackageImport(metadata).getPackageName() 返回的是包名 com.chen4393.demo
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// 获取的是项目主程序启动类所在的目录
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// metadata: 注解标注的元数据信息
// 默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
register(registry, new PackageImport(metadata).getPackageName());
}
...
}
因此我们之前在 com.chen4393.demo 下定义项目主程序启动类,并在这个包下定义子包 controller
@Import(AutoConfigurationImportSelector.class) 实现将配置类信息交给加载器
@Import 的作用:将多个配置类导入到主配置类中,避免将所有配置都写到主配置类中。因此这句话的意思是向容器添加一些组件,添加哪些组件由 AutoConfigurationImportSelector 来指定
进入 AutoConfigurationImportSelector 的定义,找到方法 selectImports
package org.springframework.boot.autoconfigure;
public class AutoConfigurationImportSelector {
...
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取 AutoConfigurationEntry 对象,该对象保存配置信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
进入 getAutoConfigurationEntry 方法,该方法为实现自动配置的核心方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取待添加到容器的配置信息
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 排除某些不希望自动配置的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 筛选出最终符合当前项目运行环境对应的自动配置类
configurations = getConfigurationClassFilter().filter(configurations);
// 触发AutoConfigurationImportEvents事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
进入 getCandidateConfigurations 方法
... getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取需要加载的工厂类全名
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
...
}
进入 loadFactoryNames 方法
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
...
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
...
}
...
}
找到 spring-boot-autoconfigure-2.3.1.RELEASE.jar 下面的 META-INF/spring.factories 配置文件,其实每个 jar 下面都有一个这样的文件
...
# 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.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
...
底层实现自动配置的步骤总结
- 应用启动
- @SpringBootApplication 标明该类为配置类
- @EnableAutoConfiguration 开启自动配置
- @AutoConfigurationPackage - @Import(AutoConfigurationPackages.Registrar.class) 将 Registrar 类导入到容器,而 Registrar 类的作用是获取项目主程序启动类所在的目录,并将相应组件导入到容器;
- @Import(AutoConfigurationImportSelector.class) 将 AutoConfigurationImportSelector 类导入到容器,而 AutoConfigurationImportSelector 类的作用是通过 selectImports 方法使用内部工具类 SpringFactoriesLoader 查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程;
- @ComponentScan 扫描主配置类所在的包及其子包
自定义 Starter
新建一个和之前 demo 项目同级的 module 模块,选择普通 maven 工程,命名为 my-spring-boot-starter
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
新建一个实体类 SimpleBean
package com.chen4393.pojo;
@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
private int id;
private String name;
// getters and setters
// toString()
}
新建一个自定义的自动配置类 MyAutoConfiguration
package com.chen4393.config;
@Configuration
@ConditionalOnClass
public class MyAutoConfiguration {
static {
System.out.println("MyAutoConfiguration init...");
}
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
}
在 resources 下创建 META-INF/spring.factories 配置文件,配置自己定义的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.chen4393.config.MyAutoConfiguration
在 demo 工程下的 application.properties 配置文件下添加属性值
simplebean.id=1
simplebean.name=自定义starter
测试
@Autowired
private SimpleBean simpleBean;
@Test
public void myStarterTest() {
System.out.println(simpleBean);
}