springboot自动装配的原理

114 阅读4分钟


springboot的出现可以说是广大java开发者们的福音,它极大的简化了配置,把我们从繁重的项目环境搭建、各种配置文件编写中解脱出来,让我们更专注于自己的业务逻辑。基于springboot项目,我们只需要把自己需要的组件通过maven pom的方式引入就行了,springboot会自动帮我们完成IOC注入。

但是我们都知道springboot默认的扫描路径都是以application启动主类作为根路径扫描的,所以只会帮我们把当前项目的主路径及其子包下的bean扫描出来(当然可以通过@ComponentScan去配置),所以一直很好奇我们引入的其他模块到底是怎么自动注入进来的,下面我们就来看看究竟是咋回事。


先让我们来看看application主类里都有啥:

@SpringBootApplication
public class Application {
    public static void main(String[] args){
        SpringApplication.run(Application.class);
    }
}

其实关键就在这个@SpringBootApplication注解里面,打开看一下:
@SpringBootApplication=@SpringBootConfiguration(里面就是@Configuration注解)+@EnableAutoConfiguration(重要)+@ComponentScan(指定扫描路径用的)


其实@SpringBootApplication就是上面这三个注解的集合,第一个和第三个我们都知道是干啥用的,现在就来看看第二个@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 {};
}

大家看到没,注意看第6行,原来是用了@Import注解,可能又有人有疑惑了,这个@Import又是干啥的呢?额......,这里简单说下大家可以去自行在了解下。@Import注解就是用来导入一个类到spring IOC中,跟我们平常用的@Component注解将类声明为一个bean是类似的。


好,看到这里我们知道此时会去初始化EnableAutoConfiguration.class这个类,让我们再进去这个类里面看看:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;

    public AutoConfigurationImportSelector() {
    }

    /**
     * 返回需要注入到IOC容器里的全类名数组
     */
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            
            /**
             * 就是调用下面的这个方法
             */
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

    protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            
            /**
             * 这一步会去扫描所有类路径下的META-INF/spring.factories文件,将其里面的configuration类捞出来
             */
            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.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }


因为这个类比较长嘛,我们就简单描述下这个类的执行过程:

  1. AutoConfigurationImportSelector类实现了ImportSelector这个接口,重写了它的selectImports()方法,这个方法会返回一个全类名的数组,里面的类会被加载到IOC中
  2. 重写的selectImports()方法里面会调用自己的内部getAutoConfigurationEntry()方法。这个方法会去扫描所有类路径下的META-INF/spring.factories文件,将其里面的configuration类捞出来
  3. 每个META-INF/spring.factories文件内配置的configuration类最终被加载到IOC中

其实看到这里,我们大概就能明白了。其实我们开发中引入的那些starter包里面几乎都会含有META-INF/spring.factories这样一个文件,像我们常用的mybatis-spring-boot-starter,它里面的spring.factories配置文件是这样的:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

然后就会去加载MybatisAutoConfiguration这个类到IOC中,同时这个类里面又声明了其他的bean,例如这里又初始化了sqlSessionFactory加入到IOC中

   	@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        this.applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }


@ConditionalOnMissingBean注解的意思就是当bean容器中不存在该类的时候就将其初始化放入bean容器中,跟其类似的还有很多个注解,例如:

  • @ConditionalOnWebApplication:仅在web应用下生效。
  • @ConditionalOnClass:需要指定的类(依赖)存在才生效。
  • @ConditionalOnProperty:主配置文件中存在指定的属性才生效。


所以就通过这一系列的Conditional注解又把这个starter里面的其他bean完成了实例化放入到了IOC中,这样就能直接在我们的主项目中引用了。

最后我们再来总结下,springboot项目启动的时候会去扫描每个类路径下的META-INF/spring.factories文件,这个文件里面会对应一个AutoConfiguration类,然后会对这个类进行实例化,同时这个AutoConfiguration类里面会通过@ConditionalOn系列注解和@Bean注解搭配完成一系列功能类的初始化到IOC容器中。即完成了通过maven pom引入就完成了自动装配的功能。