SpringBoot自动装配原理及分析

一、什么是自动装配

在使用SpringBoot的时候,会自动将Bean装配到IOC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到Ioc容器中进行后续使用。
自动装配的大致过程如下:

  • 获取到组件(spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件
  • spring.factories文件中列出需要注入Ioc容器的类
  • 将实体类注入到Ioc容器中使用

二、自动装配原理源码分析

自动装配原理的大致流程是通过@SpringBootApplication进行实现,这个注解声明在SpringBoot的启动类上。

1.@SpringBootApplication注解

@SpringBootApplication是一个组合注解,主要由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan组成

@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 {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

@Target({ElementType.TYPE})等前四个注解JDK中的元注解,用于修饰注解的注解
@Filter用于实现过滤,比如可以将某些类排除在外
@AliasFor注解用于为注解属性声明别名

2.@SpringBootConfiguration

其中@SpringBootConfiguration的本质其实是@Configuration,标识该Java类是一个配置类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

3.@ComponentScan注解

@ComoponentScan是用来指定扫描路径的,如果不指定特定的扫描路径的话,扫描的路径是当前修饰类所在的包及其子包。

4.@EnableAutoConfiguration注解

@EnableAutoConfiguration这个注解是SpringBoot自动装配的关键。它也有两个注解@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class}).

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

@AutoConfigurationPackage
其中@AutoConfigurationPackage(basePackages =" ")的作用是指定SpringBoot要扫描的包,将包中的Bean注入IOC容器中。该注解内部是一个@Import注解,引入了一个Registrar.class。Registrar类的作用将将主启动类所在包以及子包里的所有组件扫描到Spring容器里。
注意:@AutoConfigurationPakage与@ComponentScan都是将Spring Boot启动类所在的包及其子包里面的组件扫描到IOC容器中,但区别在于@AutoConfigurationPackage还会扫描@Enitity、@Mapper等第三方依赖的注解,@ComponentScan只扫描@Controller/@Service/@Component/@Repository等Spring容器相关的注解。
参考:https://www.jb51.net/program/288508ml1.htm

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

@EnableAutoConfiguration注解最重要的是AutoConfigurationImportSelector.class,将需要装配的类装配到IoC容器中,下面重点分析一下这个类的实现

5.AutoConfigurationImportSelector.class

AutoConfigurationImportSelector实现了ImportSelector接口,那么我们清楚只需要关注selectImports方法的返回结果即可。
AutoConfigurationImportSelector中的selectImports方法是自动装配的核心实现,它主要是读取META-INF/spring.factories文件,经过去重、过滤,返回需要装配的配置类集合

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            // 返回的就是需要注册到IoC容器中的对象对应的类型的全类路径名称的字符串数组
            // ["com.bobo.pojo.User","com.bobo.pojo.Person", ....]
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

我们清楚了该方法的作用就是要返回需要注册到IoC容器中的对象对应的类型的全类路径名称的字符串数组。那么我接下来分析的关键是返回的数据是哪来的?所以呢进入getAutoConfigurationEntry方法中。

 protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            // 获取注解的属性信息
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            // 获取候选配置信息 加载的是 当前项目的classpath目录下的 所有的 spring.factories 文件中的 key 为
            // org.springframework.boot.autoconfigure.EnableAutoConfiguration 的信息
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

getAttributes方法:获取@EnableAutoConfiguration中的exclude、excludeName等
getCandidateConfigurations方法:获取所有自动装配的配置类,也就是读取spring.factories文件,后面会再次说明
removeDuplicates方法:去除重复的配置项
getExclusions方法:根据@EnableAutoConfiguration中的exclude、excludeName移除不需要的配置类
fireAutoConfigurationImportEvents方法:广播事件
最后根据多次过滤、判重返回配置类合集
在这里插入图片描述

6.getCandidateConfigurations方法

 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

通过loadFactoryNames方法,扫描classpath下的META-INF/spring.factories文件,里面是以key=value形式存储,读取其中key=EnableAutoConfiguration,value就是需要装配的配置类,也就是getCandidateConfigurations返回的值

7.loadFactoryNames方法

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		// 此处的类加载器是appClassLoader
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			//如果为空则再次获取
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		// 类的全限定名称(即解析properties文件中需要的key值)
		String factoryTypeName = factoryType.getName();
		// 根据类加载器,加载classpath下/META-INF/spring.factories下所有的类名称列表
		// 从结果Map<String, List<String>>中,根据指定类型获取所有实现类名称的集合List<String>
		// 核心
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

  • loadSpringFactories(ClassLoader classLoader)返回的是所有META-INF/spring.factories文件解析完的结果
  • 返回值: 根据指定类名获取结果,没有则返回空列表

8. loadSpringFactories方法

	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
		// 根据类加载器去缓存中获取加载好的结果集
		//因为SpringApplication实例化时就加载过一次了,这里就是从缓存中获取到值了
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			//结果不为空,则返回
			return result;
		}
		//已从缓存获取到结果,后面代码省略
		//已从缓存获取到结果,后面代码省略
		//已从缓存获取到结果,后面代码省略
	}

因为SpringApplication实例化时就加载过一次了,所以这里可以从缓存中获取到值。具体原理查看Alian解读SpringBoot 2.6.0 源码(一):SpringApplication对象创建(Spring工厂加载机制)

总结

1)通过注解@SpringBootApplication=>@EnableAutoConfiguration=>@Import({AutoConfigurationImportSelector.class})实现自动装配
2)AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类
3)通过Spring提供的SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要自动装配的配置类
4)依据条件筛选的方式,把不符合的配置类移除掉,最终完成自动装配
参考:
链接1
链接2

相关问题:

1.@Component,@Bean,@Import的区别

@Component作用于类,@Bean作用于方法,@Import作用于类

@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean。

@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。@Bean通常用于配置类中声明Bean,配合@Configuration使用。

@Import注解是Java中的元注解,,@Import注解是用于引入其他配置类或Bean的注解。它可以帮助将特定的配置类或Bean注册到Spring容器中,使其可供应用程序使用。
参考:https://blog.csdn.net/Ascend1977/article/details/131391041

2.@Bean一定需要搭配@Configuration才能使用吗?

不需要。Spring允许通过@Bean注解方法来向容器中注册Bean.默认情况下,bean应该是单例的,但是如果我们手动去调用@Bean方法,bean会被实例化多次,这破坏了bean的单例语义。
于是,Spring提供了@Configuration注解,当一个配置类被加上@Configuration注解后,Spring会基于该配置类生成CGLIB代理类,子类会重写@Bean方法,来保证bean是单例的.

参考:https://blog.51cto.com/u_16099278/7039588
https://blog.csdn.net/z69183787/article/details/121979457

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值