前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot自动装配的原理

SpringBoot自动装配的原理

作者头像
NorthS
发布2023-03-21 21:24:26
6000
发布2023-03-21 21:24:26
举报
文章被收录于专栏:北冥博客北冥博客

前言

自动装配是SpringBoot的核心,从Spring的起源来说,一开始是通过XML文件对Bean进行装载,后来使用JavaConfig的方式实现无配置化的解决方案,但是本质问题并没有得到解决,直到SpringBoot的问世,帮开发者省略了繁琐的基础性的工作,是开发者更加关注于逻辑本身,SpringBoot将繁琐的基础性工作交给了Starter组件和自动装配去做。

自动装配的原理

自动装配的实现

当我们新建一个SpringBoot项目时,启动类上只有一个注解(如下),我们点进去这个注解

代码语言:javascript
复制
@SpringBootApplication
public class ElmSpringApplication {

    public static void main(String[] args) {
        SpringApplication.run(ElmSpringApplication.class, args);
    }

}

当我们点进来后(如下),SpringBoot官方的开发人员也对这个注解进行了说明,简单来说@SpringBootApplication就是@Configuration, @EnableAutoConfiguration@ComponentScan的集合,下面我们分别说下这几个注解的作用:

  • @Target:用来表示注解的作用范围
  • @Retention:指定它所修饰注解的保留策略,也就是该注解的生命周期
  • @Documented:标记注解,可以被例如javadoc此类的工具文档化
  • @Inherited:用于标注一个父类的注解是否可以被子类继承
  • @SpringBootConfiguration:继承自@Configuration,允许在上下文中注册额外的 bean 或导入其他配置类
  • @EnableAutoConfiguration:开启SpringBoot的自动装载机制
    • @Enable:Spring3.1就已经支持@Enable注解了,他的作用是把相关组件的Bean装配到IoC容器中,如果基于JavaConfig的形式完成Bean的装载就必须使用@Configuration@Bean,而@Enable本质上就是针对这两个注解的封装
  • @ComponentScan:扫描包下的类中添加了@Component (@Service@Controller@Repostory@RestController)注解的类 ,并添加的到spring的容器中,可以自定义不扫描某些 bean
代码语言:javascript
复制
/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @since 1.2.0
 */
@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 {};

	
	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

EnableAutoConfiguration

进入@EnableAutoConfiguration注解(如下),可以看到除了我们在上面所说的注解外还多出了两个注解:@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class),这两个注解在这的作用是:

  • @AutoConfigurationPackage:把使用了该注解所在包及子包下的所有组件扫描到Spring IoC容器中
  • @Import(AutoConfigurationImportSelector.class):一般情况下,@Import导入的应该是一个Configuration配置类,但在这里导入的是一个AutoConfigurationImportSelector类,我会在下文中详细介绍此类,但知道的是,无论此类是什么,它一定会实现配置类的导入
代码语言:javascript
复制
@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

AutoConfigurationImportSelector类实现了ImportSelector接口(关系如下),它只有一个selectImports抽象方法,并且返回一个String数组,在这个数组中可以指定装配到IoC容器的类,当@Import导入ImportSelector的实现类后,就会把该方法返回的String数组里的类名的类都装载到IoC容器里。和@Configuration不同的是,这种方法可以实现自动装配,也就是根据上下文来决定哪些类能够被IoC容器初始化。

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                 .............
                 .............
}
代码语言:javascript
复制
public interface DeferredImportSelector extends ImportSelector {
                 .............
                 .............
}
代码语言:javascript
复制
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

简单使用ImportSelector的例子

创建两个类Codeone.classCodetwo.class

代码语言:javascript
复制
public class Codeone {
}

public class Codetwo {
}

创建ImportSelector的实现类ImportSelectorImpl.class

代码语言:javascript
复制
public class ImportSelectorImpl implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Codeone.class.getName(),Codetwo.class.getName()};
    }
}

自定义注解EnableAutoImport

代码语言:javascript
复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(ImportSelectorImpl.class)
public @interface EnableAutoImport {
}

在启动类上添加注解并进行测试

代码语言:javascript
复制
@SpringBootApplication
@EnableAutoImport
public class ElmSpringApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(ElmSpringApplication.class, args);
        Codeone codeone=run.getBean(Codeone.class);
    }

}

也可以在ImportSelector的实现类中把Codeone.classCodetwo.class变为xxxConfiguration.class,就是把具体的类变为Configuration类,实现批量导入

自动装配的原理分析

根据上面的分析我们已经知道了是ImportSelector通过导入Configuration类从而完成Bean的装载过程,那么这些Configuration是从哪里获取的呢?是怎样获取的呢?这是下面我们要分析的问题,上文中我们提到了AutoConfigurationImportSelector类,当时我们只分析了selectImports方法的作用,没分析具体的代码,而要想知道以上的两个问题的答案就要继续分析其代码,我把代码贴在下面:

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                               .......
                               .......
                               .......
        @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
                               .......
                               .......
                               .......
}

我们可以看到以上selectImports方法中getAutoConfigurationEntry方法,这个方法也是在该类中,下面我们找到getAutoConfigurationEntry方法的具体实现

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                               .......
                               .......
                               .......
       protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
                //检测自动装配是否开启 可在yml中通过spring.boot.enableautoconfiguration=true开启,默认开启
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
                //获取@EnableAutoConfiguration注解中exclude和excludeName
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
                //获得所有自动装配的配置类,后面会继续分析
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
                //去除重复的配置项,防止多个项目引入同样的配置类
		configurations = removeDuplicates(configurations);
                //根据@EnableAutoConfiguration注解中exclude等属性,把不需要自动装配的配置类移除
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
                //检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常
		checkExcludedClasses(configurations, exclusions);
                //从自动配置类集合中取出被排除的类
		configurations.removeAll(exclusions);
                //检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件
		configurations = getConfigurationClassFilter().filter(configurations);
                //广播事件,所有监听 AutoConfigurationImportEvent 事件的都会做出相关的反应
		fireAutoConfigurationImportEvents(configurations, exclusions);
                //创建自动配置的对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}
                               .......
                               .......
                               .......
}

以上源码都进行了注释,我们只看其中的核心,也就是getCandidateConfigurations这个方法,他是获得配置类最核心的方法,下面我们看他的代码

代码语言:javascript
复制
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                               .......
                               .......
                               .......
        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;
	}
                               .......
                               .......
                               .......
}

通过上面的代码,我们看到代码中用了SpringFactoriesLoader,它是Spring内部提供的一种约定俗成的加载方式,类似于JAVA中的SPI,简单来说,他会扫描classpath下的META-INF/spring.factories文件,spring.factories文件中的数据以key=value的形式存储,而SpringFactoriesLoader.loadFactoryNames会根据key得到对应的value值,因此在这个场景中key对应为EnableAutoConfiguration,value是多个配置类,也就是getCandidateConfigurations的返回值,我们通过debug获取它的返回值,如下图所示:

图片[1]-SpringBoot自动装配的原理-北冥博客
图片[1]-SpringBoot自动装配的原理-北冥博客

打开RabbitAutoConfiguration,代码如下

代码语言:javascript
复制
@AutoConfiguration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {
                      ....................
                      ....................
                      ....................
}

除了基本的@Configuration注解,还有一个@ConditionalOnClass注解,这个条件控制机制在这里的用途是,判断classpath下是否存在着RabbitTemplateChannel这两个类,如果是,则把当前配置类注册到IoC容器中。另外,@EnableConfigurationProperties是属性配置,也就是说我们可以按照约定在application.yml中配置RabbitMQ的参数,而这些配置会加载到RabbitProperties中。

@Conditional条件装配

@Conditional是Spring提供的一个核心注解,这个注解的作用是提供自动装配的条件约束,一般与@Configuration@Bean配合使用

简单来说,Spring在解析@Configuration配置类时,如果该配置类增加了@Conditional注解,那么就会根据该注解配置的条件来决定是否要实现Bean的装配

@Conditional的注解类声明代码如下,该注解可以接收一个Condition类型数组

代码语言:javascript
复制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

Condition是一个函数式接口,提供了matchs方法,它主要提供一个匹配规则,返回true则装载Bean反之不装载

代码语言:javascript
复制
@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

基于@Conditional实现一个条件装配案例

自定义一个Condition,规则是如果当前是windows系统则返回true,反之则返回false

代码语言:javascript
复制
public class ConditionImpl implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os=context.getEnvironment().getProperty("os.name");
        if(os.contains("Windows")){
            return true;
        }
        return false;
    }
}

创建一个配置类,装载一个自定义类Codeone,在Codeone的Bean声明方法中增加@Conditional(ConditionImpl.class)注解,如果ConditionImpl.class中的matchs方法返回true则Codeone装载到Spring IoC容器中,反之则不装载

代码语言:javascript
复制
@Configurable
public class ConditionConfig {

    @Bean
    @Conditional(ConditionImpl.class)
    public Codeone codeone(){
        return new Codeone();
    }
}

运行测试,输出结果

代码语言:javascript
复制
@SpringBootTest
class ElmSpringApplicationTests {

    @Test
    void contextLoads() {
        AnnotationConfigApplicationContext annotationConfigApplicationContext=new AnnotationConfigApplicationContext(ConditionConfig.class);
        Codeone codeone=annotationConfigApplicationContext.getBean(Codeone.class);
        System.out.println(codeone);
    }

}
图片[2]-SpringBoot自动装配的原理-北冥博客
图片[2]-SpringBoot自动装配的原理-北冥博客

SpringBoot中的@Conditional

在SpringBoot中,针对@Conditional做了扩展,提供了更简单的使用形式,扩展注解如下:

  • @ConditionOnBean/@ConditionOnMissingBean:容器中存在某个类或者不存在某个Bean时进行Bean的装载
  • @ConditionOnClass/@ConditionOnMissingClass:classpath下存在指定类或者不存在指定类时进行Bean的装载
  • @ConditionOnCloudPlatform:只用运行在指定的云平台上才加载指定的Bean
  • @ConditionOnExpression:基于SpEI表达式的条件判断
  • @ConditionOnJava:只用运行指定版本的Java才会加载Bean
  • @ConditionOnJndi:只有指定的资源通过JNDI加载后才加载Bean
  • @ConditionOnWebApplication/@ConditionOnNotWebApplication:如果是Web应用或者不是Web应用,才加载指定的Bean
  • @ConditionOnProperty:系统中指定的对应的属性是否有对应的值
  • @ConditionOnResource:classpath中是否存在要加载的Bean依赖的资源
  • @ConditionOnSingleCandidate:只有在确定了给定Bean类的单个候选项时才会加载Bean

spring-autoconfigure-metadata

它的作用和@Conditional是一样的,只是将这些条件配置放进了配置文件里

代码语言:javascript
复制
org.springframework.boot.autoconfigure.AutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitMessagingTemplate
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=com.rabbitmq.client.Channel,org.springframework.amqp.rabbit.core.RabbitTemplate
org.springframework.boot.autoconfigure.amqp.RabbitStreamConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitStreamConfiguration.ConditionalOnClass=org.springframework.rabbit.stream.config.StreamRabbitListenerContainerFactory
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration=
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration.ConditionalOnClass=org.aspectj.weaver.Advice
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration=
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnBean=javax.sql.DataSource
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnClass=org.springframework.jdbc.datasource.init.DatabasePopulator
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnBean=org.springframework.batch.core.launch.JobLauncher
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.batch.core.launch.JobLauncher
org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration=

这种形式也是约定大于配置的体现,通过这种配置实现条件过滤必须要遵循两个条件:

  • 配置文件的路径和名称必须是META-INF\spring-autoconfigure-metadata.properties
  • 配置文件中key的配置格式:自动配置类的类全路径名.条件=值

这种配置方式有效的降低了SpringBoot的启动时间,减少配置类的加载数量

温馨提示:本文最后更新于2023-03-14,若文件或内容有错误或已失效,请在下方留言

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023年03月14日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 自动装配的原理
    • 自动装配的实现
      • EnableAutoConfiguration
        • AutoConfigurationImportSelector
          • 简单使用ImportSelector的例子
            • 自动装配的原理分析
              • @Conditional条件装配
                • 基于@Conditional实现一个条件装配案例
                • SpringBoot中的@Conditional
              • spring-autoconfigure-metadata
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档