Spring IoC 源碼分析 (基於註解) 之 包掃描

  在上篇文章Spring IoC 源碼分析 (基於註解) 一咱們分析到,咱們經過AnnotationConfigApplicationContext類傳入一個包路徑啓動Spring以後,會首先初始化包掃描的過濾規則。那咱們今天就來看下包掃描的具體過程。
  
  仍是先看下面的代碼:
  
  AnnotationConfigApplicationContext類
  
  //該構造函數會自動掃描以給定的包及其子包下的全部類,並自動識別全部的Spring Bean,將其註冊到容器中
  
  public AnnotationConfigApplicationContext(String... basePackages) {
  
  //初始化
  
  this();
  
  //掃描包、註冊bean
  
  scan(basePackages);
  
  refresh();
  
  }
  
  上文咱們分析了this()方法,會去初始化AnnotatedBeanDefinitionReader讀取器和ClassPathBeanDefinitionScanner掃描器,並初始化掃描過濾規則。
  
  接下來咱們看一下scan(basePackages)方法:
  
  一直跟蹤下去,發現調用了ClassPathBeanDefinitionScanner類中的scan()方法
  
  //調用類路徑Bean定義掃描器入口方法
  
  public int scan(String... basePackages) {
  
  //獲取容器中已經註冊的Bean個數
  
  int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
  
  //啓動掃描器掃描給定包
  
  doScan(basePackages);
  
  // Register annotation config processors, if necessary.
  
  //註冊註解配置(Annotation config)處理器
  
  if (this.includeAnnotationConfig) {
  
  AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  
  }
  
  //返回註冊的Bean個數
  
  return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
  
  }
  
  能夠看到主要是doScan(basePackages)方法實現了掃描的邏輯,咱們繼續跟蹤進去看下
  
  //類路徑Bean定義掃描器掃描給定包及其子包
  
  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  
  Assert.notEmpty(basePackages, "At least one base package must be specified");
  
  //建立一個集合,存放掃描到Bean定義的封裝類
  
  Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  
  //遍歷掃描全部給定的包
  
  for (String basePackage : basePackages) {
  
  //調用父類ClassPathScanningCandidateComponentProvider的方法
  
  //掃描給定類路徑,獲取符合條件的Bean定義
  
  Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  
  //遍歷掃描到的Bean
  
  for (BeanDefinition candidate : candidates) {
  
  //獲取@Scope註解的值,即獲取Bean的做用域
  
  ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
  
  //爲Bean設置做用域
  
  candidate.setScope(scopeMetadata.getScopeName());
  
  //爲Bean生成名稱
  
  String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  
  //若是掃描到的Bean不是Spring的註解Bean,則爲Bean設置默認值,
  
  //設置Bean的自動依賴注入裝配屬性等
  
  if (candidate instanceof AbstractBeanDefinition) {
  
  postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
  
  }
  
  //若是掃描到的Bean是Spring的註解Bean,則處理其通用的Spring註解
  
  if (candidate instanceof AnnotatedBeanDefinition) {
  
  //處理註解Bean中通用的註解,在分析註解Bean定義類讀取器時已經分析過
  
  AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
  
  }
  
  //根據Bean名稱檢查指定的Bean是否須要在容器中註冊,或者在容器中衝突
  
  if (checkCandidate(beanName, candidate)) {
  
  BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  
  //根據註解中配置的做用域,爲Bean應用相應的代理模式
  
  definitionHolder =
  
  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  
  beanDefinitions.add(definitionHolder);
  
  //向容器註冊掃描到的Bean
  
  registerBeanDefinition(definitionHolder, this.registry);
  
  這一大段代碼基本上就是spring掃描識別註解,並註冊Bean到IOC容器中的代碼。
  
  在第10行有一個findCandidateComponents(basePackage)方法,這個方法裏就是具體的掃描邏輯。
  
  繼續跟蹤:
  
  ClassPathScanningCandidateComponentProvider類
  
  //掃描給定類路徑的包
  
  public Set<BeanDefinition> findCandidateComponents(String basePackage) {
  
  //spring5.0開始 索引 開啓的話生成文件META-INF/spring.components 後面加載直接從本地文件讀取(通常不建議開啓 spring.index.ignore=true)
  
  if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
  
  return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
  
  }
  
  else {
  
  return scanCandidateComponents(basePackage);
  
  這裏有一個if判斷,咱們默認走的是else裏的分支,即scanCandidateComponents(basePackage)方法。
  
  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
  
  Set<BeanDefinition> candidates = new LinkedHashSet<>();
  
  try {
  
  //補全掃描路徑,掃描全部.class文件 classpath*:com/mydemo/**/*.class
  
  String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
  
  resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  
  //定位資源
  
  Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  
  boolean traceEnabled = logger.isTraceEnabled();
  
  boolean debugEnabled = logger.isDebugEnabled();
  
  for (Resource resource : resources) {
  
  if (traceEnabled) {
  
  logger.trace("Scanning " + resource);
  
  }
  
  if (resource.isReadable()) {
  
  try {
  
  //經過ASM獲取class元數據,並封裝在MetadataReader元數據讀取器中
  
  MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
  
  //判斷該類是否符合@CompoentScan的過濾規則
  
  //過濾匹配排除excludeFilters排除過濾器(能夠沒有),包含includeFilter中的包含過濾器(至少包含一個)。
  
  if (isCandidateComponent(www.meiwanyule.cn metadataReader)) {
  
  //把元數據轉化爲 BeanDefinition
  
  ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
  
  sbd.setResource(resource);
  
  sbd.setSource(resource);
  
  //判斷是不是合格的bean定義
  
  if (isCandidateComponent(www.jintianxuesha.com sbd)) {
  
  if (debugEnabled) {
  
  logger.debug("Identified candidate component class: " + resource);
  
  }
  
  //加入到集合中
  
  candidates.add(sbd);
  
  }
  
  else {
  
  //不合格 不是頂級類、具體類
  
  if (debugEnabled) {
  
  logger.debug("Ignored because not a concrete top-level class: " + resource);
  
  }
  
  }
  
  }
  
  else {
  
  //不符@CompoentScan過濾規則
  
  if (traceEnabled) {
  
  logger.trace("Ignored because not matching any filter: " + resource);
  
  }
  
  }
  
  }
  
  catch (Throwable ex)www.fengshen157.com {
  
  throw new BeanDefinitionStoreException(
  
  "Failed to read candidate component class: " + resource, ex);
  
  }
  
  }
  
  else {
  
  if (traceEnabled) {
  
  logger.trace("Ignored because not readable: " + resource);
 
  catch (IOException ex) {
  
  throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
  
  這裏就是主要的掃描邏輯,代碼中的註釋已經說的很清楚了。
  
  主要過程:
  
  根據包路徑,掃描全部.class文件
  
  根據包路徑,生成.class對應的Resource對象
  
  經過ASM獲取class元數據,並封裝在MetadataReader元數據讀取器中
  
  判斷該類是否符合過濾規則
  
  判斷該類是否爲獨立的類、具體的類
  
  加入到集合中
  
  咱們來詳細看下過濾的方法 isCandidateComponent(metadataReader)
  
  //判斷元信息讀取器讀取的類是否符合容器定義的註解過濾規則
  
  //@CompoentScan的過濾規則支持5種 (註解、類、正則、aop、自定義)
  
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  
  //若是讀取的類的註解在排除註解過濾規則中,返回false
  
  for (TypeFilter tf : this.excludeFilters) {
  
  if (tf.match(metadataReader, getMetadataReaderFactory())) {
  
  return false;
  
  //若是讀取的類的註解在包含的註解的過濾規則中,則返回ture
  
  for (TypeFilter tf : this.includeFilters) {
  
  //判斷當前類的註解是否match規則
  
  if (tf.match(metadataReader, getMetadataReaderFactory())) {
  
  //是否有@Conditional註解,進行相關處理
  
  return isConditionMatch(metadataReader);
  
  //若是讀取的類的註解既不在排除規則,也不在包含規則中,則返回false
  
  return false;
  
  接着跟蹤 tf.match()方法
  
  AbstractTypeHierarchyTraversingFilter類
  
  public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
  
  throws IOException {
  
  // This method optimizes avoiding unnecessary creation of ClassReaders
  
  // as well as visiting over those readers.
  
  //檢查當前類的註解是否符合規律規則
  
  if (matchSelf(metadataReader)www.xingtuylgw.com) {
  
  return true;
  
  }
  
  //check 類名是否符合規則
  
  ClassMetadata metadata = metadataReader.getClassMetadata();
  
  if (matchClassName(metadata.getClassName(www.chaoyul.com))) {
  
  return true;
  
  //若是有繼承父類
  
  if (this.considerInherited) {
  
  String superClassName = metadata.getSuperClassName();
  
  if (superClassName != null) {
  
  // Optimization to avoid creating ClassReader for super class.
  
  Boolean superClassMatch = matchSuperClass(superClassName);
  
  if (superClassMatch != null) {
  
  if (superClassMatch.booleanValue(www.baiyiyulgw.com )) {
  
  return true;
  
  else {
  
  // Need to read super class to determine a match...
  
  try {
  
  if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
  
  return true;
  
  //若是有實現接口
  
  if (this.considerInterfaces) {
  
  for (String ifc : metadata.getInterfaceNames()) {
  
  // Optimization to avoid creating ClassReader for super class
  
  Boolean interfaceMatch = matchInterface(ifc);
  
  if (interfaceMatch != null) {
  
  if (interfaceMatch.booleanValue()) {
  
  return true;
  
  catch (IOException ex) {
  
  logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
  
  metadata.getClassName(www.yishenpt3.com) www.yuxinyulept.com+ "]");
  
  這裏面最主要的是 matchSelf(metadataReader) 方法
  
  AnnotationTypeFilter類
  
  protected boolean matchSelf(MetadataReader metadataReader) {
  
  //獲取註解元數據
  
  AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
  
  //check 註解及其派生註解中是否包含@Component
  
  //獲取當前類的註解 metadata.hasAnnotation @Controller
  
  //獲取當前類的註解及其派生註解 metadata.hasAnnotation @Controller包含的@Component\@Documented等等
  
  return metadata.hasAnnotation(this.annotationType.getName(www.zhuyngyule.cn)) ||
  
  (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
  
  }
  
  在這段代碼代碼中,能夠解決咱們以前的疑惑「Spring是怎麼發現@Configuration、@Controller、@Service這些註解修飾的類的?」
  
  原來@Configuration、@Controller、@Service這些註解其實都是@Component的派生註解,咱們看這些註解的代碼會發現,都有@Component註解修飾。而spring經過metadata.hasMetaAnnotation()方法獲取到這些註解包含@Component,因此均可以掃描到。以下:
  
  而後咱們再看回 scanCandidateComponents(basePackage)方法,接下來有一個 isCandidateComponent(sbd)方法,以下:
  
  //是不是獨立的類、具體的類
  
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  
  AnnotationMetadata metadata = beanDefinition.getMetadata();
  
  return (metadata.isIndependent(www.51dfyLgw.com) && (metadata.isConcrete() ||
  
  (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
  
  }
  
  這個方法的做用是,判斷該類是否爲
  
  頂層的類(沒有父類或靜態內部類)
  
  具體的類(不是抽象類或接口)
  
  至此,ClassPathBeanDefinitionScanner類中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已經結束了,即咱們的包掃描也結束了,已經把掃描到的類存入到了集合中,結下來就是解析註冊Bean的過程了。
  
  總結
  
  經過這篇文章,咱們能夠回答以前的一些問題了:
  
  Spring是怎麼發現@Bean、@Controller、@Service這些註解修飾的類的?
  
  經過 matchSelf(metadataReader)方法,判斷這些註解中是否包含@Component
  
  @CompoentScan註解是怎麼起做用的?
  
  經過 isCandidateComponent(metadataReader)方法過濾spring

相關文章
相關標籤/搜索