likes
comments
collection
share

SpringBoot源码解析(一) @SpringBootApplication注解解析

作者站长头像
站长
· 阅读数 38

我们新建一个SpringBoot项目都会引入一个pom然后新建一个这样启动类,就能运行一个JAVA项目,而不需要像传统的SSM框架那样编写各种XML以及配置项,那么SpringBoot是如何完成我们之前那些事情的呢?

pom

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

启动类

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

新建的启动类只做了两件事

  1. 添加了一个@SpringBootApplication注解
  2. 调用了SpringApplication类的run方法

首先我们先分析@SpringBootApplication注解做了什么

SpringBoot源码解析(一) @SpringBootApplication注解解析

@SpringBootApplication注解上组合了其他注解,红色框子中的元注解不用关注,让我们看看其他几个注解各自有什么作用

一、@SpringBootConfiguration

@SpringBootConfiguration组合了@Configuration

SpringBoot源码解析(一) @SpringBootApplication注解解析

1.1 @Configuration的作用

SpringBoot源码解析(一) @SpringBootApplication注解解析

@Configuration是Spring的中的注解,表明当前类是一个配置类并且它组合@Component注解,意味也将会注册为bean

由此我们可以得出的结论的是我们新建的启动类是一个配置类,并且会作为一个Bean导入到Spring容器中

二 @EnableAutoConfiguration

SpringBoot源码解析(一) @SpringBootApplication注解解析

@EnableAutoConfiguration组合了@AutoConfigurationPackage注解和向容器中导入了AutoConfigurationImportSelector这个类,我们分开来看这两个注解的作用

2.1 @AutoConfigurationPackage

SpringBoot源码解析(一) @SpringBootApplication注解解析 @AutoConfigurationPackage 向容器中导入了AutoConfigurationPackages.Registrar这个类, 进入Registrar这个类看他究竟干了啥

2.1.1AutoConfigurationPackages.Registrar

SpringBoot源码解析(一) @SpringBootApplication注解解析

Registrar实现了ImportBeanDefinitionRegistrar接口和DeterminableImports(测试使用不用关心)接口

如果不清楚ImportBeanDefinitionRegistrar作用的可以看下面的案例

2.1.1.1 ImportBeanDefinitionRegistrar接口

这个接口配合@Import注解向容器中注册Bean的 使用案例:

定义测试Bean

public class Teacher {

    private String[] name;

    public Teacher(String...  name) {
        this.name = name;
    }

    public void hello(){
        System.out.println(name + "你好啊!");
    }
}

注册Bean信息

public class AutoConfiguredTeacherRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        //设置Bean的类别
        beanDefinition.setBeanClass(Teacher.class);
        //添加构造参数
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "张三");
        //注册bean
        registry.registerBeanDefinition("teacher", beanDefinition);
    }
}

导入注册类

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(AutoConfiguredTeacherRegistrar.class)
public @interface EnableTeacherBean {
}

测试启动类

@EnableTeacherBean
@SpringBootApplication
public class SpringBootDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class);
        context.getBean(Teacher.class).hello();
    }
}

SpringBoot源码解析(一) @SpringBootApplication注解解析

所以我们接下来就看看Registrar向容器中注册了什么

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   register(registry, new PackageImport(metadata).getPackageName());
}

入参出创建了一个PackageImport类,调取了getPackageName方法 我们看看这个类是什么作用

SpringBoot源码解析(一) @SpringBootApplication注解解析

这个类的构造方法中会通过类工具获取当元数据所在类的包路径,也就启动类的包路径

继续进入register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
   //如果容器中包含了AutoConfigurationPackages这个类
   if (registry.containsBeanDefinition(BEAN)) {
      //获取Bean信息
      BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
      ConstructorArgumentValues constructorArguments = beanDefinition
            .getConstructorArgumentValues();
      //添加并合并构造参数
      constructorArguments.addIndexedArgumentValue(0,
            addBasePackages(constructorArguments, packageNames));
   }
   else {
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      //设置Bean类型
      beanDefinition.setBeanClass(BasePackages.class);
      //添加构造参数
      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
            packageNames);
      beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      //注册Bean信息
      registry.registerBeanDefinition(BEAN, beanDefinition);
   }
}

这个方法是向容器中注入了一个BasePackages类,并且将根路径作为入参传入进去

所以@AutoConfigurationPackage的作用是向容器中注册了一个BasePackages的类,并且这个类存储了启动类的路径

虽然Spring和SpringBoot没有使用到这个类 但是可以作为三方Starter中运用到了,比如Mybatis集成SpringBoot的自动配置

AutoConfiguredMapperScannerRegistrar在注册Mapper配置信息的时候会读取启动类的包路径作为扫描路径

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    if (!AutoConfigurationPackages.has(this.beanFactory)) {
        MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
    } else {
        MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
        //获取启动类的根路径
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        //设置注解类型
        builder.addPropertyValue("annotationClass", Mapper.class);
        //设置包扫描路径
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
        //注册Bean信息
        registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
}

2.2 AutoConfigurationImportSelector

SpringBoot源码解析(一) @SpringBootApplication注解解析 熟悉Spring的都知道Aware接口是获取Spring加载中的一些信息没有实质作用,Ordered接口是用来排序的,所以只剩下了一个接口DeferredImportSelector,那么这个接口是用来干什么的?

它是 ImportSelector 的子接口,它的文档注释原文和翻译

A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional . Implementations can also extend the org.springframework.core.Ordered interface or use the org.springframework.core.annotation.Order annotation to indicate a precedence against other DeferredImportSelectors . Implementations may also provide an import group which can provide additional sorting and filtering logic across different selectors.

ImportSelector 的一种扩展,在处理完所有 @Configuration 类型的Bean之后运行。当所选导入为 @Conditional 时,这种类型的选择器特别有用。

实现类还可以扩展 Ordered 接口,或使用 @Order 注解来指示相对于其他 DeferredImportSelector 的优先级。

实现类也可以提供导入组,该导入组可以提供跨不同选择器的其他排序和筛选逻辑。

DeferredImportSelectorHandler中维护了DeferredImportSelector, DeferredImportSelectorGrouping调用getImportGroup获取所有SelectorHandler拿到待处理的DeferredImportSelector(其中就包含AutoConfigurationGroup),接着调用 process 方法,AutoConfigurationGroup 会调用我们导入的AutoConfigurationImportSelector类的getAutoConfigurationEntry方法,拿到要引入的配置集合(Entry类型的集合),最后遍历这个集合逐个解析配置类。

接下来让我们看下是怎么拿到所有自动的配置的

protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   //1.获取@EnableAutoConfiguration注解所有属性信息
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   //2.加载自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
   //3.移除重复配置
   configurations = removeDuplicates(configurations);
   //4.根据注解的属性信息获取被排除的配置类
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   //5.移除所有被排除的自动配置类
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

进入getCandidateConfigurations看看是如何拿到需要导入的自动配置类

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   //使用Spring的SPI机制获取META-INF/spring.factories下所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类型
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         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;
}

获取@EnableAutoConfiguration注解的类信息

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
   return EnableAutoConfiguration.class;
}

所以AutoConfigurationImportSelector类的作用就是获取所有META-INF/spring.factories下的自动配置类信息,之后这些自动配置类会被装配到Spring容器中,所以即便没有任何配置文件,SpringBoot的Web应用都能正常运行。

三、@ComponentScan

@ComponentScan这个注解在Spring中的作用是指定包扫描的根路径,让Spring来扫描指定包及子包下的组件,也可以不指定路径,默认扫描当前配置类所在包及子包里的所有组件,所以Springboot只会扫描启动类当前包和以下的包

SpringBoot源码解析(一) @SpringBootApplication注解解析 excludeFilters属性的作用是指定包扫描的时候根据规则指定要排除的组件,type = FilterType.CUSTOM是自定义过滤,classes 指定的类要实现TypeFilter接口,在match方法中可以获取当前扫描到的类的信息,比如注解、类名和类路径。接下来我们看看这两个类的match的作用

3.1 TypeExcludeFilter

它是一种扩展机制,能让我们向IOC容器中注册一些自定义的组件过滤器,以在包扫描的过程中过滤它们。主要用于测试

3.2AutoConfigurationExcludeFilter

它是用来判断是否是配置类和自动配置类

总结

1. 当前类被@Configuration标注,所以启动类也是一个配置类

2. 当前类被@ComponentScan标注,默认扫描当前配置类所在包及子包里的所有组件,所以Springboot只会扫描启动类当前包和以下的包

3. 当前类被@EnableAutoConfiguration标注,导入了AutoConfigurationImportSelector类,这个类会利用Spring的SPI机制扫描加载META-INF/spring.factories下所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的自动配置类

SpringBoot源码解析(一) @SpringBootApplication注解解析

转载自:https://juejin.cn/post/7201364247501930557
评论
请登录