每次我看到这个问题,我总是无法理清一条线来顺利回答。这一次,我理清了一个八股文本的标准答案。复习的时候拿出来想一想。如果有不合适的地方,我希望你能给我一些建议~
[源代码基于springboot2.4.3]
框架启动类每个SpringBoot项目都有一个标有@SpringBotaplication注释的main启动类,
@SpringBootApplicationpublic class Application { public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); }}
直接看Springaplication.run方法,向下和向下发现整个启动过程分为两部分,一个Springbootaplication结构方法和操作run方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
核心注解@点击SpringBotaplication注释后,只需注意三个核心注释:
@SpringBootConfiguration
- 本质上包装了一层@configuration注释,通过Javaconfig配置bean,标识当前类别为配置类别。
@EnableAutoConfiguration
- 点击@EnableAutoconfiguration注释,只看AutoconfigurationPackage和两个核心注释@Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage
,这个注释类似于@component注释的含义,一个用于手动添加注释加载自动配置,另一个用于扫描指定路径注入bean。@AutoConfigurationPackage
为了在一条路径下加载多个自动配置类,不需要单独添加此注释 @Import更方便直接引入包路径。@ComponentScan
自定义包路径很方便,比如一些 Bean 跟 SpringBootApplication 不在同一条路径下。@Import(AutoConfigurationImportSelector.class)
,在Autoconfigurationimportselector类中,getcandidateconigurations()方法,这种方法通过SpringFactoriesloader.loadFactoryNames()META位于META-INF/spring.factories文件中的所有自动配置类别,并加载这些类别
@ComponentScan
- 默认扫描当前的包和子包,会有
@Component
,@Controller
,@Service
,@Repository
等注解类注册到容器中,以便调用。
以上注释只是将需要加载的类加载到classpath中,真正获取类转换为bean的过程仍在run方法中,因此建议在回答问题时提到核心注释,启动过程主要是springbootaplcation类的结构方法和run方法。
构造方法public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 获取应用类型,根据Servlet是否加载,判断web环境是否为web this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class)); // 读META-INFO/spring.factories文件,获取相应的Applicationcontextinitinitializer组装到集合 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 设置所有监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推断main函数 this.mainApplicationClass = deduceMainApplicationClass();}
我们可以看到,这些事情主要是在结构方法中完成的:
- 根据是否加载servlet,判断web环境是否为web环境
- 获取所有初始化器,扫描所有初始化器
META-INF/spring.factories
Applicationcontextinitinitializer子类通过反射获得实例,并在spring实例启动前后进行一些回调。 - 获取所有监听器,同2,也是扫描配置加载对应的类实例。
- 定位main方法
/** * 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) { // 启动秒表计时器,统计项目启动时间 StopWatch stopWatch = new StopWatch(); stopWatch.start(); // 创建和启动上下文对象,即spring根容器 DefaultBootstrapContext bootstrapContext = createBootstrapContext(); // 可配置应用程序上下文变量的定义 ConfigurableApplicationContext context = null; /** * 设置jdk系统属性 * headless直译是无头模式, * headless模式是指明确Springboot应在无鼠键支持的环境中运行,一般程序也运行在Linux等服务器上,无鼠键支持,这里的默认值是truee; */ configureHeadlessProperty(); /** * 获取运行监听器 getRunListeners, 还调用了上述getspringFactoriesinstances 方法 * 从spring.在factories中获得配置 */ SpringApplicationRunListeners listeners = getRunListeners(args); // 启动监听器 listeners.starting(bootstrapContext, this.mainApplicationClass); try { // 默认包装应用程序参数,即在命令下启动应用带的参数,如--server.port=9000 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // /** * 准备环境 prepareEnvironment 是个硬茬,主要涉及 * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles * environmentPrepared、bindToSpringApplication、在下面的例子中可以查看attach的许多方法 */ ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); // 配置忽略的 bean configureIgnoreBeanInfo(environment); // 打印 SpringBoot 标志,即启动时在控制台上的图案logo,可在src///main把名字放在resources中是banner的自定义文件 Banner printedBanner = printBanner(environment); // 创建 IOC 容器 context = createApplicationContext(); // 设置启动器,开始设置应用程序 context.setApplicationStartup(this.applicationStartup); // 配置 IOC 容器的基本信息 (spring容器前置处理) prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); /** * IOC容器刷新 * 它将涉及Spring容器的启动、自动组装和创建 WebServer启动Web服务,即SpringBoot启动内嵌 Tomcat */ refreshContext(context); /** * 用户自定义容器刷新后留下的处理逻辑 * 刷新容器后的扩展接口(spring容器后处理) */ afterRefresh(context, applicationArguments); // 结束计时器并打印,这就是我们启动后console显示的时间 stopWatch.stop(); if (this.logStartupInfo) { // 打印完成的日志 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } // 发布监听应用上下文启动完成(发布启动结束事件),所有运行监听器调用 started() 方法 listeners.started(context); // 执行runnner,遍历所有的 runner,调用 run 方法 callRunners(context, applicationArguments); } catch (Throwable ex) { // 异常处理,如果Run过程出现异常 handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { // 所有操作监听器都被调用 running() 方法,上下文的监控应用 listeners.running(context); } catch (Throwable ex) { // 异常处理 handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } // 返回最终构建的容器对象 return context; }
谁能背诵这一套?。。八股文讲究的是通俗易背。你知道你是背的,面试官也知道你是背的,但是这层窗纸不能刺穿。去面试背到一半有多尴尬。
背最重要的几点,反正其他不痛不痒的地方背也会忘记。最重要的是Abstractapplicationconconetxtt。.refresh方法。
refresh方法这里就不粘代码了。直接总结成几句话。如果代码太多,记不住。refresh方法贯穿bean的生命周期
invokeBeanFactoryPostProcessors- invokeBeanfactoryPostprocesors方法将在beanfactory中找到所有实现beandefinitiongistoryPostprocesors接口和beanfactoryPostprocesors接口的bean,并在postprocesor中执行postprocesprocesbeandefinitiongistory()和postprocesbeanfactory()方法
- 使用doprocesconfigurationclass方法将处理所有springboot标记的所有类别,如@Import、@Bean等注释。
- 调用BeandefinitionregistryProcesor,将bean定义添加到容器中, 调用BeanFactoryPostProcesor,将Bean定义添加到容器中。(SpringBean的生命周期)
创建web容器。如果是web环境,将构建tomcattat。 web容器。
总结写多了也背不过来,总结一下springbot的整体启动步骤。
- 整个spring框架的启动分为两部分,
SpringBootaplication对象构建
和执行run方法
- 核心注释@SpringBonguration标识启动类为配置类,@enableautonguration通过内部@Import注释autongurationgurationselectortion.class实现自动组装,@ComponentScan默认扫描当前目录和子目录下的bean。
- SpringBotaplication的构造方法主要做了几件事。
- 根据是否加载servlet,判断web环境是否为web环境
- 获取所有初始化器,扫描所有初始化器
META-INF/spring.factories
Applicationcontextinitinitializer子类通过反射获得实例,并在spring实例启动前后进行一些回调。 - 获取所有监听器,同2,也是扫描配置加载对应的类实例。
- 定位main方法
- rfresh方法主要创建配置环境、事件监控和启动应用的上下文,其中rfresh方法贯穿springbean的生命周期,执行bean生命周期的前后钩法,并处理spring的注释标记类别。tomcat容器是通过Java代码在onrefresh中构建和启动的。
- 好了,回家等通知