深入理解Spring(一)、扫描过程

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

前言

深入理解Spring源码分为7小节,本小节为Spring源码第一小节,各个小节目录如下。

1. 扫描过程

  1. bean创建过程
  2. 容器扩展
  3. AOP源码分析
  4. 事务源码分析
  5. Spring JDBC源码分析
  6. Spring常用设计模式

扫描过程

扫描过程这个叫法可能不太合适,但是在源码中,有个很重要的方法叫scan,他是第一步要执行的,或者也可以叫做信息收集过程,因为在这个过程中,主要做的就是从xml配置文件或者类路径中收集Spring所要管理的对象。

scan是AnnotationConfigApplicationContext中才有的,xml方式可能以后也不太常见了,所以本章以及以后的文章中,都是使用AnnotationConfigApplicationContext,不论是使用哪种方式,第一步要做的都是解析、收集必要的信息。

比如你写的@Configuration类,需要让Spring管理,那么Spring启动过程第一步就是从若干个类中,找到需要被管理的类,把这些类,封装在一个BeanDefinition对象,这个对象,就是我们俗称的bean定义,比如WebConfig类上标有@Configuration,那么这个类的信息会被封装成BeanDefinition,这里面包含这个类是不是懒加载、初始化方法等信息。

BeanDefinition是个接口,他有三个实现,RootBeanDefinition、ChildBeanDefinition、GenericBeanDefinition。

那这些信息一定会有一个集合保存着,而这个集合所在的类,也一定也很重要。

这个类就是DefaultListableBeanFactory,可以说是一个核心类,其中有个字段beanDefinitionMap,就保存这一堆BeanDefinition,那么就需要一个方法,来对这个集合增加、删除,分别叫registerBeanDefinition,removeBeanDefinition。

所以第一部分,其实就是围绕这registerBeanDefinition展开,而我们需要知道的是Spring怎么找到带有@Configuration、@Component等注解的类的,以及如何封装成BeanDefinition,并调用registerBeanDefinition进行保存的。

DefaultListableBeanFactory类中还有其他重要字段,在其他章节介绍。

ClassPathBeanDefinitionScanner

扫描哪些类中具有这些注解的工作也是一个不小的任务,所以,Spring单独使用ClassPathBeanDefinitionScanner进行处理,首先看下他类上的注释。

 A bean definition scanner that detects bean candidates on the classpath,
 registering corresponding bean definitions with a given registry ({@code BeanFactory}
 

 * {@link org.springframework.stereotype.Component @Component},
 * {@link org.springframework.stereotype.Repository @Repository},
 * {@link org.springframework.stereotype.Service @Service}, or
 * {@link org.springframework.stereotype.Controller @Controller} stereotype.

意思就是bean定义扫描器,在类路径上检测是否有以下注解,如果存在,就在给定的BeanFactory中注册相应的BeanDefinition,这里就又牵扯出BeanFactory,他是个很重要的接口,DefaultListableBeanFactory就实现了他,这个接口定义了获取bean以及bean的各种属性。

直接看ClassPathBeanDefinitionScanner#scan方法,非常简单。

public int scan(String... basePackages) {
    //开始时候的bean 定义的数量
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   
   //要开始做实事了,进行扫描
   doScan(basePackages);
   //添加一些后置处理器,后面说
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }
   //返回新增了多少个
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

Spring中所有do开头的方法,都是做实事的方法,所以必须看doScan().

doScan

Spring会扫描目标包和他的子包,doScan方法接受多个包名,通常这里只传递项目的根路径,这个方法其中最重要的还是findCandidateComponents,他会找到符合这个包路径的所有类,从中挑选出需要被管理的。

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   /**
    * 保存扫描到的BeanDefinition
    */

   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   /**
    * 遍历目标包名
    */

   for (String basePackage : basePackages) {
      /**
       * 根据包名找到带有指定注解的类的所有BeanDefinition对象
       */

      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      /**
       * 遍历BeanDefinition
       */

      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);
         }
         /**
          * 根据其他注解设置BeanDefinition对应字段值
          */

         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         /**
          * 验证这个bean,比如是否存在
          */

         if (checkCandidate(beanName, candidate)) {
            /**
             * 使用BeanDefinitionHolder包装BeanDefinition
             */

            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            /**
             * 向容器注册这个BeanDefinition
             */

            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

findCandidateComponents->scanCandidateComponents

findCandidateComponents中做实事的还是scanCandidateComponents,他首先会根据传递的包名,拼接成完整的通配符路径,比如包名是com.xxx,那么最后完整的通配路径就是classpath*:com/xxx/**/*.class,这个通配路径中也包含子包。

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
   Set<BeanDefinition> candidates = new LinkedHashSet<>();
   try {
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
            resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      /**
       * 扫描出符合指定通配符的所有class
       */

      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

      /**
       * 过滤不需要的class
       */

      for (Resource resource : resources) {
         if (traceEnabled) {
            logger.trace("Scanning " + resource);
         }
         try {
            /**
             * 真正读取的Class的Metadata信息的是SimpleMetadataReader,但是默认实现是CachingMetadataReaderFactory,只不过在他的基础
             * 上增加了缓存功能
             */

            MetadataReaderFactory metadataReaderFactory = getMetadataReaderFactory();
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            /**
             * 如果这个类标有Component注解或者他的子类,那么进行BeanDefinition注册
             */

            if (isCandidateComponent(metadataReader)) {
               ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
               sbd.setSource(resource);
               if (isCandidateComponent(sbd)) {
                  candidates.add(sbd);
               }
            } 
         } catch (FileNotFoundException ex) {

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

这里重要的还是下面这句,他会收集所有符合这个通配路径的Resource对象,Resource中包含着具体这个类的位置,如果项目中依赖其他jar文件,并且这个jar中同样有符合这个路径的类,也会收集,所以这个结果通常都比较多,但如果你指定的扫描路径越详细,这里也就越少,处理的速度也更快。

Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);

接下来就是判断这个类上存在不存在指定注解,这里是比较复杂的,因为要解析class文件判断类上是不是标有注解,普通的做法可能是使用ClassLoader加载成Class对象,之后在通过getDeclaredAnnotations之类的方法提取注解,但是在样效率不高,更高的方法是暴力按照class格式规范去解析,但是这就增加了难度,需要对class文件格式非常熟悉。

所以这部分是由ClassReader完成的,他的核心代码也不需要看,也看不懂,他就是按照class规范,前几个字节是什么意思,后几个字节是什么意思的约定,一点点的读取,可以取出访问权限、父类名、注解等信息,最终返回成MetadataReader对象,MetadataReader就包含这这个类上有哪些注解。

MetadataReaderFactory metadataReaderFactory = getMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);

而下面这段,则是判断有没有指定注解。

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
         return isConditionMatch(metadataReader);
      }
   }
   return false;
}

includeFilters字段里保存的就是一些注解列表,在默认的时候里面含有Component、ManagedBean、Named这三个,所以,类上只要标有这三个注解,就可以让Spring管理,而@Service这些注解上都又标有Component注解,简单可以理解成@Service继承@Component,所以,标有@Service也可以让Spring管理,同样我们自定义一个注解,注解上加入@Component,同样可以。

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

最后这个方法返回所有带有这些注解的类,在回到doScan,剩下的就是遍历上一步返回的结果,注册到容器里,除此之外,还有下面这段代码,首先这个条件是成立的,因为在扫描过程中,返回的是ScannedGenericBeanDefinition,而他是继承AnnotatedBeanDefinition

if (candidate instanceof AnnotatedBeanDefinition) {
   AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}

最后会进入这段代码,他的作用是提取其余辅助注解,设置信息到BeanDefinition中,比如获取Lazy注解,如果存在,对BeanDefinition设置他的value值,Lazy注解是懒加载,就是当这个类用到的时候才实例化,否在不,Spring默认都是不进行懒加载的。

还有其余注解如Primary用来设置当有众多相同的Bean中,优先使用@Primary注解的Bean。

所以下面代码,只是根据其他注解的值填充到BeanDefinition,这样BeanDefinition就完善了。

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
   AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
   if (lazy != null) {
      abd.setLazyInit(lazy.getBoolean("value"));
   }
   else if (abd.getMetadata() != metadata) {
      lazy = attributesFor(abd.getMetadata(), Lazy.class);
      if (lazy != null) {
         abd.setLazyInit(lazy.getBoolean("value"));
      }
   }
   if (metadata.isAnnotated(Primary.class.getName())) {
      abd.setPrimary(true);
   }
   AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
   if (dependsOn != null) {
      abd.setDependsOn(dependsOn.getStringArray("value"));
   }
   AnnotationAttributes role = attributesFor(metadata, Role.class);
   if (role != null) {
      abd.setRole(role.getNumber("value").intValue());
   }
   AnnotationAttributes description = attributesFor(metadata, Description.class);
   if (description != null) {
      abd.setDescription(description.getString("value"));
   }
}

注册

最后会封装成BeanDefinitionHolder,这个对象里面含有BeanDefinition,调用registerBeanDefinition添加到容器中,容器就是DefaultListableBeanFactory。

registerBeanDefinition(definitionHolder, this.registry);

到这里就结束了,这里就会调用开头我们说的registerBeanDefinition方法,位于DefaultListableBeanFactory,把BeanDefinitionHolder对象添加到beanDefinitionMap中,这个过程中还要处理别名。

系统其余BeanDefinition

除了我们自己的,Spring还有他自己的需要注册,位于scan下的这一句。

if (this.includeAnnotationConfig) {
     AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

但是在AnnotationConfigApplicationContext的构造方法执行阶段,同样也调用了这句。所以在内部也判断了如果对应的BeanDefinition不存在,才注册,这里Spring添加的BeanDefinition非常重要,比如ConfigurationClassPostProcessor,这是后续的事情了,总之这个过程就是收集所有的BeanDefinition对象,这些对象以后会进行实例化,完成依赖注入。

但是这还不算结束,还有其他Bean在这个过程中是收集不到的,比如类中还有方法标有@Bean,这需要实例化所在类才可以收集到,另外还可以通过扩展接口收集,这部分在下章说。

本章结束。

- END -


原文始发于微信公众号(十四个字节):深入理解Spring(一)、扫描过程