你一定能看懂的 SpringBoot 自动装配原理

简介: 你一定能看懂的 SpringBoot 自动装配原理

1. 什么是 SpringBoot 自动装配

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的 META-INF/spring.factories 文件,将文件中配置的类型信息加载到 Spring 容器并执行类中定义的各种操作。

对于外部 jar 包来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot.

自动装配可以简单理解为:通过注解或者一些简单的配置就能在 SpringBoot 的帮助下实现某块功能。

2. SpringBoot 如何实现自动装配

自动装配避免了手写 xml 文件带来的繁琐可以轻松创建并管理 bean,简化了开发过程,它涉及以下几个关键步骤:

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

2.1 自动配置

环境准备,需要引入 mybatis 的 maven 坐标:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>

mybatis-spring-boot-starter 这个 jar 包为例,该 jar 包下面还包括其他关于 mybatis 的 jar 包,如下图所示:2e718f6de6d94987ae2f2182c528d9c8.png

其中,有一个与 mybatis 自动配置相关的称为 mybatis-spring-boot-autoconfigure 的子 jar 包 ,该 jar 包下找到 MybatisAutoConfiguration 自动配置类,如下图所示:2e718f6de6d94987ae2f2182c528d9c8.png查看 MybatisAutoConfiguration 自动配置类的源码:2e718f6de6d94987ae2f2182c528d9c8.png

该类上面有 @Configuration 注解修饰,被该注解修饰的类可以看作是一个能够生产出让 Spring IOC 容器管理的 Bean 实例的工厂,也就是说 MybatisAutoConfiguration 是一个生产 Bean 实例的工厂类

类中的 sqlSessionFactory 和 sqlSessionTemplate 两个方法被 @Bean 修饰,表示 bean 方法,这两个方法返回的对象可以注册到 Spring 容器中

@Configuration 和 @Bean 这两个注解一起使用就可以创建一个基于 Java 代码的配置类,可以用来替代传统的 xml 配置文件

MybatisAutoConfiguration 自动配置类帮助我们实现了以前在 mybatis.xml 文件中繁琐的配置工作,包括日志、类型处理器、语言驱动、资源加载器以及个性化设置等等。

2.2 自动配置条件依赖

还是以 mybatis-spring-boot-starter 包下的 MybatisAutoConfiguration 自动配置类为例。

从该类上面的 @ConditionalOnClass 和 ConditionalOnSingleCandidate 可以发现,要完成 mybatis 的自动配置还需要依赖条件,那就是在类的路径中必须存在 SqlSessionFactory 和 SqlSessionFactoryBean 这两个类,以及存在 DataSource 类作为 bean.

下表是关于 springboot 提供的条件依赖的注解描述:

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

2.3 Bean 参数获取

要完成 mybatis 的自动配置,还需要我们在配置文件中提供数据源相关的配置参数。例如,数据库驱动、连接 url、数据库用户名、密码等。那么,springboot 就是通过读取 yml 或者 properites 配置文件的的参数来创建数据源对象的。

在 spring-boot-autoconfigure 子包下有一个自动配置类叫作 DataSourceAutoConfiguration,该类实现了自动配置数据源相关的参数的功能。

可以看到在该类上面加了 @EnableConfigurationProperties 注解,这个注解的作用就是使 @ConfigurationProperties 生效。

继续查看 @EnableConfigurationProperties 注解括号里面类 DataSourceProperties 的源码,可以看到该类被 @ConfigurationProperties 修饰,这个注解的作用是把 yml 或者 properties 配置文件中的配置参数信息封装到 DataSourceProperties 类的相应属性上,源码截图如下所示:image.png

2.4 Bean 的发现

对于启动类所在的包下的主类与子类的所有组件 springboot 默认是可以扫描的,但是不包括依赖包中的类,那么依赖包中的 bean 是如何被发现的?

首先看一下 SpringBoot 的核心注解 @SpringBootApplication 的源码:

@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 {
}

@SpringBootApplication 其实可以看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 三个注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:

EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

ComponentScan: 扫描被 @Component (@Service,@Controller) 注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean. 如下图所示,容器中将排除 TypeExcludeFilter 和 AutoConfigurationExcludeFilter2e718f6de6d94987ae2f2182c528d9c8.png

  • 其中,@EnableAutoConfiguration 是实现自动装配的核心注解,看一下它的源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

从上面的源码中可以看到,只是一个接口,并没有实现什么功能,自动装配核心功能其实是通 AutoConfigurationImportSelector 类实现的(@Import 作用是导入需要自动配置的组件)。

查看 AutoConfigurationImportSelector 类的源码,部分源码截图如下所示:

2e718f6de6d94987ae2f2182c528d9c8.png

从上图中可以看到,在该类的 getCandidateConfigurations 方法中调用了 SpringFactoriesLoader类的 loadFactoryNames 方法,继续跟踪源码:2e718f6de6d94987ae2f2182c528d9c8.png

SpringFactoriesLoader 类的 loadFactoryNames 静态方法可以从所有的 jar 包中读取 META-INF/spring.factories 文件,而自动配置的类就在这个文件中进行配置,spring.factories 文件的内容如下所示:2e718f6de6d94987ae2f2182c528d9c8.png

那么,springboot 通过读取文件的内容,便可以发现 bean 了。

2.5 Bean 的加载

在发现依赖包中的 bean 之后,SpringBoot 便可以进行将这些 bean 加载到 Spring 容器进行管理了。

在 SpringBoot 应用中要让一个普通类交给 Spring 容器管理,通常有以下方法:

  • 使用 @Configuration 与 @Bean 注解
  • 使用 @Controller、@Service、@Repository、@Component 注解标注该类并且启用@ComponentScan 自动扫描
  • 使用 @Import 注解

其中,SpringBoot 实现自动配置使用的是 @Import 注解这种方式。

AutoConfigurationImportSelector 类的 selectImports 方法返回一组从 META-INF/spring.factories 文件中读取的 bean 的全类名,这样 SprinBoot 就可以加载这些 bean 并完成实例的创建工作。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   //是否开启自动装配
   if (!this.isEnabled(annotationMetadata)) {
       return NO_IMPORTS;
   } else {
       //获取所有需要装配的bean
       AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
       return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
   }
}

getAutoConfigurationEntry() 方法主要负责加载自动配置类,查看它的源码:

private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        //<1>.
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //<2>.
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //<3>.
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //<4>.
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
}

代码解释:

判断自动装配开关是否打开。默认 spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置

用于获取 EnableAutoConfiguration 注解中的 exclude 和 excludeName

获取需要自动装配的所有配置类,这不就是之前在 2.4 小节中提到的 bean 发现过程中提到的方法嘛!

加载 spring.factories 中的配置,但不是每次启动都会加载其中的所有配置,会有一个筛选的过程,剔除重复的

至此,以上就是 bean 的大致的加载过程。

3. 自定义 starter

那么在理解了 SpringBoot 的自动装配原理之后,可以遵循 SpringBoot 的接口规范自定义一个 starter 来加强对自动装配原理的印象。

自定义的 starter 工程的目录结构如下2e718f6de6d94987ae2f2182c528d9c8.png

首先,创建一个 Project 命名为 hello-spring-boot-starter,pom.xml 文件内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hzz</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--引入自动装配包 autoconfigure-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>
</project>

之后,创建属性类 HelloProperties.java,该类在 com.hzz.config 包下。

package com.hzz.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * 配置属性类,用于封装配置文件中配置的参数信息
 */
@ConfigurationProperties(prefix = "hello")
public class HelloProperties {
    private String name;
    private String address;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "HelloProperties{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

创建服务类 HelloService.java,该类在 com.hzz.service 包下。

package com.hzz.service;
public class HelloService {
    private String name;
    private String address;
    public HelloService(String name, String address) {
        this.name = name;
        this.address = address;
    }
    public String sayHello() {
        return "你好!我的名字叫"+name+",我来自"+address;
    }
}

创建自动配置类 HelloServiceAutoConfiguration,该类在 com.hzz.config 包下。

package com.hzz.config;
import com.hzz.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 自动配置类,用于自动配置HelloService对象
 */
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    private HelloProperties helloProperties;
    //通过构造方法注入配置属性对象 HelloProperties,SpringBoot会帮我们自动注入,如果红线警告可以忽略
    public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }
  //实例化 HelloService 并载入 Spring IOC 容器
    @ConditionalOnMissingBean
    @Bean
    public HelloService helloService() {
        return new HelloService(helloProperties.getName(),helloProperties.getAddress());
    }
}

然后,在 resources 目录下创建 META-INF/spring.factories 文件,文件内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hzz.config.HelloServiceAutoConfiguration

最后,使用 maven 工具将工程安装到本地的 maven 仓库中,供其他工程使用。2e718f6de6d94987ae2f2182c528d9c8.png

注意:为了避免 maven 仓库缓存的影响,避免其他工程发现不到自定义的 starter,建议将本地的 maven 仓库更新一下,如下图所示:2e718f6de6d94987ae2f2182c528d9c8.png

4. 使用自定义 starter

那么在自定义完成一个 starter 之后,就要去在另一个工程中去使用它了,还是新建一个新的工程。

工程的目录结构如下所示2e718f6de6d94987ae2f2182c528d9c8.png

首选,工程命名为 myapp,pom.xml 文件内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hzz</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
      <!--引入自定义的 starter-->
        <dependency>
            <groupId>com.hzz</groupId>
            <artifactId>hello-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

,创建 application.yml 文件

server:
  port: 8080
hello:   # HelloProperties 类头上配置的prefix
  name: 华仔仔coding     
  address: 中国

然后,创建 HelloController.java,该文件在 com.hzz.controller 包下。

package com.hzz.controller;
import com.hzz.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
    @Autowired
    private HelloService helloService; //当前实例已由自定义的starter完成了创建
    @GetMapping("/say")
    public String sayHello() {
        return helloService.sayHello();
    }
}

创建主启动类 HelloApplication.java,该文件在 com.hzz 包下。

package com.hzz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}

最后,运行主启动类,访问 /hello/say接口,地址为 http://localhost:8080/hello/say

2e718f6de6d94987ae2f2182c528d9c8.png

出现上图所示的运行效果表示成功访问!以上便是使用自定义 starter 的过程。

相关文章
|
1月前
|
安全 Java 数据安全/隐私保护
SpringBoot实现二维码扫码登录的原理与详细步骤
SpringBoot实现二维码扫码登录的原理与详细步骤
81 1
|
1月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
43 0
|
2月前
|
缓存 Java Maven
Spring Boot自动配置原理
Spring Boot自动配置原理
48 0
|
3月前
|
NoSQL Java 测试技术
字节二面:Spring Boot Redis 可重入分布式锁实现原理?
字节二面:Spring Boot Redis 可重入分布式锁实现原理?
158 1
|
3月前
|
消息中间件 存储 安全
RabbiMQ原理与SpringBoot使用
RabbiMQ原理与SpringBoot使用
38 0
|
1月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
3月前
|
监控 Java 应用服务中间件
SpringBoot3 快速入门及原理分析
SpringBoot3 快速入门及原理分析
|
10天前
|
Java 容器 Spring
Springboot自动配置原理
Springboot自动配置原理
|
26天前
|
Java API 开发者
springboot 多线程的使用原理与实战
在Spring Boot中实现多线程,主要依赖于Spring框架的@Async注解以及底层Java的并发框架。这里将深入剖析Spring Boot多线程的原理,包括@Async注解的工作方式、任务执行器的角色以及如何通过配置来调整线程行为。
33 5
|
1月前
|
消息中间件 NoSQL Java
springboot - 条件注解@ConditionalOnClass原理
springboot - 条件注解@ConditionalOnClass原理
springboot - 条件注解@ConditionalOnClass原理