Spring-Boot-Starter 学习笔记(1)

Spring-Boot-Starter

1. 准备配置类和 Bean 对象

Spring Boot 提供了两个注解:

  • @Configuration:Spring 提供的配置类注解,作用在类上,代表整个类是个 Spring 配置类,对照传统的 Spring XML 配置文件。
  • @Bean:作用于方法上,代表此方法的返回值(对象)将会被 Spring 容器所管理,从而完成 Bean 的自动注册。

这两个组合起来搭配可以完美的代替传统的 Spring XML 配置文件,并给 Spring Boot 的自动配置提供基本数据体。

2. 自动配置条件依赖

有些情况下自动配置类并不是在任何条件下都能生效的,此时我们需要制定自动配置生效的条件,可以使用 Spring Boot 提供的注解来指定生效条件。

这些注解是 spring boot 特有的,常见的条件依赖注解有:

注解 功能说明
@ConditionalOnBean 仅在当前上下文中存在某个 bean 时,才会实例化这个 Bean
@ConditionalOnClass 某个 class 位于类路径上,才会实例化这个 Bean
@ConditionalOnExpression 当表达式为 true 的时候,才会实例化这个 Bean
@ConditionalOnMissingBean 仅在当前上下文中不存在某个 bean 时,才会实例化这个 Bean
@ConditionalOnMissingClass 某个 class 在类路径上不存在的时候,才会实例化这个 Bean
@ConditionalOnNotWebApplication 不是 web 应用时才会实例化这个 Bean
@AutoConfigureAfter 在某个 Bean 完成自动配置后实例化这个 Bean
@AutoConfigureBefore 在某个 Bean 完成自动配置前实例化这个 Bean

3. Bean 的参数获取

举个例子,例如在 Spring Boot Web 项目中,我们经常会导入 MyBatis 相关的依赖,帮助我们与数据库打交道,那么在传统的 Spring 项目中,我们一般会在 Spring 容器配置 XML 文件中去使用 <bean> 标签生成相关数据源(DataSource),那么这个 DataSource 需要我们提供数据库连接参数:driver、url、username、password 这四个最基本的参数,这些数据可能放在一个叫做 db.properties 文件中,这种文件我们称为外部数据源文件 ,在 Spring 配置文件中声明:<context:property-placeholder location="classpath:db.properties"/>

这样就可以引入文件中的配置参数了,从而赋值给 DataSource 这个 Bean 所需要的属性参数。最后完成对象初始化。

这个过程在传统 Spring 开发,无疑是略显繁琐,如果在某些我们需要自定义类和大量参数属性从外部文件引入,这个时候 properties 文件格式也比较复杂,文件可能较多,在初始化 Bean 时,需要手写大量的属性赋值。

那么 Spring Boot 提供了注解帮助我们减小开发量,更加规范 Bean 参数的获取方式。

默认情况下我们 Bean 的参数配置在 application.yml 文件中,使用 YAML 文件格式定义,比 properties 文件更有层级感,更简约。

搭配 @EnableConfigurationProperties、@ConfigurationProperties 这两个注解可以直接实现自动配置类的 Bean 参数获取。

3.1 @EnableConfigurationProperties 注解

这个注解使用情况:自动配置类中需要从外部文件获取参数,来进行初始化。

在注解中指定一个类,这个时候可以配置类可以在这个类中获取到外部文件的参数,交给配置类中的 Bean 进行初始化。

3.2 @ConfigurationProperties 注解

注解使用情况:从外部文件获取参数信息,加载到自身类属性中,给 Spring 配置类提供外部数据来源,外部数据文件通常指的是 application.yml

在注解中指定从 application.yml 文件获取的前缀,例如 @ConfigurationProperties(prefix="spring.datasource"),会获取以这个字符串为前缀的所有参数进行自动匹配赋值。

所以可以看出两个注解的关系是:

@EnableConfigurationProperties 注解作用在配置类 中,并且使得该注解指定的数据文件类 中的 @ConfigurationProperties 注解生效。

4. Bean 的发现

4.1 自己项目的 Bean 扫描

在写 Spring Boot 项目时,一般在项目的代码的根目录会有一个 Spring Boot 启动类:xxxApplication.java,这个类被 @SpringBootApplication 注解修饰标记成 Spring Boot 项目的启动类。

java 复制代码
@SpringBootApplication
public class SpringbootStarterApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootStarterApplication.class, args);
    }
}

此时再来看看如何完成 Bean 扫描,我们需要查看 @SpringBootApplication 注解源码:

java 复制代码
@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 {
    //...
}

我们重点查看 @SpringBootConfiguration 注解:

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

可以看到这个注解被熟悉的 @Configuration 注解修饰。@SpringBootConfiguration 应用标注在某个类上说明这个类是SpringBoot 的主配置类,SpringBoot 需要运行这个类的 main 方法来启动 SpringBoot 应用。

底层 Spring Boot 会帮我们将启动类的当前路径包以及子包的所有 Spring 组件(可能需要 @ComponentScan注解去做组件扫描)以及 Bean 扫描初始化。暂时只说浅层的流程,后续会深入 Spring 源码学习。

4.2 jar 包的 Bean 扫描

那么前面聊了自己项目的 Bean 扫描,且 Spring Boot 默认扫描启动类所在包下的主类与子类的所有组件,其中并没有包括项目依赖包中的类,那么这些类是如何被 Spring Boot 发现的呢?

这就是第二个主要注解:@EnableAutoConfiguration,开启自动配置:

这里引入其他博客的理解,觉得这几句话简单易懂:

一、@EnableAutoConfiguration 的作用 简单点说就是 Spring Boot 根据依赖中的 jar 包,自动选择实例化某些配置,配置类必须有 @Configuration 注解。

说白了,还是实例化对象,只是实例化的是依赖包中的类。

另外,我们也可以按照自动装配的规范自己定义装配的类。

接下来查看一下注解源码:

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    //...
}

主要看 @Import({AutoConfigurationImportSelector.class}),这个注解导入了 AutoConfigurationImportSelector.class 这个类(如果是其他版本,可能会是 @EnableAutoConfigurationImportSelector 这个是子类)。

我们找到 getCandidateConfigurations() 方法,这个方法就是用来加载依赖所需要的自动配置相关。

看到 SpringFactoriesLoader.loadFactoryNames() 的源码:

其他版本可能是只有 loadFactoryNames() 方法,但是我们主要关注 classLoader.getResources() 方法中的常量:

很明显这个方法是用来加载资源文件,而这个 Spring 工厂资源的路径就是依赖中自动配置的相关路径,根据这个路径找到需要自动配置的类,最后完成依赖的自动配置。我们导入 mybatis-spring-boot-starter 依赖看看这个 META-INF/spring.factories 里边是怎么样的:

factories 复制代码
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

简单概括的流程:Spring Boot 会根据 jar 包的 META-INF/spring.factories 文件的配置进行自动装配,装配的流程是 SpringFactoriesLoader.loadFactoryNames() 方法顶层实现的(底层实现需深入源码),而开始外部自动装配的注解是:@SpringBootApplication 注解中的 @Import({AutoConfigurationImportSelector.class})

主导整个过程。

5. Bean 的加载

在 Spring Boot 中将一个普通类交给 Spring 容器管理,通常有以下几个方法:

  1. 使用 @Configuration 配合 @Bean 注解使用(配置类)。
  2. 使用 @Controller、@Service、@Repository、@Component 注解标注类并且使用 @ComponentScan 自动扫描(组件扫描)。
  3. 使用 @Import 方法(加载外部依赖的类)。

在上面可以看到 Spring Boot 实现自动配置使用的是 @Import 注解这种方式。AutoConfigurationImportSelector 类的 selectImports() 方法(其中调用了 getAutoConfigurationEntry() 方法主要过程)返回一组从 META-INF/spring.factories 文件中读取的 Bean 的全限定名,这样 Spring Boot 就可以加载到这些 Bean 并完成实例的初始化工作。

自动配置总结

经过前面的分析,将自动配置的关键步骤以及对应注解抽取出来:

  1. 定义需要自动装配的类信息:@Configuration、@Bean,Spring Boot 配置类。
  2. 设置自动配置条件依赖:@Conditional
  3. 将外部配置文件读取并封装成 Bean,让配置类读取参数:@EnableConfigurationProperties、@ConfigurationProperties
  4. 实现 Bean 的发现与加载:@EnableAutoConfiguration、@Import

以上的内容是基于其他相关博客内容的基础,进行自己的学习记录,接下来需要自定义一个 Spring Boot Starter 来进一步加深理解,后续可能还需要对 Spring 、Spring Boot 的源码进行学习。

相关推荐
朱龙凯24 分钟前
MySQL那些事
后端
Re27531 分钟前
剖析 MyBatis 延迟加载底层原理(1)
后端·面试
Victor35635 分钟前
MySQL(63)如何进行数据库读写分离?
后端
Cache技术分享36 分钟前
99. Java 继承(Inheritance)
前端·后端
M1A137 分钟前
Python数据结构操作:全面解析与实践
后端·python
程序员蜗牛38 分钟前
Controller层代码瘦身70%!5招打通任督二脉,效率飙升
后端
程序员岳焱39 分钟前
Java高级反射实战:15个场景化编程技巧与底层原理解析
java·后端·编程语言
程序员小假39 分钟前
说一说 Netty 中的心跳机制
java·后端
David爱编程40 分钟前
Docker 存储卷详解:数据持久化的正确打开方式
后端·docker·容器
Re27540 分钟前
MyBatis 延迟加载:性能优化的秘密武器
后端