声明

源码基于Spring Boot 2.3.12.RELEASE

背景

此文的目的主要想弄明白为什么在Spring Boot中注册ServletFilterListener组件时需要加上@ServletComponentScan注解才能生效。

启动分析

Spring Boot应用程序的启动类一般如下所示

  • jar包启动
@SpringBootApplication
public class BootApplication { public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
  • war包启动
@SpringBootApplication
public class BootApplication extends SpringBootServletInitializer { @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(BootApplication.class);
}
}

下面主要分析jar包启动流程,war包其实最终走的也是jar包启动逻辑。只不过入口不是在main方法中调用而已,当war包部署在外部容器时,servlet容器会通过SPI寻找ServletContainerInitializer接口实现,然后调用它的onStartup方法。在spring-webjar包中,META-INF/service目录下便指定了一个实现类SpringServletContainerInitializer,入口便是该类了,该类会调用SpringApplication中的run方法。

现在来看SpringApplication

public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
} public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

构造方法

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、REACTIVE、NONE
// 不同的类型会对应不同的ApplicationContext实现
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 从spring.factories文件中获取ApplicationContextInitializer实现
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 从spring.factories文件中获取ApplicationListener实现
// 注意这里的监听器不会注册到Spring容器中,而是在Spring Boot启动中依次触发,独立于Srping容器上下文
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断启动类
this.mainApplicationClass = deduceMainApplicationClass();
}

核心实例run方法

public ConfigurableApplicationContext run(String... args) {
// 用于记录启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// Spring容器
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 用于管理SpringBoot启动过程中的声明周期,目前只有一个实现类
SpringApplicationRunListeners listeners = getRunListeners(args);
// starting生命周期
listeners.starting();
try {
// 封装参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 构建环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建容器
context = createApplicationContext();
// 配置容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,实际调用applicationContext.refresh方法
refreshContext(context);
// 空实现
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// started生命周期
listeners.started(context);
// 调用ApplicationRunner、CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
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;
}

getRunListeners方法

/**
* 从spring.factories获取SpringApplicationRunListener实现,管理启动过程中的生命周期
* 目前只有EventPublishingRunListener一个实现,用于广播事件,与前文构造方法中提到的监听器一起工作
* 比如加载application.yml文件到环境中,就是通过它实现的
*/
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

prepareEnvironment方法

private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
/*
* 触发environmentPrepared生命周期,会广播ApplicationEnvironmentPreparedEvent事件
* 从而触发ConfigFileApplicationListener监听器,加载application.yml文件的属性到
* 环境中
*/
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}

createApplicationContext方法

/**
* 创建ApplicationContext对象
* servlet环境,实现类AnnotationConfigServletWebServerApplicationContext
* 响应式环境, 实现类AnnotationConfigReactiveWebServerApplicationContext
* 其它, 实现类AnnotationConfigApplicationContext
*/
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

prepareContext方法

/**
* 主要对ApplicationContext做一些初始化
*/
private void prepareContext(
ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner) {
// 设置环境
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 调用前文构造方法中提到的ApplicationContextInitializer实例,对context做一些配置
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 注册配置类
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}

refreshContext方法

private void refreshContext(ConfigurableApplicationContext context) {
// 注册钩子,jvm退出时会回调
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
/*
* 刷新容器,这里会创建tomcat容器,实例化IOC容器中所有的单例bean
*/
refresh((ApplicationContext) context);
} protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}

tomcat内嵌容器创建

createApplicationContext方法可知,servlet环境中,Spring容器实现类为AnnotationConfigServletWebServerApplicationContext,便来看看它的refresh方法。

该方法继承自父类AbstractApplicationContext,也就是Spring IOC容器的核心方法。

@Override
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(); /**
* 子类实现
*/
onRefresh(); // Check for listener beans and register them.
registerListeners(); // 初始化单例bean
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
} catch (BeansException 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 {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

再看AnnotationConfigServletWebServerApplicationContextonRefresh方法。继承自ServletWebServerApplicationContext

@Override
protected void onRefresh() {
super.onRefresh();
try {
// 创建WebServer
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
// 内嵌容器这两个都是null,外部容器servletContext不为null
if (webServer == null && servletContext == null) {
// 从IOC容器中获取工厂
ServletWebServerFactory factory = getWebServerFactory();
// 创建WebServer,其中getSelfInitializer是注册servlet,filter组件的核心入口
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
// 外部容器手动调用
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
/**
* 从IOC容器获取WebServer工厂
* WebServer工厂的自动配置类ServletWebServerFactoryAutoConfiguration
* 主要注册了tomcat、jetty、Undertow这3个servlet容器工厂,默认使用tomcat
*/
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}

getWebServer方法,以tomcat为例,TomcatServletWebServerFactory.java

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
// 熟悉的名字,tomcat部署应用程序的根目录
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
try {
context.setCreateUploadTargets(true);
}
catch (NoSuchMethodError ex) {
// Tomcat is < 8.5.39. Continue.
}
configureTldPatterns(context);
WebappLoader loader = new WebappLoader();
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
postProcessContext(context);
}
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
// ServletContainerInitializer实现,并设置ServletContextInitializer列表
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
/*
* 添加ServletContainerInitializer实现
* tomcat启动时会异步调用ServletContainerInitializer实例的onStartup方法
* 而TomcatStarter内部维护了ServletContextInitializer列表,依次调用
* ServletContextInitializer实例的onStartup方法,从而注册Servlet、Filter组件
*/
context.addServletContainerInitializer(starter, NO_CLASSES);
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
tomcatErrorPage.setLocation(errorPage.getPath());
tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
context.addErrorPage(tomcatErrorPage);
}
for (MimeMappings.Mapping mapping : getMimeMappings()) {
context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
}
configureSession(context);
new DisableReferenceClearingContextCustomizer().customize(context);
for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
customizer.customize(context);
}
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
} private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName(); Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
}); // 该方法会导致ServletContainerInitializer实例的onStartup执行
// Start the server to trigger initialization listeners
this.tomcat.start(); // We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions(); try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
} // Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

总结

下面主要总结下比较在意的点

  • SpringApplication在创建时会从spring.factories文件中获取ApplicationContextInitializer以及ApplicationListener实现,其中ApplicationContextInitializer可以对ApplicationContext做一些定制。
  • run方法中接着从spring.factories文件中获取SpringApplicationRunListener实现,目前只有一个实现类,即EventPublishingRunListener,与前面ApplicationListener搭配使用,用于广播事件以及监听。这里的事件监听与ApplicationContext中的事件监听是相互独立的,毕竟有些事件触发时,应用上下文ApplicationContext还没有初始化好。其中application.yml属性文件加载到Environment中便是借助它实现的,SpringApplicationprepareEnvironment方法执行了environmentPrepared方法,从而广播了ApplicationEnvironmentPreparedEvent事件,而ConfigFileApplicationListener监听了该事件,会去执行application.yml属性文件的加载逻辑。
  • Environment初始化了之后,会创建ApplicationContext,对于Servlet环境,无论时内嵌容器还是运行在外部容器,实现类都是AnnotationConfigServletWebServerApplicationContext
  • 接着会调用refresh方法对ApplicationContext进行初始化,而refresh方法内部会调用onRefresh方法,然后再实例化单例bean。AnnotationConfigServletWebServerApplicationContext类在onRefresh方法中会实例化tomcat容器(默认是tomcat),其中添加了一个TomcatStarter类,这是一个ServletContainerInitializer实现,tomcat启动时会异步调用ServletContainerInitializer实例的onStartup方法,而TomcatStarteronStartup方法则会执行Spring Boot中ServletContextInitializer实例的onStartup方法,Spring Boot中注册ServletFilterListener便是通过ServletContextInitializer实现。这么做的目的是为了统一jar包运行和war包运行的差异,tomcat注册时只会在应用的部署目录搜索带有@WebServlet@WebFilter注解的类,war包运行时这没有任何问题,但是jar包运行时,我们的类文件并没有在tomcat指定的部署目录中,因此不会被注册。

黑魔法

为了验证上面所说的想法,来做以下一个实验,在上述tomcat创建过程时,会指定tomcat的docBase目录,即应用程序的部署目录,默认情况下是一个临时目录,为了方便测试,便指定一个目录。

/**
* 实现WebServerFactoryCustomizer接口,可自定义WebServerFactory
* 处理逻辑在ServletWebServerFactoryAutoConfiguration自动配置类中,
* 往容器中注册了WebServerFactoryCustomizerBeanPostProcessor
*/
@Component
public class WebServerFactoryConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setDocumentRoot(new File("D:\\Deploy"));
}
}
package com.wangtao.tomcat;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; @WebServlet({"/test"})
public class TestServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("=================TestServlet============");
System.out.println(req.getRequestURI());
}
}

然后往D:\Deploy目录中增加以下内容WEB-INF\classes\com\wangtao\tomcat\TestServlet.class,记得项目中本身不要有这个类,要的效果就是tomcat能不能加载到这个类。

测试类

@RestController
public class HelloController { @RequestMapping("/hello")
public String hello() {
// tomcat类加载器
System.out.println(Thread.currentThread().getContextClassLoader());
TomcatEmbeddedWebappClassLoader classLoader = (TomcatEmbeddedWebappClassLoader) Thread.currentThread().getContextClassLoader();
// file:/D:/Deploy/WEB-INF/classes/
Arrays.stream(classLoader.getURLs()).forEach(System.out::println);
// true
System.out.println(ClassUtils.isPresent("com.wangtao.tomcat.FirstFilter", classLoader));
// 系统类加载器
System.out.println(HelloController.class.getClassLoader());
// false
System.out.println(ClassUtils.isPresent("com.wangtao.tomcat.FirstFilter", HelloController.class.getClassLoader()));
return "Hello, Spring Boot!";
}
}

从结果中可以看到,内嵌tomcat确实可以加载到部署目录下的类文件,当开开心心的去访问这个Servlet时却发现访问不了,然而tomcat类加载器确实可以加载到它呀,经过多番查找资料,原来tomcat扫描web.xml以及@WebServlet等这些注解是通过ContextConfig组件实现的,但是Spring Boot创建tomcat时并没有加入该组件,于是虽然可以加载到类,但是相当于是一个普通的类罢了,并没有注册成Servlet。于是改写配置,如下所示

@Component
public class WebServerFactoryConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory factory) {
// 指定docBase目录
factory.setDocumentRoot(new File("D:\\Deploy"));
/*
* 添加生命周期组件,用于解析扫描web.xml、@WebFilter、@WebServlet等注解
* 从类路径中使用SPI扫描ServletContainerInitializer实现添加到tomcat中
*/
factory.addContextLifecycleListeners(new ContextConfig());
}
}

再次访问,便能看到控制台的打印了。

SpringBoot启动流程简要分析的更多相关文章

  1. Springboot启动流程简单分析

    springboot启动的类为SpringApplication,执行构造函数初始化属性值后进入run方法: 然后返回ConfigurableApplicationContext(spring应用). ...

  2. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  3. SpringBoot启动流程分析(六):IoC容器依赖注入

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  5. SpringBoot启动流程分析(二):SpringApplication的run方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  7. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  8. SpringBoot启动流程解析

    写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...

  9. 深度好文,springboot启动原理详细分析

    我们开发任何一个Spring Boot项目,都会用到如下的启动类 1 @SpringBootApplication 2 public class Application { 3 public stat ...

  10. [精] UBOOT2017+FIT 启动流程详尽分析

    开发环境:Nanopi-neo-plus2 软件版本:uboot-2017 软件版本:linux-4.14 买这个板子有一段时间了,并没有全身心的投入在上面,有时间了的话就搞一搞, 这篇随笔算是对这个 ...

随机推荐

  1. 调式源码解决 seata 报错 can not get cluster name 问题

    最近在使用Spring Cloud整合分布式事务seata,项目启动之后,控制台一直报错: can not get cluster name in registry config 'service.v ...

  2. Android 分区和内存监控

    Android 分区和内存监控 Andorid之所以是分区,是因为各自有对应的功能和用途的考量,可以进行单独读写和格式化. Android 设备包含两类分区: 一类是启动分区,对启动过程至关重要. 一 ...

  3. Word 表格对文字、图文进行排版

    在以前,Web 前端工程师利用 <table /> 元素对网页布局进行排版,但是如今却不推荐此元素排版了,而是改用 <div /> 元素和 CSS 弹性布局(或网格布局)对网页 ...

  4. 昇腾AI新技能,还能预防猪生病?

    摘要:日前,由华为与武汉伯生科技基于昇腾AI合作研发的"思符(SiFold)蛋白质结构预测平台"正式推出,并成功应用于国药集团动物保健股份有限公司的猪圆环病毒疫苗研发中. 本文分享 ...

  5. Stable Diffusion 关键词tag语法教程

    提示词 Prompt Prompt 是输入到文生图模型的文字,不同的 Prompt 对于生成的图像质量有较大的影响 支持的语言Stable Diffusion, NovelAI等模型支持的输入语言为英 ...

  6. z order curve

    https://www.cnblogs.com/tgzhu/p/8286616.html

  7. 自定义StringByteLength

    using HKElectric.ESafety.Utilities; using System.ComponentModel.DataAnnotations; namespace HKElectri ...

  8. web实践学习3

    web实践学习3 20201303张奕博 2023.1.26 创建树 从预览动图和页面可以看到,浮岛上共有两种树 ,绿色的高树和粉红色的矮树,树的实现也非常简单,是使用了两个 BoxBufferGeo ...

  9. LinkedList的线程安全解决办法

    方法一:List<String> list = Collections.synchronizedList(new LinkedList<String>()); 方法二:将Lin ...

  10. Python 20个常用库

    Requests.Kenneth Reitz写的最富盛名的http库.每个Python程序员都应该有它. Scrapy.如果你从事爬虫相关的工作,那么这个库也是必不可少的.用过它之后你就不会再想用别的 ...