重读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);
   
   return candidates;

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

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

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

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

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

springboot学习系列