springboot 從main啟動開始到完成征途記之一前半生

skyler發表於2018-09-29

對於java程式設計師來說,除了java本身外,spring應該是最需要具備的技能了。spring家族的給我帶來的驚喜,全家桶帶來的“芳香四溢”,讓我們受用無窮。

說明

由於springboot 從main啟動開始到啟動完成涉及太多內容,所以將此征途記分為:前半生和後半生,所謂後半生是將SpringApplication.run()中的refreshContext(context)單拿出來,即ApplicationContext.refresh()作為後半生。

目標

本文主要介紹spring boot在啟動過程中所做的事情。如下圖中spring的圖示是哪個類打出來的,日誌資訊又都是哪個類中的;如果我們想換掉這個spring圖示又改如何操作呢等等,讀後你會自有答案。本文力爭將spring boot啟動過程說全說細

springboot 從main啟動開始到完成征途記之一前半生

環境

  • java version 1.8.0_151
  • spring boot 1.5.8
  • intellij 2017.3.2

最簡單的springboot應用

App類及pom.xml

/**
 * spring boot 應用啟動入口
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) throws Exception {
        System.out.println("app"+new Date());
        // spring boot啟動入口,將完成載入jar檔案,載入配置檔案,從中找到class或class name,載入class and/or class name等
        SpringApplication.run(App.class, args);
    }
}

------------------------------------------------------
pom.xml
<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.5.8.RELEASE</version>
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
複製程式碼

如上圖程式碼,就可以執行spring boot 應用了。我們的講解也將從這裡開始,利用debug一起探索的旅程。

ready? --> Go!

征途開始

main方法中SpringApplication.run(App.class, args)是關鍵,spring boot 啟動、載入、例項化等一系列的操作都從這裡出發. SpringApplication類的註釋說明

springboot 從main啟動開始到完成征途記之一前半生
預設SpringApplication.run執行如下步驟去啟動你的應用

1. 建立一個合適的applicationContext例項
2. 註冊一個 CommandLinePropertySource 去暴露命令列引數作為spring引數。也就是 - jar 後面的引數
3. 重新整理剛才建立的applicationContext,載入所有的單例項bean
4. 觸發所有CommandLineRunner bean
複製程式碼

進入SpringApplication.run(App.class, args)方法,這個方法做了兩大事情:SpringApplication例項化和呼叫run方法。如下

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}
複製程式碼

SpringApplication例項化

在建構函式中呼叫一個很重要的方法initialize(source),引數source為main方法中的App.class。initialize方法做了四件事判斷是否為web應用、獲取initializers、獲取listeners、獲取啟動應用main方法所在的類

/**
 * 建立一個SpringApplication例項.這個例項在呼叫run方法前可以被自定義. The application context將從指定的sources載入beans.
 */
public SpringApplication(Object... sources) { 
    initialize(sources);
}
// 從指定資源(META-INF/spring.factories)載入指定的型別:ApplicationContextInitializer子類集合和ApplicationListener子類集合
private void initialize(Object[] sources) {
    this.sources.addAll(Arrays.asList(sources));
   
    // 推斷是不是web環境,javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext 兩個類有且都存在才是web環境
    this.webEnvironment = deduceWebEnvironment();
    
    //獲取META-INF/spring.factories中ApplicationContextInitializer的子類
    setInitializers((Collection) getSpringFactoriesInstances(
    		ApplicationContextInitializer.class));
    
    //獲取META-INF/spring.factories中ApplicationListener的子類
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 獲取啟動應用的main方法所在的類
    this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼

initialize方法會載入各個jar下的META-INF/spring.factories,從中獲取ApplicationContextInitializer子類集合與ApplicationListener子類集合。獲取的子類集合的作用下面會講到。載入的方式見getSpringFactoriesInstances方法,如下

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    // 通過要載入的型別和載入器瀏覽jar包們,從jar包們找到META-INF/spring.factories,從spring.factories中獲取其子類名稱集合
    Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

    // 通過反射例項化載入從檔案中獲取的類們
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

// 使用類名通過反射獲取物件例項
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
    	try {
    	    // 通過類名稱獲取類class
    	    Class<?> instanceClass = ClassUtils.forName(name, classLoader);
    	    Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
    	    // 通過類class獲取其構造類,結合反射獲取類例項
    	    T instance = (T) BeanUtils.instantiateClass(constructor, args);
    	    instances.add(instance);
    	}
    	catch (Throwable ex) {throw new IllegalArgumentException()}
    }
    return instances;
}

// 通過要載入的型別和載入器瀏覽jar包們,從jar包們找到META-INF/spring.factories,從spring.factories中獲取其子類名稱集合
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    try {
        //獲取指定的資源(String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories")的url。此處的url即為jar的路徑,如jar:file:/Users/yaoliang/.m2/repository/org/springframework/boot/spring-boot/1.5.8.RELEASE/spring-boot-1.5.8.RELEASE.jar!/META-INF/spring.factories
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
        		ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); 
            // 獲取ApplicationContextInitializer子類名稱集合
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }
    catch (IOException ex) {throw new IllegalArgumentException()}
}
複製程式碼

springboot例項化過程中的不同階段都會用這些ApplicationContextInitializer/ApplicationListener子類集合。看下springboot預設載入的ApplicationContextInitializer/ApplicationListener子類集合都有哪些,如下圖:

springboot 從main啟動開始到完成征途記之一前半生

SpringApplication.run(args)方法

SpringApplication(sources).run(args)方法做了幾乎前半生所有的事。首先它要獲取SpringApplicationRunListener事件釋出監聽器集合,獲取方式同ApplicationListener子類集合(載入META-INF/spring.factories獲取), 然後建立一個ApplicationContext物件。隨後給它的多個屬性賦值。如environment、profile、列印spring圖示、log列印級別等屬性賦值。這些賦值操作是通過類釋出事件監聽器(EventPublishingRunListener)廣播事件給真正的監聽器來完成的。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);// (1)
    listeners.starting(); // (2)
    try {
    	ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    	ConfigurableEnvironment environment = prepareEnvironment(listeners,
    			applicationArguments);// (3)
    	Banner printedBanner = printBanner(environment);// (4)
    	context = createApplicationContext();// (5)
    	analyzers = new FailureAnalyzers(context);
    	prepareContext(context, environment, listeners, applicationArguments,
    			printedBanner);// (6)
    	refreshContext(context);// (7)
    	afterRefresh(context, applicationArguments);// (8)
    	listeners.finished(context, null);// (9)
    	stopWatch.stop();
    	return context;
    }catch (Throwable ex) {
    	handleRunFailure(context, listeners, analyzers, ex);
    	throw new IllegalStateException(ex);
    }
}
複製程式碼

run方法(1)處是獲取事件釋出監聽器集合。run方法的功能實現很大部分都是通過事件釋出監聽器實現的,所以先看下事件釋出監聽器即SpringApplicationRunListener的結構

/**
 * 這是一個用於監聽SpringApplication類的main方法的監聽器。每個run方法對應一個監聽器物件
 */
public interface SpringApplicationRunListener {
    // SpringApplication.run()方法首次開始時立刻呼叫此方法。這個可以用於早期的例項化
    void starting();
    
    // 一旦environment準備好的時候,但是在ApplicationContext建立之前,呼叫此方法
    void environmentPrepared(ConfigurableEnvironment environment);
    
    // 一旦ApplicationContext建立和準備好的時候,但是在資源載入之前,呼叫此方法
    void contextPrepared(ConfigurableApplicationContext context);
    
    // 一旦the application context載入完但是它重新整理之前,呼叫此方法
    void contextLoaded(ConfigurableApplicationContext context);
    
    // 在run方法結束的時候立刻呼叫此方法
    void finished(ConfigurableApplicationContext context, Throwable exception);
}
複製程式碼

SpringApplicationRunListener及其子類EventPublishingRunListener是特殊的監聽器,叫事件釋出監聽器,它會廣播事件給所有真正已註冊的監聽器。事件釋出監聽器持有SimpleApplicationEventMulticaster廣播類屬性和SpringApplication,監聽流程為事件釋出監聽器把事件給廣播類,廣播類再通過retrieveApplicationListeners方法獲取真正處理事件的監聽器。從而實現監聽器可以監聽到事件的功能。我們以starting()為例,說明監聽流程的程式碼實現

// SpringApplicationRunListener類
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
}

// SimpleApplicationEventMulticaster類
public void multicastEvent(ApplicationEvent event) {
    multicastEvent(event, resolveDefaultEventType(event));
}
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
    // 遍歷所有ApplicationLister傳送事件
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        // 把事件給具體的監聽類
        invokeListener(listener, event);
    }
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    doInvokeListener(listener, event);
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    listener.onApplicationEvent(event);
}

starting()呼叫鏈: starting-->multicastEvent-->for迴圈invokeListener-->doInvokeListener-->listener.onApplicationEvent(event).
複製程式碼

SpringApplicationRunListener其他事件方法邏輯同starting()方法,environmentPrepared()、contextPrepared()、contextLoaded()、finished()。只是廣播事件不一樣。

下面解析SpringApplication.run()方法並結合SpringApplicationRunListener事件方法來說明每個事件方法釋出的事件及處理這個事件的監聽器們都做了什麼事

SpringApplication.run()方法(2)處

此處呼叫的正是SpringApplicationRunListener.starting()事件方法,只有LoggingApplicationListener監聽器處理這個廣播事件,它會載入有關log的jar,同時例項化logContext

SpringApplication.run()方法(3)處

此處建立一個ConfigurableEnvironment物件,後面會使用這個物件並給它的屬性賦值。Environment家族層級圖

private ConfigurableEnvironment prepareEnvironment(
    SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
    // 獲取或建立一個物件。我們是web環境,所以建立StandardServletEnvironment物件
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // 配置Environment類的PropertySources和Profiles屬性
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 呼叫事件釋出監聽器的environmentPrepared方法廣播事件給監聽器們
    listeners.environmentPrepared(environment);
    return environment;
}
複製程式碼

如上程式碼所示,做了三件事,如程式碼中註釋。其中,廣播事件給監聽器們需要重點分析。這個事件方法廣播的事件為:“environment準備好了”,監聽器們收到這個事件,開始執行各自的功能。這個事件類為ApplicationEnvironmentPreparedEvent,ConfigFileApplicationListener和LoggingApplicationListener對這個事件有重要的處理。

  1. LoggingApplicationListener進行日誌log相關的配置,設定LogContext物件的屬性(如level)、指定日誌配置檔案的路徑(通過這個logging.config屬性指定)、指定log日誌檔案的目錄(LOG_PATH)、指定log日誌檔案的名字(LOG_FILE)、指定日誌的樣式(FILE_LOG_PATTERN)等
/**
 * 通過Environment和classpath例項化有自己偏好的logging system
 */
protected void initialize(ConfigurableEnvironment environment,ClassLoader classLoader) {
    new LoggingSystemProperties(environment).apply();
    LogFile logFile = LogFile.get(environment);
    logFile.applyToSystemProperties();
    initializeEarlyLoggingLevel(environment);
    initializeSystem(environment, this.loggingSystem, logFile);
    initializeFinalLoggingLevels(environment, this.loggingSystem);
    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
    if (this.parseArgs && this.springBootLogging == null) {
    	if (isSet(environment, "debug")) {
    		this.springBootLogging = LogLevel.DEBUG;
    	}
    	if (isSet(environment, "trace")) {
    		this.springBootLogging = LogLevel.TRACE;
    	}
    }
}
複製程式碼
  1. ConfigFileApplicationListener在這個事件中做了一個重要的操作:載入配置檔案
這個重要的操作就是載入應用的配置檔案(classpath:/, classpath:/config/, file:./, file:./config/)application.(yml/yaml/properties/xml),(classpath:/, classpath:/config/, file:./, file:./config/application-{profile}.(yml/yaml/properties/xml)
,然後通過PropertySourceLoader載入檔案並解析這些配置檔案(key/value形式),並set到this.environment.propertySources中。

平時工作中我們在配置檔案指定的spring.profiles.active(預設為default),指定的spring.config.location配置檔案的路徑(預設為classpath:/,classpath:/config/,file:./,file:./config/),指定的spring.config.name配置檔案的名字(預設為application)都是在這時set到this.environment.propertySources中的

如下程式碼是尋找、載入和解析配置檔案的過程。
// 呼叫棧:事件被監聽到,執行監聽器的邏輯
ConfigFileApplicationListener.onApplicationEvent(ApplicationEvent event)
-this.onApplicationEnvironmentPreparedEvent(event)
--this.postProcessEnvironment(ConfigurableEnvironment,SpringApplication)
---this.addPropertySources(ConfigurableEnvironment,ResourceLoader)
----new Loader(environment, resourceLoader).load()

load方法分為兩大步:確定要載入的配置檔案有哪些;載入解析這些配置檔案,解析的結果放入profiles,propertiesLoader.propertySources
步驟一:確定要載入的配置檔案有哪些。springboot預設尋找classpath:/,classpath:/config/, file:./, file:./config/這些位置的application-{profile}.(yml/yaml/properties/xml檔案。把應用中實際宣告的配置檔案和預設路徑,預設名稱想匹配,匹配到的等待載入解析了
// 確定要載入的配置檔案有哪些
public void load() {
    this.propertiesLoader = new PropertySourcesLoader();
    this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles))
    while (!this.profiles.isEmpty()) {
    	Profile profile = this.profiles.poll();
    	for (String location : getSearchLocations()) {
    	    ... 略各種情況的判斷 ...
    		if (!location.endsWith("/")) {
    		    // 載入解析此路徑此名稱的配置檔案
    			load(location, null, profile);
    		}
    		else {
    			for (String name : getSearchNames()) {
    				load(location, name, profile);
    			}
    		}
    	}
     }
}
private void load(String location, String name, Profile profile) {
    for (String ext : this.propertiesLoader.getAllFileExtensions()) {
        loadIntoGroup(group, location + name + "-" + profile + "." + ext,profile);
    }
}

// 載入解析配置檔案並set到Environment.propertySources
private PropertySource<?> loadIntoGroup(String identifier, String location,
		Profile profile) {
    return doLoadIntoGroup(identifier, location, profile);
}
private PropertySource<?> doLoadIntoGroup(String identifier, String location, Profile profile){
    Resource resource = this.resourceLoader.getResource(location);
    // 載入的配置檔案內容存入propertySources
    PropertySource<?> propertySource = this.propertiesLoader.load(resource, group, name,(profile == null ? null : profile.getName()));
    // propertySources賦值給Environment,供後面使用
    handleProfileProperties(propertySource);
    return propertySource;
    ... 略msg資訊 ...
}
複製程式碼

SpringApplication.run()方法(4)處

看方法的名稱就知道,此處根據Environment列印spring banner。banner是啥呢,看效果圖。感覺很熟悉吧

springboot 從main啟動開始到完成征途記之一前半生
SpringApplicationBannerPrinter類完成列印操作,它根據banner.location,預設banner.location值為banner.txt,同時使用ResourceLoader載入banner.location的。這是列印文字;同時還可以配置banner.image.location列印圖片,支援的圖片格式為:"gif", "jpg", "png"。

// SpringApplication類。列印Banner到日誌檔案and/or終端
private Banner printBanner(ConfigurableEnvironment environment) {
    // 可以關閉列印Banner功能
    if (this.bannerMode == Banner.Mode.OFF) { return null;}
    ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader: new DefaultResourceLoader(getClassLoader());
    // 建立SpringApplicationBannerPrinter,負責列印應用的banner
    SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
    		resourceLoader, this.banner);
    if (this.bannerMode == Mode.LOG) {
        // 執行列印到日誌
    	return bannerPrinter.print(environment, this.mainApplicationClass, logger);
    }
    // 執行列印到終端
    return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
// SpringApplicationBannerPrinter類
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
    // 獲取banner 可能有圖片banner、文字banner、預設banner
    Banner banner = getBanner(environment, this.fallbackBanner);
    banner.printBanner(environment, sourceClass, out);
    return new PrintedBanner(banner, sourceClass);
}
// 預設的banner,對照程式碼和列印的資訊,一種煥然的感覺升起
class SpringBootBanner implements Banner {
    private static final String[] BANNER = { "",
        "  .   ____          _            __ _ _",
        " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",
        "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
        " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",
        "  '  |____| .__|_| |_|_| |_\\__, | / / / /",
        " =========|_|==============|___/=/_/_/_/" };
        
    private static final String SPRING_BOOT = " :: Spring Boot :: ";
    
    public void printBanner(Environment environment, Class<?> sourceClass,PrintStream printStream) {
        // 組織列印資訊
        for (String line : BANNER) {
            printStream.println(line);
        }
        String version = SpringBootVersion.getVersion();
        version = (version == null ? "" : " (v" + version + ")");
        String padding = "";
        while (padding.length() < STRAP_LINE_SIZE- (version.length() + SPRING_BOOT.length())) {
            padding += " ";
        }
        // 列印資訊到終端或日誌檔案
        printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));
        printStream.println();
    }

}
複製程式碼

SpringApplication.run()方法(5)處

從方法名稱知道,建立ApplicationContext物件。其實建立的是其子類:AnnotationConfigEmbeddedWebApplicationContext.

// 通過反射將類名例項為物件,反射過程中執行該類及父類們的構造方法
protected ConfigurableApplicationContext createApplicationContext() {
    // DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext"
    Class<?> contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
    return (ConfigurableApplicationContext)BeanUtils.instantiate(contextClass);
}
// 下面是呼叫AnnotationConfigEmbeddedWebApplicationContext類及父類們的構造方法。通過上面可以瞭解WebApplicationContext層級關係
public AnnotationConfigEmbeddedWebApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);		
    this.scanner = new ClassPathBeanDefinitionScanner(this);	
}        
// 建立DefaultListableBeanFactory物件和賦值GenericApplicationContext.DefaultListableBeanFactory屬性,DefaultListableBeanFactory非常重要(BeanFactory的預設例項物件)。	    
public GenericApplicationContext() {		
    this.beanFactory = new DefaultListableBeanFactory();
}         
建立PathMatchingResourcePatternResolver並賦值給AbstractApplicationContext.ResourcePatternResolver屬性	    
public AbstractApplicationContext() {		
    this.resourcePatternResolver = new PathMatchingResourcePatternResolver(this);
}	    
protected ResourcePatternResolver getResourcePatternResolver() {
    return new ServletContextResourcePatternResolver(this);
}

複製程式碼

通過AnnotationConfigEmbeddedWebApplicationContext構造方法,我們可以看到,這個構造方法建立並賦值兩個重要的屬性:

1. ClassPathBeanDefinitionScanner
2. AnnotatedBeanDefinitionReader
複製程式碼

ClassPathBeanDefinitionScanner用於掃描classpath目錄下帶註解的beanclass;AnnotatedBeanDefinitionReader解析並讀取BeanDefinition。同時AnnotatedBeanDefinitionReader有兩個重要的屬性BeanNameGenerator、ScopeMetadataResolver。BeanNameGenerator預設實現類是AnnotationBeanNameGenerator,AnnotationBeanNameGenerator用於生成帶有@Component、@Controller、@Service、@Repository註解的beanDefinition; ScopeMetadataResolver預設實現類是AnnotationScopeMetadataResolver,用於處理帶有@Scope註解的beanDefinition。

AnnotatedBeanDefinitionReader構造方法會使用AnnotationConfigUtils類來註冊BeanPostProcessor、BeanFactoryPostProcessor、AutowireCandidateResolver的各自子類,如下程式碼

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry,Environment environment) {
    this.registry = registry;	
    // 註冊相關的BeanPostProcessor、BeanFactoryPostProcessor、AutowireCandidateResolver到ApplicationContext
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
/**
 * 在給定的登錄檔中註冊所有相關的註釋後處理器(post processors)
 * @return a Set of BeanDefinitionHolders, containing all bean definitions
 * that have actually been registered by this call
 */
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, Object source) {
    DefaultListableBeanFactory beanFactory =unwrapDefaultListableBeanFactory(registry);
    beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
    
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
    
    // 將以下幾個類及對應的RootBeanDefinition放入DefaultListableBeanFactory.beanDefinitionMap中
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    
    RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    
    RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    
    RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    
    RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    
    RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
   
    return beanDefs;
}

private static BeanDefinitionHolder registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
    definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(beanName, definition);
    return new BeanDefinitionHolder(definition, beanName);
}
複製程式碼

放入DefaultListableBeanFactory.beanDefinitionMap的RootBeanDefinition如下

BeanFactoryPostProcessor子類:    
  ConfigurationClassPostProcessor: 用於處理註解了@Configuration那些類(process @Configuration classes)  
BeanPostProcessor子類:    
  AutowiredAnnotationBeanPostProcessor:用於處理註解了@Autowired、@Value、@Inject那些類(process @Autowired、@Value、@Inject classes)    
  RequiredAnnotationBeanPostProcessor: 用於處理註解了@Required那些類(process @Required classes)     
  CommonAnnotationBeanPostProcessor:用於處理註解了@PostConstruct、@PreDestroy那些類 (process @PostConstruct、@PreDestroy classes)  
EventListenerMethodProcessor:將註解了@EventListener方法註冊為單獨的ApplicationListener例項 
複製程式碼

事實上,只要是容器所負責的類都會放入這個屬性,後面例項化過程中的註冊的bean也會陸續放入DefaultListableBeanFactory.beanDefinitionMap中,即我們常說的Beans被spring容器所管理

SpringApplication.run()方法(6)處

配置context。具體為context關聯environment、context.beanFactory.beanDefinitionMap賦值、應用ApplicationContextInitializer子類集合、後置處理ApplicationContext、釋出事件監聽器釋出contextPrepared(context)和listeners.contextLoaded(context)事件方法、載入beans到the application context

private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {
    // environment賦值給ApplicationContext的屬性
    context.setEnvironment(environment);//(a1)
    postProcessApplicationContext(context);//(a2)
    // SpringApplicationRunListener.starting()方法獲取的ApplicationContextInitializer的子類開始使用,見圖三
    applyInitializers(context); //(a3)
    listeners.contextPrepared(context);//(a4)
    if (this.logStartupInfo) {
    	logStartupInfo(context.getParent() == null);//(a5)
    	logStartupProfileInfo(context);//(a6)
    }
    
    // Add boot specific singleton beans
    context.getBeanFactory().registerSingleton("springApplicationArguments",applicationArguments);//(a7)
    
    context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);//(a8)
    
    // Load the sources
    Set<Object> sources = getSources();
    load(context, sources.toArray(new Object[sources.size()]));//(a9)
    listeners.contextLoaded(context);//(a10)
}
複製程式碼

如上程式碼,幾乎每句都很重要,下面具體(a*)的作用

- (a1)處:更新context.environment屬性值,同時為ClassPathBeanDefinitionScanner.environment和AnnotatedBeanDefinitionReader.environment賦值
- (a2)處:postProcessApplicationContext此時沒有實際的作用,但是SpringApplication子類可重寫它
- (a3)處:呼叫各個ApplicationContextInitializer子類的initilize()方法,待詳說
- (a4)處:ApplicationContext物件建立和部分屬性賦值後,事件釋出監聽器開始執行contextPrepared事件方法。這個事件方法是空實現
- (a5)處:列印應用startup的日誌,包括應用名稱、id、版本、classes路徑等。如“[INFO ] [main]   23:29:48 com.yy.App => Starting App on bogon with PID 5550 (/Users/yaoliang/skyler/project/mytest/java_example/target/classes started by yaoliang in /Users/yaoliang/skyler/project/mytest)”
- (a6)處:列印應用profile資訊日誌,如“[INFO ] [main]   23:32:02 com.yy.App => No active profile set, falling back to default profiles: default”或“The following profiles are active: test,dev”
- (a7)(a8)處:將應用啟動引數(springApplicationArguments)、PrintedBanner這兩個singleton beans物件放入ApplicationContext.beanFactory.beanDefinitionMap和singletonObjects屬性中
- (a9)處:載入source("com.yy.App")到beanFactory.beanDefinitionMap.
其實load方法的通用邏輯為將classpath下的classes和指定sources("com.yy.App")下classes的beans放入BeanDefinitionRegistry的BeanDefinitions中.具體程式碼如下,兩步:建立BeanDefinitionLoader,使用它載入source資源
protected void load(ApplicationContext context, Object[] sources) {
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
    		getBeanDefinitionRegistry(context), sources);
    loader.load();
}
步1.首先會建立一個BeanDefinitionLoader物件
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources){
	this.sources = sources;
	this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
	this.xmlReader = new XmlBeanDefinitionReader(registry);
	if (isGroovyPresent()) {
	    this.groovyReader = new GroovyBeanDefinitionReader(registry);
	}
	this.scanner = new ClassPathBeanDefinitionScanner(registry);
	this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
從程式碼可以看到,BeanDefinitionLoader可以載入Xml、javaConfig、ClassPath形式的bean
步2.loader.load().開始載入beans load(Object source)可根據source型別載入不同型別資源從而得到beans.
private int load(Object source) {
    if (source instanceof Class<?>) {
    	return load((Class<?>) source);
    }
    if (source instanceof Resource) {
    	return load((Resource) source);
    }
    if (source instanceof Package) {
    	return load((Package) source);
    }
    if (source instanceof CharSequence) {
    	return load((CharSequence) source);
    }
}
當前應用的source為com.yy.App類,所以使用AnnotatedBeanDefinitionReader物件載入com.yy.App類目錄下的beans
private int load(Class<?> source) {
    if (isComponent(source)) {
    	this.annotatedReader.register(source);
    	return 1;
    }
    return 0;
}
public void register(Class<?>... annotatedClasses) {
    for (Class<?> annotatedClass : annotatedClasses) {
    	registerBean(annotatedClass);
    }
}
public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    // 解析annotatedClass把他和他的metadata註解構造到AnnotatedGenericBeanDefinition中
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    // 沒懂他的用處 TODO
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    // 生成beanName,生成邏輯值得你自己debug走下
    String beanName = this.beanNameGenerator.generateBeanName(abd, this.registry));
    // 檢查abd是否有@Lazy,@Primary,@DependsOn,@Role,@Description,有就set進Description
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);

    // 建立持有abd,beanName的BeanDefinitionHolder
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    // 沒懂 TODO
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    // 把BeanDefinition開始讀取並註冊到beanFactory.beanDefinitionMap中
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
- (a10)處:
程式碼走到此處,意味著application context has been loaded but before it has been refreshed,所以呼叫事件釋出監聽器的contextLoaded(context)方法,這個方法在釋出事件的之前,把SpringApplication.listeners的值賦值到ApplicationContext.applicationListeners中。
下面釋出事件功能,contextLoaded釋出ApplicationPreparedEvent事件,監聽到這個事件的ApplicationListener子類監聽器會做什麼呢
  1.ConfigFileApplicationListener
把new PropertySourceOrderingPostProcessor放入ApplicationContext.beanFactoryPostProcessors屬性中
  2.LoggingApplicationListener
把springBootLoggingSystem子類物件放入DefaultSingletonBeanRegistry.singletonObjects、registeredSingletons和DefaultListableBeanFactroy.manualSingletonNames中
複製程式碼

SpringApplication.run()方法(7)處

refreshContext(context),看方法名稱就知道,重新整理ApplicationContext。重點來了,怎麼重新整理,具體都重新整理什麼。看下方法程式碼

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
    	// Prepare this context for refreshing.
    	prepareRefresh();
    
    	// Tell the subclass to refresh the internal bean factory.
    	ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    
    	// Prepare the bean factory for use in this context.
    	prepareBeanFactory(beanFactory);
    
    	try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
    
    	    // Invoke factory processors registered as beans in the context.
    	    invokeBeanFactoryPostProcessors(beanFactory);
    
    	    // Register bean processors that intercept bean creation.
    	    registerBeanPostProcessors(beanFactory);
    
    	    // Initialize message source for this context.
    	    initMessageSource();
    
    	    // Initialize event multicaster for this context.
    	    initApplicationEventMulticaster();
    
    	    // Initialize other special beans in specific context subclasses.
    	    onRefresh();
    
    	    // Check for listener beans and register them.
    	    registerListeners();
    
    	    // Instantiate all remaining (non-lazy-init) singletons.
    	    finishBeanFactoryInitialization(beanFactory);
    
    	    // Last step: publish corresponding event.
    	    finishRefresh();
    	}catch (BeansException ex) {
    		destroyBeans();
    
    		// Reset 'active' flag.
    		cancelRefresh(ex);

    		// Propagate exception to caller.
    		throw ex;
    	}finally {
    		resetCommonCaches();
    	}
    }
}
複製程式碼

這個方法會做很東西,重新整理ApplicationContext、呼叫BeanFactory 的postProcessors(後置處理器)、註冊BeanPostProcessors到BeanFactory.beanPostProcessors等等,這就是從main啟動開始到完成征途記之一後半生》 的內容了

SpringApplication.run()方法(8)處

ApplicationContext refresh之後呼叫此處方法,此方法功能為呼叫ApplicationRunner.run和CommandLineRunner.run方法,這裡我們可以自定義兩個類的子類實現我們自己的邏輯

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
    	if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
    	}
    	if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
    	}
    }
}
複製程式碼

SpringApplication.run()方法(9)處

此處為呼叫事件釋出監聽器的finished(ConfigurableApplicationContext, Throwable)方法。沒有異常的話,會發布ApplicationReadyEvent事件。此時釋出事件不同以往廣播形式,此次用ApplicationContext.publishEvent(event)監聽此事件的監聽器監聽到此事件後執行自己的邏輯

  • 注意:ApplicationContext.publishEvent(event)內部使用的還是廣播事件方式:getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)

結束

相關文章