SpringBoot 运行机制

SpringBoot 框架是为了能够帮助使用 Spring 框架的开发者快速高效的构建一个基于 Spirng 框架以及 Spring 生态体系的应用解决方案。它是对“约定优于配置”这个理念下的一个最佳实践。目的是简化配置文件,俗称“快速搭建”。

我们都知道的是,使用框架的目的是简化我们的开发,将一些公共组件进行封装得以复用。不论是 Spring 中的 Ioc、aop、MVC 还是 MyBatis 它们都使得我们的 CRUD 变得更加简单,但是要使用他们,需要导入相关的依赖,书写各自的配置文件并加载它们,依赖和依赖之间还不能有冲突,重构版本时要操作很多的配置文件或类,搭配之间的依赖关系,这种感觉就如同老坛酸菜面一样的酸爽。

那这个时候我们就在想,有没有一种方式可以把它们集中到一起进行管理?

答案是,必须有的,它就是

SpringBoot 运行机制_第1张图片

就如上边图片的显示一样,SpringBoot 就如同电脑上的开机键一样,只需要按上按钮,电脑就会自行启动。看,多么简单! 

在本篇博客中我着重的从 SpringBoot 的运行机制、配置文件等方面来进行讲解。

SpringBoot 启动类

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

@SpringBootApplication

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 可更加明晰其修饰的目标。作用:用于描述注解的使用范围,也可以理解成被描述的注解可以用在什么地方。

  1. CONSTRUCTOR:用于描述构造器。
  2. FIELD:用于描述域。
  3. LOCAL_VARIABLE:用于描述局部变量。
  4. METHOD:用于描述方法。
  5. PACKAGE:用于描述包。
  6. PARAMETER:用于描述参数。
  7. TYPE:用于描述类、接口(包括注解类型) 或enum声明。

@Retention(RetentionPolicy.RUNTIME) 注解

注解 @Retention 可以用来修饰注解,是注解的注解,被称为元注解。它表明了注解的生命周期、
@Retention 注解有一个属性 value,它决定了 Retention 注解应该如何去保存注解生命周期。

RetentionPolicy有3个值:CLASS RUNTIME SOURCE。按生命周期来划分可分为3类:

  1. RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
  2. RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
  3. RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

@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 方法,逻辑大致如下:

  1. 从配置文件 META-INF/spring.factories 加载所有可能用到的自动配置类。
  2. 去重,并将 exclude 和 excludeName 属性携带的类排除。
  3. 过滤,将满足条件的自动配置类返回。

@ComponentScan 注解

@ComponentScan 的作用就是根据定义的扫描路径,把符合扫描规则的类装配到spring容器中。

SpringBoot 依赖项

SpringBoot 在配置上相比 Spring 要简单许多, 其核心在于 spring-boot-starter , 在使用 SpringBoot 来搭建一个项目时, 只需要引入官方提供的 starter, 就可以直接使用, 免去了各种配置。starter 简单来讲就是引入了一些相关依赖和一些初始化的配置。

Spring 官方提供了很多 starter,第三方也可以定义 starter。为了加以区分,starter 从名称上进行了如下规范:

  • Spring 官方提供的 starter 名称为:spring-boot-starter-xxx 例如 Spring 官方提供的 spring-boot-starter-web。
  • 第三方提供的 starter 名称为:xxx-spring-boot-starter 例如由 mybatis 提供的 mybatis-spring-boot-starter。

starter 能有这么强大的功效,主要由两部分组成:起步依赖和自动配置。 

起步依赖

起步依赖,其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。例如,我们导入 spring-boot-starter-web 这个 starter,则和 web 开发相关的 jar 包都一起导入到项目中了。例如下图:

SpringBoot 运行机制_第2张图片 

自动配置

自动配置,顾名思义就是无须手动配置配置文件(XML或配置类),自动配置并管理 bean,可以简化开发过程。那么 SpringBoot 是如何完成自动配置的呢?

自动配置涉及到如下几个关键步骤:

  1. 基于 Java 代码的 Bean 配置。
  2. 自动配置条件依赖。
  3. Bean 参数获取。
  4. Bean 的发现。
  5. Bean 的加载。

我们可以通过一个实际的例子 mybatis-plus-boot-starter 来说明自动配置的实现过程。 

在 mybatis-plus-boot-starter 引入的 jar 中有一个 MybatisAutoConfiguration 自动配置类,如下图:

SpringBoot 运行机制_第3张图片

具体代码如下:

@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 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 配置文件

SpringBoot 项目是一个标准的 Maven 项目,它的配置文件需要放在 src/main/resources/ 下,其文件名必须为 application,其存在两种文件形式,分别是 properties 和 yaml(或者 yml )文件。如果两种类型的配置文件同时存在,properties 文件的优先级大于 yml 文件。

区别

properties 是创建 SpringBoot 工程自动创建的,也是以前 web 等技术或 Spring 框架的默认配置文件格式。目前主流是 yml 格式。

properties 中的配置形式是键值对形式,例如:

server.port=8080

而 yml 是层级形式,例如:

server:
    port: 8080

YML 的特点

大小写敏感;
属性层级关系使用多行描述,每行结尾使用冒号结束;
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用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}] 

读取 yml 中的内容

使用 @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;
}

SpringBoot 加载机制

SpringBoot 运行机制_第4张图片

 

你可能感兴趣的:(Spring,spring,boot,spring)