SpringBoot自动注入原理

lz 1年前 ⋅ 1029 阅读

前言

使用 springboot 进行项目开发时,无需各种配置文件,无需繁杂的 pom 坐标,只要一个 xxxApplication (启动类)就 run 起来了,那 springboot 是怎么做到约定大于配置的?

自定义 starter

为什么要自定义 starter

在日常开发中,经常会有一些独立于业务的公共模块,如果多个工程中都可以复用这个公共模块的话,不需要手动拷贝到工程中,我们将公共的模块封装成一个个starter,复用的时候直接引入依赖即可,springboot为我们完成自动装配。

命名规则

springboot官方提供的 starter 以 spring-boot-starter-xxx 方式命名。官方建议自定义的 starter 使用 xxx-spring-boot-starter 的方式命名,以区分这两种 starter

自定义 starter 实现

1、新建空 project ,名为:hello-spring-boot-starter

2、新建子 modules,名为:hello-spring-boot-autoconfigure
3、hello-spring-boot-autoconfigure 中 pom引入依赖

<!--自动配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

<!--支持读取xml/properties文件配置-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

4、新建 HelloProperties 类

// 配置属性前缀
@ConfigurationProperties("my.hello")
public class HelloProperties {
    private String name;
    private Integer age;
    private String hometown;
    // getter setter toString 略
}

5、新建 HelloService 类

public class HelloService {
    private String name;
    private Integer age;
    private String hometown;

    public HelloService(String name, Integer age, String hometown) {
        this.name = name;
        this.age = age;
        this.hometown = hometown;
    }

    public String sayHello(String name) {
        return "hello..." + name;
    }

    public String helloWorld() {
        return String.format("[name=%s, age=%d, hometown=%s]", this.name, this.age, this.hometown);
    }
}

6、新建 HelloServiceAutoConfiguration 类

@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    private final HelloProperties helloProperties;

    public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    @Bean
    @ConditionalOnMissingBean		// HelloService 类不存在时执行此方法
    public HelloService helloService() {
        return new HelloService(this.helloProperties.getName(), this.helloProperties.getAge(), this.helloProperties.getHometown());
    }
}

7、类路径下新建 META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.springboot.HelloServiceAutoConfiguration

8、modules mvn clean install ,将子modules打包到本地仓库

9、在父project中引入子 modules 的依赖

10、此时,自定义starter已经创建完成了,新建子modules ,引入此starter测试
11、spring-boot-test 中引入依赖

<!--自定义starter-->
<dependency>
   <groupId>com.springboot</groupId>
   <artifactId>hello-spring-boot-autoconfigure</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

12、新建 HelloController 类

@RestController
@RequestMapping("/hello")
public class HelloController {
    @Resource
    private HelloService helloService;

    @GetMapping("/{name}")
    public String hello(@PathVariable String name) {
        return helloService.sayHello(name);
    }
    
	@GetMapping
    public String helloWorld() {
        return helloService.helloWorld();
    }
}

13、application.properties 添加配置信息

my.hello.name=feiyangyang
my.hello.age=19
my.hello.hometown=bj

14、启动项目,测试

自动配置原理

springboot核心注解 @SpringBootApplication 是个组合注解,其中包括三个重要注解

 

@SpringBootConfiguration

我们可以看到,@SpringBootConfiguration 注解继承自 @Configuration,都用来声明当前类是个配置类。并且会将当前类声明的一个或多个以 @Bean 注解标记的实例注入 IOC 容器中。

并且,Spring 的配置类也是 IOC 容器的组件。

 

@EnableAutoConfiguration

@EnableAutoConfiguration 注解是 springboot 实现自动化配置的核心注解,就是通过这个注解把 spring 应用所需的 bean 注入到容器中。

再来看看 @AutoConfigurationPackage 注解,自动配置包,主要是使用 @Import 来给 spring 容器导入一个组件,这里导入的是 AutoConfigurationPackages.Registrar.class

我们来看 AutoConfigurationPackages.Registrar.class ,发现是 AutoconfigurationPackages 类的内部类。通过分析 AutoConfigurationPackages.Registrar 类的 registerBeanDefinitions 方法,发现其内部调用的 register 方法的参数是 PackageImports 类的实例,我们来到 PackageImports 类的构造,发现此构造就是拿到所有要扫描的包路径。

我们打断点debug,发现拿到的包路径正是我们项目的包路径。

而 PackageImports 构造的参数 AnnotationMetadata 是什么东西?发现其实就是项目的启动类,看 PackageImports 构造发现,不过就是拿到了启动类所在的包路径,获取到了包路径,springboot 会将这个包路径下的组件扫描到 IOC 容器。

我们回到 @EnableAutoConfiguration 看看 @Import 上面标注的类是做什么的。

我们点到 AutoConfigurationImportSelector 类发现有一个 selectImports 方法,而这个方法做的就是将所有组件注入到 IOC 容器中,并且将这些组件的全限定类名返回,我们来验证一下。

selectImports() 方法有个 getAutoConfigurationEntry() ,发现getAutoConfigurationEntry()拿到了很多 自动配置类 (xxxAutoConfiguration) ,将这些自动配置类注入到 IOC 容器。那 springboot 是怎么拿到这些 xxxAutoConfiguration 类的?

 

最终 debug 来到 loadSpringFactories() 方法,发现扫描的是 META-INF/spring.factories 文件。

 

 

将META-INF/spring.factories文件中的内容用 ConcurrentReferenceHashMap 存储起来,此ConcurrentReferenceHashMap 是个Map<ClassLoader, Map<String, List>>这样的结构,其中包括了类加载器,以 META-INF/spring.factories 文件中属性名 org.springframework.boot.autoconfigure.EnableAutoConfiguration 作为 key,所有属性值作为value 的形式存储起来。

稍微总结一下,springboot 在启动的时候从类路径下的 META-INF/spring.factories 文件中获取 org.springframework.boot.autoconfigure.EnableAutoConfiguration 指定的值,将这些值作为自动配置类导入到容器中,自动配置类就会生效,帮我们进行自动配置工作,以前我们需要自己配置的东西,自动配置类就帮我们完成了。

springboot自动配置的原理:从 classpath 中搜寻所有的 META-INF/spring.factories 文件,并将其中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 属性的值 xxxAutoConfiguration 添加到容器中。

@ComponentScan

@ComponentScan 注解的功能就是自动扫描并加载符合条件的组件(eg:@Component,@Controller …),最终将这些 bean 定义加载到 IOC 容器中。

以上自定义的 starter 为例

还记得我们自定义 starter 过程中有在 META-INF/spring.factories 中指定自动配置类的全限定类名,按照上述的讲解,这个自动配置类会被 springboot 获取到并注入到 IOC 容器,如下图,果然被加入进了需要注入的自动配置类列表中。

我们再来看看 HelloServiceAutoConfiguration 类中做了什么?

// 声明此类为配置类,和 xml 配置文件注入方式作用相同
@Configuration
// 启动指定类的 ConfigurationProperties 功能,简单来说,将配置文件的值和 HelloProperties 属性进行绑定,我们在Yml中设置的属性都是通过与 xxxProperties 类中的属性进行绑定实现的
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    private final HelloProperties helloProperties;

    public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    @Bean
    @ConditionalOnMissingBean		// HelloService 类不存在时执行此方法,并且使用yml中配置的属性值传入作为构造参数新建 HelloService 实例
    public HelloService helloService() {
        return new HelloService(helloProperties.getName(), helloProperties.getAge(), helloProperties.getHometown());
    }
}

一旦这个配置类生效,并且符合所指定的条件(@ConditionalOnMissingBean)就会向 IOC 容器中注入 HelloService ,HelloService 类的属性是从对应的 properties 类中获取的,而这些属性的值又是和配置文件中绑定的。

所有能在配置文件中能配置的属性都是在 xxxProperties 类中封装着的,配置文件能配置什么就可以参照某个功能对应的xxxProperties 类。

总结

springboot在启动时为我们注入了大量的 xxxAutoConfiguration 类,而这些类是通过扫描类路径下的 META-INF/spring.factories 文件获得的。这些 xxxAutoConfiguration 类生效后,又会向IOC容器中注入 各 xxxAutoConfiguration 对应的业务类,而这些业务类的属性值又被封装在 xxxProperties 类中,而 xxxProperties 类中的属性对应的值又和 在引入此 starter 的工程中yml中定义的属性相绑定。

所以,springboot中完成一个类的自动注入,需要有如下两个类的配合才行。

  • xxxAutoConfiguration:自动配置类,向容器添加组件
  • xxxProperties:封装配置文件中相关属性

 

--end--

 

版权 本着开源共享、共同学习的精神,本文转载自 https://blog.csdn.net/weixin_42653522/article/details/117255705 , 如果侵权之处,请联系博主进行删除,谢谢~