Spring Boot 原理解析—從入口SpringApplication說起

我們說Spring Boot簡化了Spring的開發,可以根據導入的starter包自動向Spring容器中註冊Bean。在Spring Boot之前,我們要向Spring容器中註冊Bean,首先需要配置xml,如果是Web容器,則將spring.xml位置配置到Spring 提供的監聽器中,由Spring解析註冊Bean,否則則使用new ClassPathXmlApplicationContext("/spring.xml")或者new AnnotationConfigApplicationContext()等容器讀取配置解析註冊Bean。在Spring Boot中,只需要一個包含main方法的啓動類即可,在運行該main方法時,會讀取Bean,並且向Spring 容器中註冊Bean。如下爲Spring Boot最簡單的啓動類:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
	SpringApplication.run(Application.class, args);
    }
}

如上代碼所示,僅僅的四行代碼,我們就開發了一個Spring MVC的應用,我們在運行該類的時候,會自動加載和註冊對應的Bean,下面我們講述,Spring Boot是如何加載和註冊Bean的。首先我們從@SpringBootApplication說起,查看源碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}

我們先不管註解中的屬性,但是可以看出來這是一個複合註解,由@SpringBootConfiguration,@EnableAutoConfiguration和@ComponentScan三個註解構成。@ComponentScan註解我們都熟悉,是Spring中的一個元註解,用於配置Spring要掃描的包。因此我們需要繼續查看另外兩個註解的源碼。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

是不是又看到了我們熟悉的Spring的註解@Configuration和@Import,我們查看@AutoConfigurationPackage註解源碼可以發現其元註解依然是@Import註解:@Import(AutoConfigurationPackages.Registrar.class)。只要熟悉Spring中這些註解的使用,很容易就瞭解@SpringBootApplication註解的功能。其實Spring Boot中的註解基本上都是對Spring中註解的增強。熟悉完註解之後我們開始查看run方法:SpringApplication.run(Application.class, args);

//啓動調用run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    //調用重載的run方法
    return run(new Class<?>[] { primarySource }, args);
}
//重載的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    //創建SpringApplication對象並且調用run方法
    return new SpringApplication(primarySources).run(args);
}

通過上面的方法,我們發現run方法返回一個ConfigurableApplicationContext實例,如果對Spring熟悉,則很容易理解這個實例,也就是說,我們通過運行run方法創建了一個Spring 容器,並且可以返回,代碼如下所示:

public static void main(String[] args) {
    //返回ApplicationContext 容器
    ApplicationContext context = SpringApplication.run(Application.class, args);
    //從容器中獲取實例
    Application application = context.getBean(Application.class);
    //遍歷容器中的實例,並且打印Bean的名稱和類型
    for(String beanName : context.getBeanDefinitionNames()) {
        System.out.println("註冊到Spring容器中的Bean名稱爲 "+beanName +",類型爲 " +context.getBean(beanName));
    }
}

上面的代碼最後一部分沒什麼用處,只是演示了Spring Boot可以通SpringApplication類的run方法創建一個Spring容器,而真正執行邏輯的方法時在run(args)方法中,該方法用於運行一個Spring應用並且創建和刷新一個新的ApplicationContext實例。

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    //獲取監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //SpringBoot運行開始監聽
    listeners.starting();
    try {
        //準備Spring Boot運行環境
	ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
	ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
	Banner printedBanner = printBanner(environment);
        //創建ApplicationContext容器
	context = createApplicationContext();
	analyzers = new FailureAnalyzers(context);
        //準備容器
	prepareContext(context, environment, listeners, applicationArguments,printedBanner);
        //刷新容器
	refreshContext(context);
        //刷新容器後處理
	afterRefresh(context, applicationArguments);
        //SpringBoot運行成功監聽
	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);
    }
}

Spring Boot運行經歷了一系列的步驟,包括監聽器,環境的準備,容器的創建,容器的準備與刷新等,本章主要關注的是容器的創建於容器的刷新,這兩步是向Spring中註冊Bean的核心步驟。首先看createApplicationContext()方法,該方法用於創建Spring容器:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    //如果Context爲空
    if (contextClass == null) {
	try {
            //獲取容器Class實例
	    contextClass = Class.forName(this.webEnvironment? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
	}catch (ClassNotFoundException ex) {
	    throw new IllegalStateException("Unable create a default ApplicationContext, "+ "please specify an ApplicationContextClass",ex);
	}
    }
    //實例化並返回容器
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

我們需要關注的代碼爲try塊中的代碼,裏面有兩個常量DEFAULT_WEB_CONTEXT_CLASS 和 DEFAULT_CONTEXT_CLASS)他們是在SpringApplication類中聲明的如下:

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

好了 現在比較清晰了,該方法做的就是通過反射創建一個ApplicationContext實例,而平時我們是直接通過new創建的ApplicationContext實例,使用的方式是以註解容器。

ApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext();
ApplicationContext context = new AnnotationConfigApplicationContext();

創建容器之後,就要刷新容器,向容器中註冊Bean,也就是refreshContext(context)方法:

private void refreshContext(ConfigurableApplicationContext context) {
    //在此調用refresh方法
    refresh(context);
    //如果註冊勾子執行勾子方法
    if (this.registerShutdownHook) {
        try {
            //執行勾子方法
            context.registerShutdownHook();
        }catch (AccessControlException ex) {
            // Not allowed in some environments.
	}
    }
}
//真正refresh的方法
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    //調用AbstractApplicationContext的refresh()方法
    ((AbstractApplicationContext) applicationContext).refresh();
}

看到上面我們又看到重點了,最終調用AbstractApplicationContext的refresh()方法,是不是有點熟悉了,因爲我們使用AnnotationConfigApplicationContext的構造方法實例化ApplicationContext的時候,構造方法中正是調用了這個方法。

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
    this();
    register(annotatedClasses);
    refresh();
}

通過上面代碼我們看到了,最終主要的代碼邏輯全部都又走到了Spring框架中,這裏我們只講述Spring Boot入口Spring容器創建和註冊的流程,後續我們會繼續講解Spring Boot是如何完成自動註冊的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章