重读Spring系列-@SpringBootApplication注解的详细解析

Posted _微风轻起

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重读Spring系列-@SpringBootApplication注解的详细解析相关的知识,希望对你有一定的参考价值。

这一篇我们来梳理下@SpringBootApplication这个注解它注入了那些内容。

一、结构分析

1、main方法使用

​ 我们知道@SpringBootApplication是用在Main方法上面的,例如:

@SpringBootApplication
public class SpringBootSimpleDemoApplication {

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

}

​ 通过怎样,其实就能通过入参SpringBootSimpleDemoApplication.class,来获取到@SpringBootApplication

2、@SpringBootApplication结构

@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 = Configuration.class)
   boolean proxyBeanMethods() default true;

}

​ 我们进入这个方法里面,可以看到这个方法里面又引入了几个其他的注解。我们先看@ComponentScan,我们知道这个是用来自动扫描包路径的组件的,同时这里又引入了两个排除FilterTypeTypeExcludeFilterAutoConfigurationExcludeFilter

​ 再之后是SpringBootConfiguration,这个注解其实没有什么特殊的内容,主要是用来标记这个是SpringBoot配置,就是引入了@Configuration注解。

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

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

}

​ 然后是@EnableAutoConfiguration,这种就是@Enablexxx,用来自动注入的,我们之后的源码也 就是主要分析这类的逻辑。我们之后也可以自己实现一个自定义的@Enablexxx

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

}

二、关于阅读下面流程来拓展Spring的扫描组件注解

​ 这一项是我们来拓展Spring的源码

1、拓展一个与@Component同样的注入组件@BeanClass

​ 我们一般主动注入一个Bean可以是下面的样式,这里以主动注入:

@Configuration
public class MyConfiguration {

    @Bean
    public Book book()
    {
        return new Book();
    }

}

​ 当然我们也可以直接使用@Service(也就是@Component)注解,下面我们就来独立定义一个主键注解,当然不是不是想@Service本身主要是为了引入@Component

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {

   @AliasFor(annotation = Component.class)
   String value() default "";

}

​ 我们是与@Component同级别的。

1)、demo1

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface BeanClass {
}

​ 这个注解我们将其命名为@BeanClass,然后也是使用在类上面的。

再定义一个TypeFilter,如果一个类上面有这个注解,我们就返回true

public class BeanClassTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        if (metadataReader.getAnnotationMetadata().isAnnotated(BeanClass.class.getName()))
        {
            return true;
        }
        return false;
    }
}

然后再使用@ComponentScan引入BeanClassTypeFilter,并指定扫描的包名。

@Configuration
@ComponentScan(basePackages = "com.fev.springboot.simple.demo",includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = BeanClassTypeFilter.class)})
public class MyConfiguration {

}

​ 最后启动SpringBoot项目测试,就能获取到了,感觉这个demo其实可以放在上一篇文章。

在这里插入图片描述

​ 这里我们启动测试类,就能注入Book了。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringBootSimpleDemoApplicationTests {

   @Autowired
   private ApplicationContext applicationContext;

   @Test
   public void test1()
   {
      Book contextBean = applicationContext.getBean(Book.class);
      System.out.println("............" + contextBean);
   }

}
............com.fev.springboot.simple.demo.Book@33eb6758

​ 当然这个目前可能不知道为什么,但看到后面源码部分我们就知道了。

三、@ComponentScan在@SpringBootApplication中的源码分析

​ 我们在上一篇是有分析过@ComponentScan的整体逻辑的,其的解析是在ConfigurationClassPostProcessor类中的。这里我们直接去doProcessConfigurationClass方法。

1、doProcessConfigurationClass (ConfigurationClassPostProcessor)

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
		...........
   // Process any @ComponentScan annotations
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         // The config class is annotated with @ComponentScan -> perform the scan immediately
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
         // Check the set of scanned definitions for any further config classes and parse recursively if needed
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }
	..........
   // No superclass -> processing is complete
   return null;
}

在这里插入图片描述

​ 首先这里是我们会扫描到这个注解的信息,包括两个排除FilterexcludeFilters

​ 然后便是this.conditionEvaluator.shouldSkip,这里目前是会false,这个ConditionEvaluator我们本篇之后会具体讲。再就是this.componentScanParser.parse解析获取这个@ComponentScan要扫描的包下面的组件了。当前componentScanParserComponentScanAnnotationParser

2、this.componentScanParser.parse (ComponentScanAnnotationParser)

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
   ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
         componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
		.......
   for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addIncludeFilter(typeFilter);
      }
   }
   for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addExcludeFilter(typeFilter);
      }
   }
		.........
   Set<String> basePackages = new LinkedHashSet<>();
   String[] basePackagesArray = componentScan.getStringArray("basePackages");
   for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
   }
   for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
   }
   if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
   }

   scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
      @Override
      protected boolean matchClassName(String className) {
         return declaringClass.equals(className);
      }
   });
   return scanner.doScan(StringUtils.toStringArray(basePackages));
}

​ 上面这个就是构建ClassPathBeanDefinitionScanner scanner,并且讲@ComponentSans的注入信息添加到scanner中,例如IncludeFilterExcludeFilter这些。然后就是一个扫描包的路径问题,如果你有指定要扫描的包名basePackages,其就会是指定的包名,如果没有指定basePackages.isEmpty(),其就会是declaringClass也就是被@ComponentScan注解的类的包名。之后就是具体的scanner.doScan(StringUtils.toStringArray(basePackages))了。

​ 同时这里我们可以注意到这里还另外加了一个ExcludeFilterscanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false)

3、scanner.doScan (ClassPathBeanDefinitionScanner)

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}	

这里首先是通过findCandidateComponents获取到这个包下面所有的.classResource文件。

1)、findCandidateComponents (重要)

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		......
      for (Resource resource : resources) {
         if (resource.isReadable()) {
            try {
               MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
               if (isCandidateComponent(metadataReader)) {
                  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                  sbd.setResource(resource);
                  sbd.setSource(resource);
                  if (isCandidateComponent(sbd)) {
                     candidates.add(sbd);
                  }
                  .........
               }
               ....
            }
            catch (Throwable ex) {
               throw new BeanDefinitionStoreException(
                     "Failed to read candidate component class: " + resource, ex);
            }
         }
    		..........
      }
   }
   catch (IOException ex) {
      throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
   }

以上是关于重读Spring系列-@SpringBootApplication注解的详细解析的主要内容,如果未能解决你的问题,请参考以下文章

重读Spring系列-一些配置注解的使用与源码解析

重读Spring系列-一些配置注解的使用与源码解析

重读Spring系列-Conditional注入Bean的判断

重读Spring系列-Conditional注入Bean的判断

学习底层原理系列重读spring源码1-建立基本的认知模型

springboot学习系列