本文较长,可以根据目录选择自己需要的部分进行阅读
目录
- 基础使用
- 源码分析
- 数据访问
- 视图技术
- 缓存管理
1. 基础使用
1.1 SpringBoot的优点
- 起步依赖
- 只需引入一个starter就包含了特定场景所需的依赖,如
spring-boot-starter-web
内包含了tomcat、spring-web、spring-webmvc - 父项目统一管理依赖版本,子项目引入时不需要关心版本号,通过依赖传递获取,
spring-boot-dependencies
内可以看见许多常见技术框架的依赖版本
- 只需引入一个starter就包含了特定场景所需的依赖,如
- 自动装配
- 添加依赖后,能自动配置组件的配置,使用时可以不配或者少量配置就能运行项目,如添加了
spring-boot-starter-web
之后,不需要配置视图解析器、前端控制器等
- 添加依赖后,能自动配置组件的配置,使用时可以不配或者少量配置就能运行项目,如添加了
1.2 单元测试
- SpringBoot 推荐使用 JUint5 进行测试,使用IDEA 创建项目时就剔除了对JUint3、4的依赖
<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>
- 非 web 下,单元测试只需要使用 @SpringBootTest 和 @Test 即可
1.3 配置文件
- 配置文件的加载顺序
- 在
spring-boot-starter-parent
可以查阅到加载顺序:application*.yml => application*.yaml => application*.propertis - 因此,如果出现相同属性,属性值会被后面加载的配置文件覆盖
- 在
- 配置文件格式
- propertie
# 简单数据类型直接赋值 person.id=1 person.name=1 # 数组或者集合用逗号分隔,前后的中括号会自动补全 person.hobby=吃饭,睡觉,打豆豆 person.family=father,mother # map形式 person.map.k1=v1 person.map.k2=v2 # 对象用 对象名称.属性名=xx person.pet.type=dog person.pet.name=旺财
- yaml
person: id: 2 name: 2 hobby: 吃饭,睡觉,打豆豆 family: father,mother map: {k1: v1,k2: v2} pet: type: dog name: 旺财
- propertie
- 配置文件属性值的注入方式
- @Comfiguration + @ConfigurationProperties(prefix = “xx”),会按照前缀注入,支持的类型多
- @Comfiguration + @Value("${xx.xx}"),只适合简单数据类型注入,优点是可以设置默认值,如 @Value("${xx.xx : 233}")
- tips:加入下面的依赖,编写配置文件时会有书写提示和跳转到配置类
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
- 自定义配置文件
- @Configuration // 表明为配置类
- @PropertySource(“classpath:test.properties”) // 指定自定义文件文件位置和名称
- @EnableConfigurationProperties(MyProperties.class) // 开启对应配置类的属性注入功能
- @ConfigurationProperties(prefix = “test”) // 按前缀注入
- 随机参数和参数间引用
# 表达式里的值都来源于RandomValuePropertySource my.secret=${random.value} 随机值 my.number=${random.int} // 随机整数 my.bignumber=${random.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]之间的随机整数 # 参数引用 my.money.copy=${my.money}
2. 源码分析
2.1 依赖管理
- 父项目管理依赖版本,子项目引入依赖时不需要指定版本
- 在子项目的 pom.xml 可以看到
parent
标签,里面是spring-boot-starter-parent
,底层还有一个父依赖spring-boot-dependencies
,里面的properties
属性管理了一些常用技术框架的依赖版本
- 在子项目的 pom.xml 可以看到
- 子项目引入父项目
spring-boot-starter-parent
之后,通过 maven的 依赖传递 方式引入依赖,不仅不需要关心版本号,也不需要关心依赖之间存在版本冲突的问题
2.2 自动配置
首先,启动类 @SpringBootApplication 包含了3个重要的注解
2.2.1 @SpringBootConfiguration
@SpringBootConfiguration 的内部有一个 @Configuration 注解,该注解由 Spring 提供,表明是一个配置类,代替 XML,可以被组件扫描器扫描
2.2.2 @EnableAutoConfiguration
表示开启自动配置功能,内部包含了 @AutoConfiguration 和 @Import 两个重要的注解
- @AutoConfigurationPackage 的作用是将主程序类所在的包及所有子包下的组件扫描到 Spring 容器,因此启动类的配置应该在项目做外层的根目录,@AutoConfigurationPackage 的内部还导入了一个组件类 Registrar ,该类做了以下几件事
- 1.读取启动类所在的包名
- 2.将AutoConfigurationPackages 注册 beanDefinitionMap中
- 1.读取启动类所在的包名
- @Import(AutoConfigurationImportSelector.class) 的作用是将 AutoConfigurationImportSelector 导入到 Spring 容器,该类可以将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建的 IoC 容器,具体步骤如下
- 1.selectImports 是告诉 SpringBoot 需要导入哪些组件的入口方法
- 2.loadMetadata 负责加载
META-INF/spring-autoconfigure-metadata.properties
,获取所有支持自动配置类的条件,内容格式为自动配置的类全名.条件=值
,当符合条件时就会加载该类,比如:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.ConditionalOnClass=Servlet,WebMvcConfigurer,DispatcherServlet,ConditionalOnClass 表示如果Servlet、WebMvcConfigurer、DispatcherServlet等类在当前classpatch路径下,那么就会加载 WebMvcAutoConfiguration,里面就有我们的视图解析器 - 3.getAutoConfigurationEntry 里面有个 getCandidateConfigurations 方法,通过调用工具类 SpringFactoriesLoader 的 loadFactoryNames 方法,加载
META-INF/spring.factories
,获取默认支持的自动配置类名列表(即key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的value),最后导入事件通知监听器,SpringBootApplication 调用 run 方法时就生效了
从
META-INF/spring.factories
截取的片段可以看到EnableAutoConfiguration
默认支持这些自动配置类
总的来说,SpringBoot 底层实现自动配置的步骤是:
- SpringBoot 应用启动
- @SpringBootApplication 起作用
- @EnableAutoConfiguration 包含两个核心注解
- @AutoConfigurationPackage 通过导入 Registrar 扫描主程序类所在的包及所有子包下的组件扫描到 Spring 容器
- @Import 导入 AutoConfigurationimportSelector ,将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建的 IoC 容器
2.2.3 @ComponentScan
根据 @AutoConfigurationPackage 获取的包路径从中找出标识了需要装配的类自动装配到Spring容器中,如常用的@Controller、@Service、@Repository
2.3 自定义starter
- 新建maven工程,根据命名规范,工程名为 zdy-spring-boot-starter,并引入依赖
spring-boot-autoconfigure
<groupId>org.example</groupId> <artifactId>zdy-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.2.2.RELEASE</version> </dependency> </dependencies>
- 编写配置类 SimpleBean,贴上注解 @EnableConfigurationProperties 和 @ConfigurationProperties
@EnableConfigurationProperties(SimpleBean.class) @ConfigurationProperties(prefix = "simpleBean") public class SimpleBean { private int id; private String name; // setter/getter/toString }
- 编写自动配置类 MyAutoConfiguration,贴上注解 @Configuration 和 @ConditionalOnClass
@Configuration @ConditionalOnClass // 当类路径classpath下有指定的类的情况下进行自动配置 public class MyAutoConfiguration { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } }
- 在 resources 下创建
META-INF/spring.factories
,内容为org.springframework.boot.autoconfigure.EnableAutoConfiguration=MyAutoConfiguration类的路径
- 在另一个工程引入
zdy-spring-boot-starter
<dependency> <groupId>org.example</groupId> <artifactId>zdy-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
- 在全局配置文件中配置属性值
simpleBean.id=666 simpleBean.name=自定义stater
- 编写测试方法
@SpringBootTest public class CustomTest { @Autowired private SimpleBean simpleBean; @Test public void zdy() { System.out.println(simpleBean); // 预期:SimpleBean{id=666, name='自定义stater'} } }
2.4 启动原理
SpringBootApplication.run 做了两件事:SpringBootApplication的实例化以及调用 run 方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
2.4.1 SpringBootApplication的实例化
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 1.把项目启动类的class对象设置属性
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2.推断并设置应用类型(查看 classpath 类路径下面是否存在某个特征类,是SERVLET应用还是REACTIVE应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3.设置初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 4.设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 5.推断并设置项目 main 方法的启动类(从堆栈中查找包含 main 方法的栈)
this.mainApplicationClass = deduceMainApplicationClass();
}
- getSpringFactoriesInstances
调用工具类 SpringFactoriesLoader 的 loadFactoryNames 方法,加载 META-INF/spring.factories,获取对应key的value
2.4.2 调用 run 方法
public ConfigurableApplicationContext run(String... args) {
...
// 1.获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2.根据 SpringApplicationRunListeners 和参数准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 3.创建Spring容器
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 4.Spring容器前置处理
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 5.刷新容器
refreshContext(context);
// 6.Spring容器后置处理
afterRefresh(context, applicationArguments);
...
// 7.发出结束执行的事件(后续的处理有自动配置)
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 8.执行Runners(自定义执行器)
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
最后一张流程图描述启动原理