SpringBoot2 | SpringBoot启动流程源码分析(一)
置顶 张书康 2018-08-31 15:29:58 73428
收藏 388
分类专栏: SpringBoot Spring springmvc 文章标签: SpringBoot2
版权
微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
SpringBoot2 | SpringBoot启动流程源码分析(一)
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三)
SpringBoot2 | SpringBoot Environment源码分析(四)
SpringBoot2 | SpringBoot自定义AutoConfiguration | SpringBoot自定义starter(五)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | 条件注解@ConditionalOnBean原理源码深度解析(七)
SpringBoot2 | Spring AOP 原理源码深度剖析(八)
SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)
SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar (十)
SpringBoot2 | Spring 核心扩展接口 | 核心扩展方法总结(十一)
概述:
前阵子看到了SpringCloud社区的一个开源项目,主要是对服务发现增强的功能。研究项目的时候发现代码简练,优雅,最主要是spring ioc和aop特性应用的得心应手。若非对源码有深入研究,不可能写出这么优秀的开源项目。另外在现有的springboot专栏中,大多数博文旨在应用,对一些中间件的整合之类,源码分析的博客数量有限。鉴于以上两方面,该系列应运而生。
该系列主要还是Spring的核心源码,不过目前Springboot大行其道,所以就从Springboot开始分析。最新版本是Springboot2.0.4,Spring5,所以新特性本系列后面也会着重分析。
整个系列会围绕springboot启动流程进行源码分析,在整个流程中,会遇到一些核心类或者核心流程,会着重讲解,所以篇幅可能会增多,做好准备。
源码分析
首先是项目启动类:
public static void main(String[] args) {
SpringApplication.run(MarsApplication.class, args);
}
- 1
- 2
- 3
public SpringApplication(Object... sources) {
//初始化
initialize(sources);
}
- 1
- 2
- 3
- 4
初始化时,会先进行区分环境:非web环境、web环境、reactive环境三种。如下:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//设置servlet环境
this.webEnvironment = deduceWebEnvironment();
//获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//获取监听器,这里是第二次加载spring.factories文件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
来看一下具体环境判断的deduceWebEnvironment()
方法:
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里主要是通过判断REACTIVE
相关的字节码是否存在,如果不存在,则web环境即为SERVLET
类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。
ApplicationContextInitializer
是spring组件spring-context
组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。
上述代码中getSpringFactoriesInstances
方法会加载META-INF/spring.factories文件,spring.factories
文件中的默认的实现类如下:
这里监听器为9个:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
还有1个为:org.springframework.boot.autoconfigure.BackgroundPreinitializer
这10个监听器会贯穿springBoot整个生命周期。稍后会介绍。
这里先继续后面的流程。来看一下run方法:
public ConfigurableApplicationContext run(String... args) {
//时间监控
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
configureHeadlessProperty();
//获取spring.factories中的监听器变量,args为指定的参数数组,默认为当前类SpringApplication
//第一步:获取并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//第二步:构造容器环境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//设置需要忽略的bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//第三步:创建容器
context = createApplicationContext();
//第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//第五步:准备容器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//第六步:刷新容器
refreshContext(context);
//第七步:刷新容器后的扩展接口
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 第一步:获取并启动监听器
- 第二步:构造容器环境
- 第三步:创建容器
- 第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
- 第五步:准备容器
- 第六步:刷新容器
- 第七步:刷新容器后的扩展接口
下面具体分析。
一:获取并启动监听器
1)获取监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
跟进getRunListeners
方法:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
- 1
- 2
- 3
- 4
- 5
上面可以看到,args本身默认为空,但是在获取监听器的方法中,getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)
将当前对象作为参数,该方法用来获取spring.factories
对应的监听器:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
- 1
- 2
- 3
整个 springBoot 框架中获取factories的方式统一如下:
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
//装载class文件到内存
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//主要通过反射创建实例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
上面通过反射获取实例时会触发EventPublishingRunListener
的构造函数:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
重点来看一下addApplicationListener
方法:
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same listener.
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
//内部类对象
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
上述方法定义在SimpleApplicationEventMulticaster
父类AbstractApplicationEventMulticaster
中。关键代码为this.defaultRetriever.applicationListeners.add(listener);
,这是一个内部类,用来保存所有的监听器。也就是在这一步,将spring.factories
中的监听器传递到SimpleApplicationEventMulticaster
中。
继承关系如下:
2)启动监听器:
listeners.starting();
,获取的监听器为EventPublishingRunListener
,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。
@Override
public void starting() {
//关键代码,这里是创建application启动事件`ApplicationStartingEvent`
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
- 1
- 2
- 3
- 4
- 5
- 6
EventPublishingRunListener
这个是springBoot框架中最早执行的监听器,在该监听器执行started()
方法时,会继续发布事件,也就是事件传递。这种实现主要还是基于spring的事件机制。
继续跟进SimpleApplicationEventMulticaster
,有个核心方法:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。
Executor executor = getTaskExecutor();
if (executor != null) {
//异步发送事件
executor.execute(() -> invokeListener(listener, event));
}
else {
//同步发送事件
invokeListener(listener, event);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
这里会根据事件类型ApplicationStartingEvent
获取对应的监听器,在容器启动之后执行响应的动作,有如下4种监听器:
这是springBoot启动过程中,第一处根据类型,执行监听器的地方。根据发布的事件类型从上述10种监听器中选择对应的监听器进行事件发布,当然如果继承了 springCloud或者别的框架,就不止10个了。这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:
@Override
public void onApplicationEvent(ApplicationEvent event) {
//在springboot启动的时候
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
//springboot的Environment环境准备完成的时候
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
//在springboot容器的环境设置完成以后
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
//容器关闭的时候
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
//容器启动失败的时候
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
因为我们的事件类型为ApplicationEvent
,所以会执行onApplicationStartedEvent((ApplicationStartedEvent) event);
。springBoot会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。大同小异,别的监听器执行流程这里不再赘述,后面会有单独的详解。
继续后面的流程。
二:环境构建:
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
跟进去该方法:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
//获取对应的ConfigurableEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置
configureEnvironment(environment, applicationArguments.getSourceArgs());
//发布环境已准备事件,这是第二次发布事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
来看一下getOrCreateEnvironment()
方法,前面已经提到,environment
已经被设置了servlet
类型,所以这里创建的是环境对象是StandardServletEnvironment
。
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
枚举类WebApplicationType
是springBoot2新增的特性,主要针对spring5引入的reactive特性。枚举类型如下:
public enum WebApplicationType {
//不需要再web容器的环境下运行,普通项目
NONE,
//基于servlet的web项目
SERVLET,
//这个是spring5版本开始的新特性
REACTIVE
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Environment
接口提供了4种实现方式,StandardEnvironment
、StandardServletEnvironment
和MockEnvironment
、StandardReactiveWebEnvironment
,分别代表普通程序、Web程序、测试程序的环境、响应式web环境,具体后面会详细讲解。
这里只需要知道在返回return new StandardServletEnvironment();
对象的时候,会完成一系列初始化动作,主要就是将运行机器的系统变量和环境变量,加入到其父类AbstractEnvironment
定义的对象MutablePropertySources
中,MutablePropertySources
对象中定义了一个属性集合:
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
- 1
执行到这里,系统变量和环境变量已经被载入到配置文件的集合中,接下来就行解析项目中的配置文件。
来看一下listeners.environmentPrepared(environment);
,上面已经提到了,这里是第二次发布事件。什么事件呢?
顾名思义,系统环境初始化完成的事件。
发布事件的流程上面已经讲过了,这里不在赘述。来看一下根据事件类型获取到的监听器:
可以看到获取到的监听器和第一次发布启动事件获取的监听器有几个是重复的,这也验证了监听器是可以多次获取,根据事件类型来区分具体处理逻辑。上面介绍日志监听器的时候已经提到。
主要来看一下ConfigFileApplicationListener
,该监听器非常核心,主要用来处理项目配置。项目中的 properties 和yml文件都是其内部类所加载。具体来看一下:
首先方法执行入口:
首先还是会去读spring.factories
文件,List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
获取的处理类有以下四种:
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor= //一个@FunctionalInterface函数式接口
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,//为springCloud提供的扩展类
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,//支持json环境变量
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor //springBoo2提供的一个包装类,主要将`StandardServletEnvironment`包装成`SystemEnvironmentPropertySourceEnvironmentPostProcessor`对象
- 1
- 2
- 3
- 4
- 5
在执行完上述三个监听器流程后,ConfigFileApplicationListener
会执行该类本身的逻辑。由其内部类Loader
加载项目制定路径下的配置文件:
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
- 1
至此,项目的变量配置已全部加载完毕,来一起看一下:
这里一共6个配置文件,取值顺序由上到下。也就是说前面的配置变量会覆盖后面同名的配置变量。项目配置变量的时候需要注意这点。
后面五个步骤见第二篇:SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(一)
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三)
SpringBoot2 | SpringBoot Environment源码分析(四)
SpringBoot2 | SpringBoot自定义AutoConfiguration | SpringBoot自定义starter(五)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | 条件注解@ConditionalOnBean原理源码深度解析(七)
SpringBoot2 | Spring AOP 原理源码深度剖析(八)
SpringBoot2 | SpingBoot FilterRegistrationBean 注册组件 | FilterChain 责任链源码分析(九)
SpringBoot2 | BeanDefinition 注册核心类 ImportBeanDefinitionRegistrar (十)
SpringBoot2 | Spring 核心扩展接口 | 核心扩展方法总结(十一)
05-14
海量GPU计算资源,预装AI框架和开发环境,开机即用;7*24小时专家团队提供多元服务,让计算科研省时、省力、省心!
84
codeing_doc:为何我在springboot2.0.4源码中看到是 public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } 和文章上你出现的不一样啊2 年前回复
4
WinterChen的博客:
-
//获取监听器,这里是第二次加载spring.factories文件
-
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
3
-
-
站立小海豹回复linxirong1127:我觉得楼主说得没错,我看的2.3.0版本,最外层缓存key值是classloader,第二层map里面的key值已经有ApplicationContextInitializer.class,这里不需要重新加载,只需要读取缓存即可6 月前回复
NullPointExceptionc回复:你说的没问题,这个些监听是初始化的时候保存在SpringApplication中的,构建EventPublishingListenter,时候通过application.getListeners(),放入广播器中的1 年前回复
linxirong1127回复:设置系统初始化器时,this.setInitializers方法中,是以ApplicationContextInitializer.class作为key去读取spring.factories文件,构建ApplicationContextInitializer.class对应的实现类properties对象, 所以在设置系统监听器的时候,缓存中是没有的ApplicationListener.class的实现类properties对象的,我觉得作者没写错1 年前回复
扎哇开发攻城狮:作者大佬,你一开始怎么点进那个
-
public SpringApplication(Object... sources) {
-
//初始化
-
initialize(sources);
-
}
1
-
码哥
天亮前灬晚安:能说一下源码项目怎么跑起来吗?发现很多个包互相依赖,spring core,spring-bean,spring-boot包。我下载源码后发现各种报错1 年前回复
1
-
萧凡君回复:用idea直接就可以跑10 月前回复
qq_26052969:话说作者貌似是众安保险的,我看到健康险有个人和你头像名字都一样!3 年前回复
1
-
qq_26052969回复张书康:哈哈,核心系统部的3 年前回复
张书康
回复:这么巧。确实是健康险的,你是哪个部门的?3 年前回复
1
青灰色的墙:请问博主,springboot在启动的时候,配置在application.yml中的信息,在哪些地方用到了5 月前回复
码哥
半吊子的程序狗:樓主您好:有點沒有看明白,如果你有時間能否再指導一下
-
这里会根据事件类型ApplicationStartingEvent获取对应的监听器,在容器启动之后执行响应的动作,有如下4种监听器:
-
-
张书康
回复:spring的监听器都是在配置文件里指定的,然后全部加载到内存。根据事件类型进行筛选。6 月前回复
f45056231p:转载一下,方便自己慢慢研究2 年前回复
相关推荐
SpringBoot源码深度解析(一):SpringBoot的自动配置原理...
4-17
SpringBoot2 | Spring AOP 原理深度源码分析_u01451317...
4-11
SpringBoot源码深度解析(六)springBoot 源码环境构建
61
4万+
5-1
SpringBoot源码深度解析(五)springBoot 日志管理_茶叶...
5-4
深入理解Spring 之 源码剖析 SpringBoot Aop 切面编织过程和代理执行过程
6313
2万+
03-01
5183
9068
5509
1290
3万+
557
2799
2万+
492
SpringBoot2 | Spring AOP 原理深度源码分析(八)
1万+
04-17
2万+
互联网架构-SpringBoot源码深度解析-033:SpringBoot源码深入解析
56
1851
一步一步分析SpringBoot启动源码(一) 一步一步分析SpringBoot启动源码(二) 一步一步分析SpringBoot启动源码(三)
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页
- 公安备案号11010502030143
- 京ICP备19004658号
- 京网文〔2020〕1039-165号
- 经营性网站备案信息
- 北京互联网违法和不良信息举报中心
- 网络110报警服务
- 中国互联网举报中心
- 家长监护
- Chrome商店下载
- ©1999-2021北京创新乐知网络技术有限公司
- 版权与免责声明
- 版权申诉
- 出版物许可证
- 营业执照
码龄10年 暂无认证
35万+
访问
等级
3070
积分
700
粉丝
232
获赞
108
评论
770
收藏
热门文章
- SpringBoot2 | SpringBoot启动流程源码分析(一)
73381
- Idea debug模式下出现 "Connected to the target VM, address: '127.0.0.1:55606', transport: 'socket'"
33900
- SpringBoot2 | SpringBoot2 Hikari数据源配置
22575
- SpringCloud | Feign如何整合Ribbon进行负载均衡的?
18010
- SpringBoot2 | SpringBoot启动流程源码分析(二)
17317
最新评论
- Java | httpClient请求put方法
weixin_45522494: getHttpClient()没有这个方法
- SpringBoot2 | SpringBoot2 Hikari数据源配置
Tisfy: 写得太好了!正如那:年年郡县送征人,将与辽东作丘坂。
- SpringBoot2 | SpringBoot启动流程源码分析(二)
浪p迹: 太复杂了些,作为面试准备的材料有点太深了
- SpringBoot2 | @SpringBootApplication注解 自动化配置流程源码分析(三)
baichuanyu: spring boot 面试
- SpringBoot2 | SpringBoot启动流程源码分析(一)
Tisfy: 深得人心,正如古人云:沉舟侧畔千帆过,病树前头万木春。
最新文章
目录
分类专栏
SpringBoot源码深入解析10篇
SpringBoot24篇
Spring24篇
SpringCloud12篇
多线程7篇
分布式锁2篇
开发工具1篇
spring-security2篇
springmvc5篇
oauth21篇
java22篇
Consul1篇
Netty2篇
举报