baby sword‘s blog baby sword‘s blog
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)

zhengjian

不敢承担失去的风险,是不可能抓住梦想的
首页
  • java基础
  • java进阶
大数据
  • mysql

    • mysql索引
    • mysql日志
  • redis

    • 单机下的redis
    • 集群下的redis
  • Spring
  • springboot
  • RPC
  • netty
  • mybatis
  • maven
  • 消息队列
  • kafka
  • zookeeper
  • rocketmq
  • 七大设计原则
  • 创建型模式
  • 结构型模式
  • 行为型模式
  • SpringCloud

    • eureka
  • SpringCloud Alibaba

    • nacos
  • 计算机网络
  • 操作系统
  • 算法
  • 个人项目
  • 个人面试面经
  • 八股记忆
  • 工作积累
  • 逻辑题
  • 面试

    • 百度后端实习二面
GitHub (opens new window)
  • 其他

  • Spring

  • springboot

    • 自动装配原理
      • 什么是自动装配?
      • 怎么实现自动装配?
        • 规范
        • 原理
        • @EnableAutoConfiguration:实现自动装配的核心注解
        • AutoConfigurationImportSelector:加载自动装配类
      • 自己实现一个自动装配
      • 总结
      • 加餐:
    • spring import注解详解
    • beanDefinition加载过程
    • spring aop详解
  • RPC

  • netty

  • mybatis

  • maven

  • 单元测试

  • 常见框架
  • springboot
xugaoyi
2023-01-01
目录

自动装配原理

# 什么是自动装配?

​ spring自动地将某些bean加载到容器(这个bean我们没有人为声明过,而是自动装载进去的。但是这个类是怎么装载进去的呢?这就是引入我们的自动装配)

例如我们创建一个springboot项目,然后我们想使用一个redis-start来快速整合springboot,整合好后,我们在配置文件中配置相应的账号密码,然后使用@Autowire注解去注入对应的redisTemplate就可以了,但是,这个redisTemplate是如何自己创建好的呢?我们一开始怎么知道这个对象就创建好了?

为什么引入第三方的依赖,就可以引入对应的bean?

思考:实现自动将对象放入容器中,那么我们是不是也要想想中间经历了什么,首先我们是要创建一个对象的是吧?在spring中创建对象的方式有哪些?

  • xml配置:applicationContext.xml (太麻烦一般不使用)
  • @ComponentScan+@Service (一般不使用,因为需要ComponentScan去扫描路径,但是一些三方包我们并不知道路径)
  • @Configuration+@Bean 去xml化 (推荐:但是也存在这个配置类在哪个路径下的问题?然后就是如何实现动态加载;一般是第三方告诉ioc对应的位置,但是如何告诉,约定一个文件放置对应的位置文件信息。)

额外关注下@Import注解,配置文件中可以导入另一个配置类。 例如@Import(UserServiceImpl.class) 此时,UserServiceImpl可能没有被componentScan扫描到,但是import仍可以将这个类引入加载到spring容器中。

实际上自动装配是通过第三种方式

这里额外提一下@import注解的使用方式:

在Spring框架中,@Import注解用于导入其他配置类或组件类,以便将它们纳入到当前配置类中。通过使用@Import注解,可以将其他配置类或组件类的定义引入到当前配置类中,从而实现配置的组合和重用。

@Import注解有以下几种用法:

  1. 导入配置类:可以使用@Import注解导入其他配置类,将其配置信息合并到当前配置类中。这样可以将多个配置类组合在一起,方便管理和维护。
  2. 导入组件类:可以使用@Import注解导入其他组件类,将其实例化并注册到Spring容器中。这样可以将多个组件类集中管理,方便依赖注入和使用。
  3. 导入ImportSelector:可以使用@Import注解导入实现了ImportSelector接口的类,通过该接口的实现类可以动态地选择要导入的配置类或组件类。
  4. 导入ImportBeanDefinitionRegistrar:可以使用@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类,通过该接口的实现类可以动态地注册Bean定义到Spring容器中。

通过使用@Import注解,可以实现配置的模块化和复用,提高代码的可维护性和可扩展性。它是Spring框架中一种重要的组件装配方式。

# 怎么实现自动装配?

# 规范

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

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

# 原理

实现动态装配的核心组件:ImportSelector (返回需要自动加载的类的全路径,注解@Import里面就写具体的ImportSelector实现类) 、 ImportBeanDefinitionRegistrar

我们先看一下 SpringBoot 的核心注解 SpringBootApplication 。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
<1.>@SpringBootConfiguration
<2.>@ComponentScan
<3.>@EnableAutoConfiguration
public @interface SpringBootApplication {

}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration //实际上它也是一个配置类
public @interface SpringBootConfiguration {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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

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

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

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

    image-20230717160939922

@EnableAutoConfiguration 是实现自动装配的重要注解,我们以这个注解入手

# @EnableAutoConfiguration:实现自动装配的核心注解

EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13

我们现在重点分析下AutoConfigurationImportSelector 类到底做了什么?

# AutoConfigurationImportSelector:加载自动装配类

AutoConfigurationImportSelector类的继承体系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}
1
2
3
4
5
6
7
8
9
10
11

可以看出,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // <1>.判断自动装配开关是否打开
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
          //<2>.获取所有需要装配的bean
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。

该方法调用链如下:

image-20230717161116522

现在我们结合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>.获取ConditionOnClass等判断注解的数据
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //<3>.获取所有的spring.factories里面的数据
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //<4>.移除重复数据
            configurations = this.removeDuplicates(configurations);
          //获取exclude的数据并从list中移除
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
          
          //从list中移除不满足ConditionOnClass的数据
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

这里我们还需要知道是怎么加载到spring.factories的目录的,我们继续跟进

getCandidateConfigurations,

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = new ArrayList<>(
				SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
		ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
		Assert.notEmpty(configurations,
				"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}
1
2
3
4
5
6
7
8
9

进入loadFactoryNames,

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}
1
2
3
4
5
6
7
8

在进入loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		result = new HashMap<>();
		try {
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			
			……………………

		
1
2
3
4
5
6
7
8
9
10
11
12
13

这里可以看到FACTORIES_RESOURCE_LOCATION其实就是代表了META-INF/spring.factories,使用classLoader去加载到对应的文件了。

第 1 步:

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

image-20230717161126456

第 2 步 :

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

image-20230717161138075

第 3 步

获取需要自动装配的所有配置类,读取META-INF/spring.factories

spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
1

image-20230717161149110

从下图可以看到这个文件的配置内容都被我们读取到了。XXXAutoConfiguration的作用就是按需加载组件。

image-20230717161158509

不光是这个依赖下的META-INF/spring.factories被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。

所以,你可以清楚滴看到, druid 数据库连接池的 Spring Boot Starter 就创建了META-INF/spring.factories文件。

如果,我们自己要创建一个 Spring Boot Starter,这一步是必不可少的。

image-20230717161208068

第 4 步 :

到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。

很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了。

image-20230717161217017

因为,这一步有经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

@Configuration
// 检查相关的类:RabbitTemplate 和 Channel是否存在
// 存在才会加载
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}
1
2
3
4
5
6
7
8

有兴趣的童鞋可以详细了解下 Spring Boot 提供的条件注解

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件
  • @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

# 自己实现一个自动装配

第一步,创建threadpool-spring-boot-starter工程

image-20230717161226957

第二步,引入 Spring Boot 相关依赖

image-20230717161235073

第三步,创建ThreadPoolAutoConfiguration

image-20230717161243007

第四步,在threadpool-spring-boot-starter工程的 resources 包下创建META-INF/spring.factories文件

image-20230717161249531

最后新建工程引入threadpool-spring-boot-starter

image-20230717161318398

测试通过!!!

image-20230717161325062

代码地址:springboot-autoconfiguration-demo (opens new window)

# 总结

Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,自动配置类其实就是通过@Conditional按需加载的配置类,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖

# 加餐:

关键注解@Import实现原理:

https://juejin.cn/post/6934499568037937160

ConfigurationClassParser.processImports()核心方法

参考:https://juejin.cn/post/7162568709955911717

image-20240108002750836

BeanFactoryPostProcessor 和 BeanPostProcessor 是 Spring Framework 中两个不同的接口,它们在容器启动过程中扮演不同的角色。

BeanFactoryPostProcessor(Bean工厂后置处理器):

BeanFactoryPostProcessor 是在容器加载 BeanDefinition 之后、实例化 Bean 之前执行的。
它可以对 BeanFactory 进行修改和配置,例如修改 BeanDefinition 的属性、注册额外的 BeanDefinition 等。
BeanFactoryPostProcessor 的实现类可以通过实现 postProcessBeanFactory() 方法来完成后置处理操作。
BeanFactoryPostProcessor 在容器启动时执行,对所有的 BeanFactory 都生效。
BeanPostProcessor(Bean后置处理器):

BeanPostProcessor 是在容器实例化 Bean 之后、初始化之前和销毁之后执行的。
它可以对 Bean 进行增强、修改或者进行其他的处理操作,例如在 Bean 初始化前后执行一些逻辑、动态代理等。
BeanPostProcessor 的实现类可以通过实现 postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法来完成后置处理操作。
BeanPostProcessor 在容器启动时执行,对所有的 Bean 都生效。
总结:

BeanFactoryPostProcessor 是在容器加载 BeanDefinition 之后、实例化 Bean 之前执行的,用于对 BeanFactory 进行修改和配置。
BeanPostProcessor 是在容器实例化 Bean 之后、初始化之前和销毁之后执行的,用于对 Bean 进行增强、修改或者进行其他的处理操作。
BeanFactoryPostProcessor 在容器启动时执行,对所有的 BeanFactory 都生效。
BeanPostProcessor 在容器启动时执行,对所有的 Bean 都生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
编辑 (opens new window)
上次更新: 2024/02/22, 14:03:19
小马哥AOP
spring import注解详解

← 小马哥AOP spring import注解详解→

最近更新
01
spark基础
02-22
02
mysql读写分离和分库分表
02-22
03
数据库迁移
02-22
更多文章>
Theme by Vdoing | Copyright © 2019-2024 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式