章节01 / 完整阅读
01. 先画责任边界:Boot编排,Framework执行
问题与设计动机
为什么读完SpringApplication仍不能解释Bean实例化?Boot解决的是应用如何以约定启动:选上下文、准备环境、加载配置、接入WebServer并发布运行事件。它把这些能力接到Spring容器,容器内部的定义解析、依赖注入、后置处理器和生命周期仍由Spring Framework实现。把两层混为一谈,会把失败发生的阶段说错。
字段、状态与不变量
Boot的SpringApplication持有primarySources、initializers、listeners和启动属性;Framework的AbstractApplicationContext持有beanFactory、active状态和事件广播器。Environment是两层之间共享的配置视图,BeanDefinition是配置解析的产物,Bean实例则是后续实例化的产物。三者不能互相替代。
沿真实调用链推演
业务main→SpringApplication.run→prepareEnvironment→prepareContext→refreshContext→AbstractApplicationContext.refresh。refresh中的invokeBeanFactoryPostProcessors负责执行配置类解析,registerBeanPostProcessors负责把实例拦截器准备好,onRefresh让Boot的Servlet上下文创建WebServer。
真实源码 · SpringApplication.java:301–314 · 3.5.16
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
Startup startup = Startup.create();
if (this.properties.isRegisterShutdownHook()) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
startup.started();
if (this.properties.isLogStartupInfo()) {
new StartupInfoLogger(this.mainApplicationClass, environment).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
真实源码 · AbstractApplicationContext.java:588–612 · 6.2.19
@Override
public void refresh() throws BeansException, IllegalStateException {
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 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);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 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 (RuntimeException | Error ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
contextRefresh.end();
}
}
finally {
this.startupShutdownThread = null;
this.startupShutdownLock.unlock();
}
}
分支与失败边界
一个缺依赖的Bean可能在Framework创建实例时失败,然后被Boot的失败处理包装、分析并关闭上下文。不能因最终日志由Boot打印,就把根因归到自动配置。MVC请求分派也应继续进入Framework的DispatcherServlet,而非停在Boot的WebMvcAutoConfiguration。
可复现实验与观察点
先在调试器对SpringApplication.run、AbstractApplicationContext.refresh、AbstractAutowireCapableBeanFactory.initializeBean各设断点。启动第27章样例;每次停下记录当前方法、beanName及上下文active状态。预期先进入Boot,再进入Framework,Bean初始化过程中不会重复执行整条Boot启动链。