Debugger技巧和Spring Boot註解工做過程

調試方法以及代碼分析

以前調試源碼的時候,老是在自認爲是關鍵點的地方打上斷點,而後就一步一步的調試到斷點處。可是這種方式是錯誤並且低效的,由於全部的端點只是你想固然的認爲它會走的,不少細節若是不注意的話就容易錯過,而像Spring的這種超大工程,錯過這些細節就可能錯過了一個重要的知識點,而這個知識點就是你理解原理的重要一步。今天參考一個教學視頻,發現他的調試方式與本身的方法不同,並且人家是高效。他是查看Debugger的調用棧,之前也看到過這玩意,可是不知道這是幹嗎。經過Debugger調用棧,能快速定位系統跑到你打的斷點的地方調用的每個方法。下面與Idea爲例。java

輸入圖片說明

後置處理器web

輸入圖片說明

執行到 ConfigurationClassPostProcessor.processConfigBeanDefinitions() 中,須要執行ConfigurationClassParser.parse(class),其中這個class就是你的主類spring

//聲明parse對象,
ConfigurationClassParser parser = new ConfigurationClassParser(
  this.metadataReaderFactory, this.problemReporter, this.environment,
  this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//這裏獲取到的對象就是SpringBoot的啓動類
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
do {
  parser.parse(candidates);
  parser.validate();

再來看看pase方法幹了啥。websocket

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    try {
      if (bd instanceof AnnotatedBeanDefinition) {
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
    }
  }
  
  //parse方法
  //獲取到主啓動類的註解@SpringBootApplication
  protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
    processConfigurationClass(new ConfigurationClass(metadata, beanName));
  }
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  //省去 ------
  // Recursively process the configuration class and its superclass hierarchy.
 // sourceClass --> SpringBoot的主啓動類
  SourceClass sourceClass = asSourceClass(configClass);
  do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  }

doProcessConfigurationClasssocket

//處理啓動類的註解,給他們各自的註解映射處理器
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  throws IOException {

  // Recursively process any member (nested) classes first
  //返回空
  processMemberClasses(configClass, sourceClass);

  // Process any @PropertySource annotations
  for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), PropertySources.class,
    org.springframework.context.annotation.PropertySource.class)) {
    if (this.environment instanceof ConfigurableEnvironment) {
      processPropertySource(propertySource);
    }
    else {
      logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                  "]. Reason: Environment must implement ConfigurableEnvironment");
    }
  }

  // Process any @ComponentScan annotations
  //處理ComponentScan註解,將對應的包名下的類加載到Spring容器裏面
  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
      //將對應的包名下的類加載到Spring容器裏面
      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) {
        if (ConfigurationClassUtils.checkConfigurationClassCandidate(
          holder.getBeanDefinition(), this.metadataReaderFactory)) {
          parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
        }
      }
    }
  }

  // Process any @Import annotations
  //處理@Import,getImports(sourceClass)返回@Import下的類的集合
  //核心處理方法,將Import的類注入到容器裏面,後期經過發射調用該類的方法
  //getImports(sourceClass)
  //ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
 // ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
  processImports(configClass, sourceClass, getImports(sourceClass), true);

  // Process any @ImportResource annotations
  if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
    AnnotationAttributes importResource =
      AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    String[] resources = importResource.getStringArray("locations");
    Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    for (String resource : resources) {
      String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
      configClass.addImportedResource(resolvedResource, readerClass);
    }
  }

  // Process individual @Bean methods
  Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  }

  // Process default methods on interfaces
  processInterfaces(configClass, sourceClass);

  // Process superclass, if any
  if (sourceClass.getMetadata().hasSuperClass()) {
    String superclass = sourceClass.getMetadata().getSuperClassName();
    if (!superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
      this.knownSuperclasses.put(superclass, configClass);
      // Superclass found, return its annotation metadata and recurse
      return sourceClass.getSuperClass();
    }
  }

  // No superclass -> processing is complete
  return null;
}
@Import(EnableAutoConfigurationImportSelector.class)

以**@Import(EnableAutoConfigurationImportSelector.class)**爲例,實際在上面吧該有的主鍵加載到Spring容器後,會調用 ConfigurationClassParser.學習

private void processDeferredImportSelectors() {
  List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
  this.deferredImportSelectors = null;
  Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
  //循環獲取Selector類
  for (DeferredImportSelectorHolder deferredImport : deferredImports) {
    //返回的是主啓動類
    ConfigurationClass configClass = deferredImport.getConfigurationClass();
    try {
      String[] imports = 
        //deferredImport.getImportSelector() 獲取到AutoConfigurationImportSelector對象
        deferredImport.getImportSelector().selectImports(configClass.getMetadata());
     //將上一步的獲取須要加載的類所有加載到容器中
      processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
    }

List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; 這個方法裏邊,返回的是主啓動類全部的加了@Import的方法 。通過調試,他的返回結果是ui

輸入圖片說明

這樣就完成了調用 AutoConfigurationImportSelector.selectImports方法. 該方法返回的結果是系統啓動須要自動裝配的類。結果以下:this

String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
0 = "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration"
1 = "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration"
2 = "org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration"
3 = "org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration"
4 = "org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration"
5 = "org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration"
6 = "org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration"
7 = "org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration"
8 = "org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration"
9 = "com.alibaba.boot.dubbo.actuate.autoconfigure.DubboEndpointAutoConfiguration"
10 = "com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration"
11 = "com.alibaba.boot.dubbo.actuate.autoconfigure.DubboHealthIndicatorAutoConfiguration"
12 = "org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration"
13 = "org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration"
14 = "org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration"
15 = "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration"
16 = "org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration"
17 = "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration"
18 = "org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration"
19 = "org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration"
20 = "org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration"
21 = "org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration"
22 = "org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration"
23 = "org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration"

最後,經過 processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false); 將須要的類加載到容器裏面。lua

總結

這找到了以前一直糾結的問題根源在哪裏:原理都是根據反射區獲取註解,而後再利用反射獲取調用註解上面的屬性來完成對象的賦值等操做。可是以前都覺得是顯式的調用。xxx.getAnnotation().xxx()。可是一直沒到到對應的代碼,因此最近一直在糾結這一塊的問題,後來在看別人的視屏看到人家的調試方法,瞬時恍然大悟,之前調試都是按照本身的主觀意識去猜想程序的下一步會跳到哪一個類裏,這樣作的方式低效。而經過Debugger的調用棧,可以很清晰的看到程序要執行到斷點處,會通過哪些方法,再根據實際狀況在這些調用棧上面加上對應的斷點來調試,這樣不只效率能提升也能提高對源碼學習的興趣。spa

相關文章
相關標籤/搜索