1. 程式人生 > >AnnotationConfigApplicationContext的例項化過程

AnnotationConfigApplicationContext的例項化過程

1,類繼承結構

理解AnnotationConfigApplicationContext的例項化過程之前先看一下它的類繼承結構:
這裡寫圖片描述
主要涉及到的類或介面有以下幾個:

  1. GenericApplicationContext——通用應用上下文,內部持有一個DefaultListableBeanFactory例項,這個類實現了BeanDefinitionRegistry介面,可以在它身上使用任意的bean definition讀取器。典型的使用案例是:通過BeanFactoryRegistry介面註冊bean definitions,然後呼叫refresh()方法來初始化那些帶有應用上下文語義(org.springframework.context.ApplicationContextAware
    )的bean,自動探測org.springframework.beans.factory.config.BeanFactoryPostProcessor等。關於這兩個介面,在介紹bean的生命週期時進行詳細講解。
  2. BeanDefinitionRegistry——用於持有像RootBeanDefinitionChildBeanDefinition例項的bean definitions的登錄檔介面。DefaultListableBeanFactory實現了這個介面,因此可以通過相應的方法向beanFactory裡面註冊bean。GenericApplicationContext內建一個DefaultListableBeanFactory
    例項,它對這個介面的實現實際上是通過呼叫這個例項的相應方法實現的
  3. AbstractApplicationContext——ApplicationContext介面的抽象實現,沒有強制規定配置的儲存型別,僅僅實現了通用的上下文功能。這個實現用到了模板方法設計模式,需要具體的子類來實現其抽象方法。自動通過registerBeanPostProcessors()方法註冊BeanFactoryPostProcessor, BeanPostProcessorApplicationListener的例項用來探測bean factory裡的特殊bean——對比1分析
  4. AnnotationConfigRegistry
    ——註解配置登錄檔。用於註解配置應用上下文的通用介面,擁有一個註冊配置類和掃描配置類的方法。

2,構造方法

/**
 * Create a new AnnotationConfigApplicationContext that needs to be populated
 * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
 */
public AnnotationConfigApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

/**
 * Create a new AnnotationConfigApplicationContext with the given   
 * DefaultListableBeanFactory.
 * @param beanFactory the DefaultListableBeanFactory instance to use for this context
 */
public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
   super(beanFactory);
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

/**
 * Create a new AnnotationConfigApplicationContext, deriving bean definitions
 * from the given annotated classes and automatically refreshing the context.
 * @param annotatedClasses one or more annotated classes,
 * e.g. {@link Configuration @Configuration} classes
 */
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
   this();
   register(annotatedClasses);
   refresh();
}

/**
 * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
 * in the given packages and automatically refreshing the context.
 * @param basePackages the packages to check for annotated classes
 */
public AnnotationConfigApplicationContext(String... basePackages) {
   this();
   scan(basePackages);
   refresh();
}

第一個構造器是最基本的無引數構造器,需要通過呼叫register()方法填充註解類,並進行手動重新整理。在這個構造器裡初始化了一個讀取器和掃描器。

第二個構造器能手動指定beanFactory。

第三個構造器能手動指定註解類。

第四個通過指定包名進行自動掃描並重新整理。

由於AnnotationConfigApplicationContextGenericApplicationContext的子類,在呼叫它的構造器之前會先呼叫父類的構造器,父類構造器會例項化一個DefaultListableBeanFactory例項,這個就是基於註解配置的應用上下文的IoC容器。SpringBoot預設呼叫第一個無引數構造器。

3,SpringApplication類

首先,使用者的Application類是被@SpringBootApplication註解的。有必要對@SpringBootApplication進行了解。

@SpringBootApplication可以認為是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan三個註解的組合。下面進行詳細分析。

3.1,@SpringBootConfiguration

@SpringBootConfiguration只是對@Configuration做了一下包裝,說明編寫的類是一個配置類。

3.2,@EnableAutoConfiguration

@EnableAutoConfiguration註解使能了自動配置功能。這個註解由@Import(EnableAutoConfigurationImportSelector.class)註解。在目前的版本中EnableAutoConfigurationImportSelector被廢棄了,反而用了它的基類AutoConfigurationImportSelector

AutoConfigurationImportSelector用於處理@EnableAutoConfiguration註解—未完待續!!!

3.3,@ComponentScan

開啟元件掃描,和xml配置的<context:component-scan>效果一樣

3.4,SpringApplication的執行過程

SpringApplication的靜態run()方法通過主類構造一個新的SpringApplication例項,然後執行真正的run()方法。

SpringApplication類的初始化

SpringApplication的構造器中呼叫了一個initialize()方法,這個方法的程式碼如下:

private void initialize(Object[] sources) {
  if (sources != null && sources.length > 0) {
    this.sources.addAll(Arrays.asList(sources));
  }
  //推斷是否是web環境,如果是會專門針對web上下文進行設定
  this.webEnvironment = deduceWebEnvironment();
  //設定初始化器
  setInitializers((Collection) getSpringFactoriesInstances(
    ApplicationContextInitializer.class));
  //設定監聽器
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  this.mainApplicationClass = deduceMainApplicationClass();
}

初始化方法主要做了三件事情:

  1. 判斷上下文環境,web還是非web?後面會根據上下文環境進行鍼對性的設定

  2. 設定初始化器,這裡只獲取ApplicationContextInitializer(應用上下文初始化器)。這裡主要執行的程式碼是獲取初始化器對應的工廠例項,程式碼如下:

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     // Use names and ensure unique to protect against duplicates
     Set<String> names = new LinkedHashSet<String>(
       SpringFactoriesLoader.loadFactoryNames(type, classLoader));
     List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                                                        classLoader, args, names);
     AnnotationAwareOrderComparator.sort(instances);
     return instances;
    }

    這裡用到了SpringFactoriesLoader類。這個類主要用於裝載工廠,工廠資源位於META-INF/spring.factories檔案中,這個檔案可能在類路徑的多個jar檔案中。然後呼叫createSpringFactoriesInstances()方法對工廠進行例項化並根據註解進行排序。

  3. 設定監聽器,具體過程同2.

run()方法的執行

run()的程式碼如下:

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  FailureAnalyzers analyzers = null;
  configureHeadlessProperty();
  // 獲取啟動監聽器
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting();
  try {
    // 構建應用引數 
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
    // 準備應用環境
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                        applicationArguments);
    Banner printedBanner = printBanner(environment);
    // 建立ApplicationContext
    context = createApplicationContext();
    analyzers = new FailureAnalyzers(context);
    // 準備context
    prepareContext(context, environment, listeners, applicationArguments, 
                   printedBanner);
    // 重新整理context-----!!!很重要
    refreshContext(context);
    // 後置工作
    afterRefresh(context, applicationArguments);
    listeners.finished(context, null);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
    }
    return context;
  }
  catch (Throwable ex) {
    handleRunFailure(context, listeners, analyzers, ex);
    throw new IllegalStateException(ex);
  }
}
  1. 獲取啟動監聽器。具體實現與獲取初始化器的方式一樣。實際上獲取的是一個EventPublishingRunListener物件,這個類能通過一個SimpleApplicationEventMulticaster物件廣播事件,用到了Executor多執行緒非同步執行框架,複習!!!!

  2. 構建應用引數。儲存命令列引數。

  3. 準備應用環境。prepareEnvironment()的程式碼如下:

    private ConfigurableEnvironment prepareEnvironment(
     SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
     // Create and configure the environment
     ConfigurableEnvironment environment = getOrCreateEnvironment();
     // 配置環境
     configureEnvironment(environment, applicationArguments.getSourceArgs());
     listeners.environmentPrepared(environment);
     if (!this.webEnvironment) {
       environment = new EnvironmentConverter(getClassLoader())
         .convertToStandardEnvironmentIfNecessary(environment);
     }
     return environment;
    }

    在構造environment的時候,會根據前面推斷的web環境建立相應的Environment物件,如果是web環境,則建立StandardServletEnvironment物件,否則建立StandardEnvironment物件,StandardEnvironment的構造器會先對屬性源進行定製(將系統屬性和系統環境新增到一個MutablePropertySources維護的list中)。這個物件的主要工作包括property分析、profile相關操作、獲取系統屬性和系統環境等。參考相關類結構。

    接著是配置環境,configureEnvironment()的實現如下:

    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
     // 配置屬性源
     configurePropertySources(environment, args);
     // 配置profile
     configureProfiles(environment, args);
    }

    SpringApplication#configurePropertySources()的實現如下:

    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
     MutablePropertySources sources = environment.getPropertySources();
     // 新增預設屬性
     if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
       sources.addLast(new MapPropertySource("defaultProperties",this.defaultProperties));
     }
     // 新增命令列屬性
     if (this.addCommandLineProperties && args.length > 0) {
       String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
       if (sources.contains(name)) {
         PropertySource<?> source = sources.get(name);
         CompositePropertySource composite = new CompositePropertySource(name);
         composite.addPropertySource(new SimpleCommandLinePropertySource(
           name + "-" + args.hashCode(), args));
         composite.addPropertySource(source);
         sources.replace(name, composite);
       }
       else {
         sources.addFirst(new SimpleCommandLinePropertySource(args));
       }
     }
    }

    在現在這個階段的配置主要是新增預設屬性(defaultProperties)和命令列屬性。

    SpringApplication#configureProfiles()的實現如下:

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
     // 獲取啟用的profiles
     environment.getActiveProfiles(); // ensure they are initialized
     // But these ones should go first (last wins in a property key clash)
     Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
     profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
     environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }

    AbstractEnvironment#getActiveProfiles()解析spring.profile.active屬性,新增到activeProfiles這個集合中。後面的程式碼是為了實現“last win”策略。

    此時應用環境也準備完畢,它包括系統屬性、系統環境和啟用的profile。之後,通過釋出環境準備完畢事件,這裡,啟動監聽器會繼續對environment進行配置,參考啟動監聽器!!

  4. 建立應用上下文。SpringApplication#createApplicationContext()方法通過反射構造應用上下文。AnnotationConfigApplicationContext的預設構造器會構造一個AnnotationBeanDefinitionReader物件和ClassPathBeanDefinitionScanner物件,在構造AnnotationBeanDefinitionReader物件的過程中會向bean factory添加註解處理器和事件監聽處理器BeanDefinition,為後續的配置解析作準備。這樣,就能通過JavaConfig構建BeanDefinition並實現自動掃描。AnnotationConfigApplicationContext的父類GenericApplicationContext的預設構造器會構造一個DefaultListableBeanFactory物件,這樣應用上下文持有一個bean factory的引用,大部分應用只需與應用上下文提供的介面打交道就是因為它對bean factory進行了一層封裝。至此,一個Spring容器已經構造出來了,但是目前這個容器還什麼都沒有,需要根據使用者的配置檔案進行配置才能按照使用者邏輯進行工作。

  5. 準備應用上下文。SpringApplication#prepareContext()的實現如下:

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners
                listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
     // 使context持有應用環境的引用,同時將應用環境的引用賦給reader和scanner
     context.setEnvironment(environment);
     // 實現應用上下文的後置處理
     postProcessApplicationContext(context);
     // 應用初始化器--新增監聽器、logger、warnning等元件
     applyInitializers(context);
     listeners.contextPrepared(context);
     if (this.logStartupInfo) {
       logStartupInfo(context.getParent() == null);
       logStartupProfileInfo(context);
     }
    
     // 新增啟動相關的bean
     context.getBeanFactory().registerSingleton("springApplicationArguments",
                                                applicationArguments);
     if (printedBanner != null) {
       context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
     }
    
     // Load the sources
     Set<Object> sources = getSources();
     Assert.notEmpty(sources, "Sources must not be empty");
     // 將source bean裝載到應用上下文
     load(context, sources.toArray(new Object[sources.size()]));
     // 日誌配置---參考具體實現
     listeners.contextLoaded(context);
    }

    上下文後置處理SpringApplication#postProcessApplicationContext()程式碼如下:

    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
     if (this.beanNameGenerator != null) {
       context.getBeanFactory().registerSingleton(
         AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
     }
     if (this.resourceLoader != null) {
       if (context instanceof GenericApplicationContext) {
         ((GenericApplicationContext) context)
                        .setResourceLoader(this.resourceLoader);
       }
       if (context instanceof DefaultResourceLoader) {
         ((DefaultResourceLoader) context)
                        .setClassLoader(this.resourceLoader.getClassLoader());
       }
     }
    }

    這段後置處理程式碼主要是註冊BeanNameGenerator型別的bean並設定應用上下文的資源載入器和類載入器。

    SpringApplication#load()方法會例項化一個BeanDefinitionLoader物件loader, 然後呼叫它的load()方法裝載bean,這裡完成的工作就是將使用者編寫的主類作為bean進行註冊。

  6. 重新整理應用上下文。重新整理上下文實際上是呼叫AbstractApplicationContext#refresh()方法,這個方法很複雜,執行完畢後整個應用上下文也就完成了配置。下一節對這個方法進行詳細講解。

  7. 後置工作。後置工作等分析完了步驟6之後再寫。