你一定能看懂的 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 包,如下图所示:

在这里插入图片描述

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

在这里插入图片描述

查看 MybatisAutoConfiguration 自动配置类的源码:

在这里插入图片描述

  • 该类上面有 @Configuration 注解修饰,被该注解修饰的类可以看作是一个能够生产出让 Spring IOC 容器管理的 Bean 实例的工厂,也就是说 MybatisAutoConfiguration 是一个生产 Bean 实例的工厂类
  • 类中的 sqlSessionFactorysqlSessionTemplate 两个方法被 @Bean 修饰,表示 bean 方法,这两个方法返回的对象可以注册到 Spring 容器中
  • @Configuration@Bean 这两个注解一起使用就可以创建一个基于 Java 代码的配置类,可以用来替代传统的 xml 配置文件
  • MybatisAutoConfiguration 自动配置类帮助我们实现了以前在 mybatis.xml 文件中繁琐的配置工作,包括日志、类型处理器、语言驱动、资源加载器以及个性化设置等等。

2.2 自动配置条件依赖

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

从该类上面的 @ConditionalOnClassConditionalOnSingleCandidate 可以发现,要完成 mybatis 的自动配置还需要依赖条件,那就是在类的路径中必须存在 SqlSessionFactorySqlSessionFactoryBean 这两个类,以及存在 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 类的相应属性上,源码截图如下所示:

在这里插入图片描述

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 和 AutoConfigurationExcludeFilter
    在这里插入图片描述
    其中,@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 类的源码,部分源码截图如下所示:

在这里插入图片描述
从上图中可以看到,在该类的 getCandidateConfigurations 方法中调用了 SpringFactoriesLoader类的 loadFactoryNames 方法,继续跟踪源码:

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

在这里插入图片描述

那么,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);
        }
}

代码解释

  1. 判断自动装配开关是否打开。默认 spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置
  2. 用于获取 EnableAutoConfiguration 注解中的 exclude 和 excludeName
  3. 获取需要自动装配的所有配置类,这不就是之前在 2.4 小节中提到的 bean 发现过程中提到的方法嘛!
  4. 加载 spring.factories 中的配置,但不是每次启动都会加载其中的所有配置,会有一个筛选的过程,剔除重复的

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

3. 自定义 starter

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

自定义的 starter 工程的目录结构如下

在这里插入图片描述

首先,创建一个 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 仓库中,供其他工程使用。

在这里插入图片描述

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

在这里插入图片描述

4. 使用自定义 starter

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

工程的目录结构如下所示

在这里插入图片描述

首选,工程命名为 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

在这里插入图片描述
出现上图所示的运行效果表示成功访问!以上便是使用自定义 starter 的过程。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ReadThroughLife

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值