SpringBoot 框架是为了能够帮助使用 Spring 框架的开发者快速高效的构建一个基于 Spirng 框架以及 Spring 生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。目的是简化配置文件,俗称“快速搭建”。
我们都知道的是,使用框架的目的是简化我们的开发,将一些公共组件进行封装得以复用。不论是 Spring 中的 Ioc、aop、MVC 还是 MyBatis 它们都使得我们的 CRUD 变得更加简单,但是要使用他们,需要导入相关的依赖,书写各自的配置文件并加载它们,依赖和依赖之间还不能有冲突,重构版本时要操作很多的配置文件或类,搭配之间的依赖关系,这种感觉就如同老坛酸菜面一样的酸爽。
那这个时候我们就在想,有没有一种方式可以把它们集中到一起进行管理?
答案是,必须有的,它就是
就如上边图片的显示一样,SpringBoot 就如同电脑上的开机键一样,只需要按上按钮,电脑就会自行启动。看,多么简单!
在本篇博客中我着重的从 SpringBoot 的运行机制、配置文件等方面来进行讲解。
@SpringBootApplication
public class GuanWeiApplication {
public static void main(String[] args) {
SpringApplication.run(GuanWeiApplication.class, args);
}
}
SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类,SpringBoot 就应该运行这个类的 main 方法来启动 SpringBoot 应用。
@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 {
...
}
@Target({ElementType.TYPE}) 注解
@Target 说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在注解类型的声明中使用了 target 可更加明晰其修饰的目标。作用:用于描述注解的使用范围,也可以理解成被描述的注解可以用在什么地方。
@Retention(RetentionPolicy.RUNTIME) 注解
注解 @Retention 可以用来修饰注解,是注解的注解,被称为元注解。它表明了注解的生命周期、
@Retention 注解有一个属性 value,它决定了 Retention 注解应该如何去保存注解生命周期。
RetentionPolicy有3个值:CLASS RUNTIME SOURCE。按生命周期来划分可分为3类:
@Documented 注解
@Documented 是元注解,可以修饰其他注解。如果一个注解 @GuanWei 被 @Documented 标注,那么被 @GuanWei 修饰的类,生成文档时,会显示 @GuanWei。如果 @GuanWei 没有被 @Documented 标准,最终生成的文档中就不会显示@GuanWei。
@Inherited 注解
@Inherited 注解可以修饰注解,所以它是一个元注解,代表的作用是“继承”。被 @Inherited 注解修饰的注解,如果作用于某个类上,其子类是可以继承的该注解的。反之,如果一个注解没有被 @Inherited 注解所修饰,那么他的作用范围只能是当前类,其子类是不能被继承的。所以以后我们在定义一个作用于类的注解时候,如果希望该注解也作用于其子类,那么可以用@Inherited 来进行修饰。
@SpringBootConfiguration 注解
@Configuration 用于定义配置类,可替换 xml 配置文件,被注解的类内部包含有一个或多个被 @Bean 注解的方法,这些方法将会被 AnnotationConfigApplicationContext 或AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器。
@SpringBootConfiguration 只是 Spring 标准 @Configuration 注解的替代方法。 两者之间的唯一区别是@SpringBootConfiguration 允许自动找到配置。
@EnableAutoConfiguration 注解
在 Spring 中有很多 Enable 开头的注解,作用就是 @Import 来收集并注册特定的场景相关的 bean,并且加入到 IoC 容器。@EnableAutoConfiguration 就是借助 @Import 来收集所有符合自动配置条件的 bean 定义,并加载到 IoC 容器中。
允许 SpringBoot 自动配置注解,开启这个注解之后,SpringBoot 就能根据当前类路径下的包或者类来配置 Spring 的 Bean。
@EnableAutoConfiguration 实现的关键在于引入了 AutoConfigurationImportSelector,其核心逻辑为 selectImports 方法,逻辑大致如下:
@ComponentScan 注解
@ComponentScan 的作用就是根据定义的扫描路径,把符合扫描规则的类装配到spring容器中。
SpringBoot 在配置上相比 Spring 要简单许多, 其核心在于 spring-boot-starter , 在使用 SpringBoot 来搭建一个项目时, 只需要引入官方提供的 starter, 就可以直接使用, 免去了各种配置。starter 简单来讲就是引入了一些相关依赖和一些初始化的配置。
Spring 官方提供了很多 starter,第三方也可以定义 starter。为了加以区分,starter 从名称上进行了如下规范:
starter 能有这么强大的功效,主要由两部分组成:起步依赖和自动配置。
起步依赖,其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。例如,我们导入 spring-boot-starter-web 这个 starter,则和 web 开发相关的 jar 包都一起导入到项目中了。例如下图:
自动配置,顾名思义就是无须手动配置配置文件(XML或配置类),自动配置并管理 bean,可以简化开发过程。那么 SpringBoot 是如何完成自动配置的呢?
自动配置涉及到如下几个关键步骤:
我们可以通过一个实际的例子 mybatis-plus-boot-starter 来说明自动配置的实现过程。
在 mybatis-plus-boot-starter 引入的 jar 中有一个 MybatisAutoConfiguration 自动配置类,如下图:
具体代码如下:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);
private final MybatisPlusProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List configurationCustomizers;
private final List mybatisPlusPropertiesCustomizers;
private final ApplicationContext applicationContext;
public MybatisPlusAutoConfiguration(MybatisPlusProperties properties, ObjectProvider interceptorsProvider, ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider> configurationCustomizersProvider, ObjectProvider> mybatisPlusPropertiesCustomizerProvider, ApplicationContext applicationContext) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
this.mybatisPlusPropertiesCustomizers = (List)mybatisPlusPropertiesCustomizerProvider.getIfAvailable();
this.applicationContext = applicationContext;
}
public void afterPropertiesSet() {
if (!CollectionUtils.isEmpty(this.mybatisPlusPropertiesCustomizers)) {
this.mybatisPlusPropertiesCustomizers.forEach((i) -> {
i.customize(this.properties);
});
}
this.checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);
Class extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (!ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
}
Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);
if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
}
GlobalConfig globalConfig = this.properties.getGlobalConfig();
this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
this.getBeansThen(IKeyGenerator.class, (i) -> {
globalConfig.getDbConfig().setKeyGenerators(i);
});
this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
private void getBeanThen(Class clazz, Consumer consumer) {
if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
consumer.accept(this.applicationContext.getBean(clazz));
}
}
private void getBeansThen(Class clazz, Consumer> consumer) {
if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
Map beansOfType = this.applicationContext.getBeansOfType(clazz);
List clazzList = new ArrayList();
beansOfType.forEach((k, v) -> {
clazzList.add(v);
});
consumer.accept(clazzList);
}
}
private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {
MybatisConfiguration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new MybatisConfiguration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
Iterator var3 = this.configurationCustomizers.iterator();
while(var3.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
@Configuration(
proxyBeanMethods = false
)
@Import({AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisPlusAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
public AutoConfiguredMapperScannerRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisPlusAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisPlusAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
List packages = AutoConfigurationPackages.get(this.beanFactory);
if (MybatisPlusAutoConfiguration.logger.isDebugEnabled()) {
packages.forEach((pkg) -> {
MybatisPlusAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
});
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set propertyNames = (Set)Stream.of(beanWrapper.getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
builder.addPropertyValue("lazyInitialization", "${mybatis-plus.lazy-initialization:${mybatis.lazy-initialization:false}}");
}
if (propertyNames.contains("defaultScope")) {
builder.addPropertyValue("defaultScope", "${mybatis-plus.mapper-default-scope:}");
}
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
}
}
看看,有没有似曾相识,对的这个就是我们过去在 SpringIoC 中管理 MyBatis 时书写的配置类,这里有管理数据源、会话工厂、自动扫描 Mapper 注册等。
SpringBoot 项目是一个标准的 Maven 项目,它的配置文件需要放在 src/main/resources/ 下,其文件名必须为 application,其存在两种文件形式,分别是 properties 和 yaml(或者 yml )文件。如果两种类型的配置文件同时存在,properties 文件的优先级大于 yml 文件。
properties 是创建 SpringBoot 工程自动创建的,也是以前 web 等技术或 Spring 框架的默认配置文件格式。目前主流是 yml 格式。
properties 中的配置形式是键值对形式,例如:
server.port=8080
而 yml 是层级形式,例如:
server:
port: 8080
大小写敏感;
属性层级关系使用多行描述,每行结尾使用冒号结束;
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键,貌似idea会自动识别);
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔);
# 表示注释。
boolean: TRUE #TRUE,true,True大小写均可,false也一样
float: 3.14 #6.8523015e+5 #支持科学计数法
int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制
null: ~ # ~表示null
string: HelloWorld #字符串可以直接书写
string2: "Hello World" #可以使用双引号包裹特殊字符
date: 2022-09-05 #日期必须使用yyyy-MM-dd格式
datetime: 2022-09-05T22:19:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区(这个一般少见)
#数组的写法
array1:
- zuqiu
- lanqiu
- paiqiu
array2: [zuqiu,lanqiu,paiqiu]
#对象数组的写法
users:
- name: guanwei
age: 18
- name: xiaoming
age: 22
//还可以按照JSON格式来写
users3: [{name:guanwei,age:18},{name:xiaoming,age:22}]
使用 @Value 读取单个数据,属性名引用方式: ${一级属性名.二级属性名……}
@Value("${string}")
private String str;
@Value("${array1[1]}")
private String yundong;
@Value("${users[0].age}")
private Integer userAge;
也可以读取对象格式数据
@ConfigurationProperties(prefix = "users[0]")
public class User{
private Integer age;
private String name;
}