四、SpringApplication启动类运行阶段
lipiwang 2024-10-23 13:56 14 浏览 0 评论
目录
- 前言
- 1、起源
- 2、SpringApplication 运行阶段
- 2.1 SpringApplicationRunListeners 结构
- 2.1.1 SpringApplicationRunListener 事件和监听机制
- 2.1.2 SimpleApplicationEventMulticaster 广播器
- 2.2 ApplicationArguments 加载启动参数
- 2.3 ConfigurableEnvironment 加载外部化配置
- 2.4 ConfigurableApplicationContext 创建 Spring 应用上下文
- 3、总结
前言
开始之前呢,希望大家带着几个问题去学习: 1、Spring Boot SpringApplication 是什么? 2、整体流程或结构是怎样的? 3、重点内容或者核心部分是什么? 4、怎么实现的? 5、是怎么和 Spring 关联起来的? 这是对自我的提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。
1、起源
上篇文章我们讲了 SpringApplication 的准备阶段,在这个阶段,完成了运行时所需要准备的资源,如:initializers、listeners等。而这篇文章我们就来讲讲 SpringApplication 的运行阶段,在这个阶段,它是如何启动 Spring 应用上下文的,且如何与 Spring 事件结合起来,形成完整的 SpringApplication 生命周期的。
注:本篇文章所用到的 Spring Boot版本是 2.3.3.RELEASE
2、SpringApplication 运行阶段
上篇文章我们讲了 SpringApplication 的构造方法,这里我们就来讲讲 SpringApplication 的核心,也就是run方法,代码如下:
public class SpringApplication {
// ...
public ConfigurableApplicationContext run(String... args) {
// 这是 Spring 的一个计时器,计算代码的执行时间(ms级别)
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 这俩变量在后面赋值处进行说明
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 用来设置java.awt.headless属性值
configureHeadlessProperty();
// 该对象属于组合模式的实现,核心是内部关联的 SpringApplicationRunListener 集合,SpringApplicationRunListener 是 Spring Boot 的运行时监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 会在不同的阶段调用对应的方法,这里表示启动run方法被调用,发送ApplicationStartingEvent
listeners.starting();
try {
// 用来获取 SpringApplication.run(args)传入的参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 获取 properties 配置文件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 设置 spring.beaninfo.ignore 的属性值,判断是否跳过搜索BeanInfo类
configureIgnoreBeanInfo(environment);
// 这里是项目启动时,控制台打印的 Banner
Banner printedBanner = printBanner(environment);
// 这里就是创建 Spring 应用上下文
context = createApplicationContext();
// 获取 spring.factories 中key为 SpringBootExceptionReporter 的类名集合
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 这里是准备 Spring 应用上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 这里是启动 Spring 应用上下文,底层调用的是 ApplicationContext 的 refresh() 方法,到这里就正式进入了 Spring 的生命周期,同时,SpringBoot的自动装配特性也随之启动
refreshContext(context);
// 里面是空的,猜测应该是交由开发人员自行扩展
afterRefresh(context, applicationArguments);
stopWatch.stop();
// 这里打印启动信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// ApplicationContext 启动时,调用该方法
listeners.started(context);
// 项目启动后,做的一些操作,开发人员可自行扩展
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// ApplicationContext 启动完成时,调用该方法
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
//...
}
上面就是整个过程的概览,可以看到,在运行阶段执行的操作比较多,虽然看起来杂乱无章,但其实还是有规律可循的。比如,执行的 SpringApplicationRunListeners 中的阶段方法,刚启动阶段的 starting 、已启动阶段的 started 、启动完成阶段的 running 等。还有对应的 Spring 应用上下文的创建、准备、启动操作等。接下来,就对里面的几个核心对象进行讨论。
2.1 SpringApplicationRunListeners 结构
我们先来看看 SpringApplicationRunListeners 对象,从代码可以看出该对象是由 getRunListeners 方法创建的:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
可以看到,通过传入的 getSpringFactoriesInstances 方法的返回值,执行 SpringApplicationRunListeners 的构造方法,进行对象的创建。接着看 getSpringFactoriesInstances 方法:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
看到这大家应该比较熟悉了,通过前面几篇文章的讨论我们知道,该方法通过 SpringFactoriesLoader.loadFactoryNames 返回所有 classpass 下的 spring.factories 文件中 key 为 SpringApplicationRunListener 的实现类集合。如 Spring Boot 的内建实现:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\\
org.springframework.boot.context.event.EventPublishingRunListener
最后,就是将该集合传入 SpringApplicationRunListeners 的构造方法:
class SpringApplicationRunListeners {
//...
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
//...
}
里面是将集合赋值到 listeners 属性,可以看到 SpringApplicationRunListeners 属于组合模式的实现,核心其实是内部关联的 SpringApplicationRunListener 对象集合,当外部调用该阶段方法时,就会迭代执行集合中 SpringApplicationRunListener 对应的方法。所以接下来我们就来讨论 SpringApplicationRunListener。
2.1.1 SpringApplicationRunListener 事件和监听机制
SpringApplicationRunListener 负责在 SpringBoot 的不同阶段广播相应的事件,然后调用实际的 ApplicationListener 类,在该类的 onApplicationEvent 方法中,根据不同的 Spring Boot 事件执行相应操作。整个过程大概如此,接下来进行详细讨论,先来看看 SpringApplicationRunListener 定义:
public interface SpringApplicationRunListener {
// 在run()方法开始执行时被调用,表示应用刚刚启动,对应的 Spring Boot 事件为 ApplicationStartingEvent
void starting();
// ConfigurableEnvironment 构建完成时调用,对应的 Spring Boot 事件为 ApplicationEnvironmentPreparedEvent
void environmentPrepared(ConfigurableEnvironment environment);
// ApplicationContext 构建完成时调用,对应的 Spring Boot 事件为 ApplicationContextInitializedEvent
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext 完成加载但还未启动时调用,对应的 Spring Boot 事件为 ApplicationPreparedEvent
void contextLoaded(ConfigurableApplicationContext context);
// ApplicationContext 已启动,但 callRunners 还未执行时调用,对应的 Spring Boot 事件为 ApplicationStartedEvent
void started(ConfigurableApplicationContext context);
// ApplicationContext 启动完毕被调用,对应的 Spring Boot 事件为 ApplicationReadyEvent
void running(ConfigurableApplicationContext context);
// 应用出错时被调用,对应的 Spring Boot 事件为 ApplicationFailedEvent
void failed(ConfigurableApplicationContext context, Throwable exception);
}
我们来看看它的实现类,也就是上面加载的 spring.factories 文件中的 EventPublishingRunListener 类,该类也是 Spring Boot 内建的唯一实现类,具体广播事件的操作在该类中进行,代码如下:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
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);
}
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
//...
}
可以看到,通过构造方法创建 EventPublishingRunListener 实例的过程中,调用了 getListeners 方法,将 SpringApplication 中所有 ApplicationListener 监听器关联到了 initialMulticaster 属性中。没错,这里的 ApplicationListener 监听器就是上篇文章中在 SpringApplication 准备阶段从 spring.factories 文件加载的 key 为 ApplicationListener 的实现类集合,该实现类集合全部重写了 onApplicationEvent 方法。
2.1.2 SimpleApplicationEventMulticaster 广播器
这里又引出了另一个类, 也就是 SimpleApplicationEventMulticaster ,该类是 Spring 的事件广播器,也就是通过它来广播各种事件。接着,当外部迭代的执行到 EventPublishingRunListener 的 starting 方法时,会通过 SimpleApplicationEventMulticaster 的 multicastEvent 方法进行事件的广播,这里广播的是 ApplicationStartingEvent 事件,我们进入 multicastEvent 方法:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
//...
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
}
通过 getApplicationListeners 方法,根据事件类型返回从上面关联的 ApplicationListener
集合中筛选出匹配的
通过 getApplicationListeners方法,根据事件类型返回从上面关联ApplicationListener 集合中筛选出匹配的 ApplicationListener集合,根据 Spring Boot版本的不同,在这个阶段获取到的监听器也有可能不同,如 2.1.6.BUILD-SNAPSHOT版本返回的是:
然后依次遍历这些监听器,同步或异步的调用 invokeListener 方法:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
}
//...
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
String msg = ex.getMessage();
if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
// -> let's suppress the exception and just log a debug message.
Log logger = LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: " + listener, ex);
}
}
else {
throw ex;
}
}
}
可以看到,最终调用的是 doInvokeListener 方法,在该方法中执行了 ApplicationListener 的 onApplicationEvent 方法,入参为广播的事件对象。我们就拿其中一个的监听器来看看 onApplicationEvent 中的实现,如 BackgroundPreinitializer 类:
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {
//...
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
&& event instanceof ApplicationStartingEvent && preinitializationStarted.compareAndSet(false, true)) {
performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
&& preinitializationStarted.get()) {
try {
preinitializationComplete.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
//...
}
在该方法中,通过 instanceof 判断事件的类型,从而进行相应的操作。该监听器主要的操作是新建一个后台线程去执行那些耗时的初始化工作,包括验证器、消息转换器等。LoggingApplicationListener 监听器则是对 Spring Boot 的日志系统做一些初始化的前置操作。另外两个监听器在该阶段无任何操作。
至此,SpringBoot 事件机制的整体流程大概如此,我们简要回顾一下几个核心组件:
- SpringApplicationRunListeners:首先,在 run 方法的执行过程中,通过该类在 SpringBoot 不同的阶段调用不同的阶段方法,如在刚启动阶段调用的 starting 方法。
- SpringApplicationRunListener:而 SpringApplicationRunListeners 属于组合模式的实现,它里面关联了 SpringApplicationRunListener 实现类集合,当外部调用阶段方法时,会迭代执行该集合中的阶段方法。实现类集合是 spring.factories 文件中定义好的类。这里是一个扩展点,详细的后面述说。
- EventPublishingRunListener:该类是 Spring Boot 内置的 SpringApplicationRunListener 唯一实现类,所以,当外部调用各阶段的方法时,真正执行的是该类中的方法。
- SimpleApplicationEventMulticaster:在阶段方法中,会通过 Spring 的 SimpleApplicationEventMulticaster 事件广播器,广播各个阶段对应的事件,如这里的 starting 方法广播的事件是 ApplicationStartingEvent 。
- ApplicationListener:最后 ApplicationListener 的实现类也就是 Spring Boot 监听器会监听到广播的事件,根据不同的事件,进行相应的操作。这里的 Spring Boot 监听器是也是在 spring.factories 中定义好的,这里我们也可自行扩展。
到这里 Spring Boot 事件监听机制差不多就结束了,值得注意的是 Spring Boot 监听器实现的是 Spring 的 ApplicationListener 类,事件类最终继承的也是 Spring 的 ApplicationEvent 类,所以,Spring Boot 的事件和监听机制都基于 Spring 而实现的。
2.2 ApplicationArguments 加载启动参数
当执行完 listeners.starting 方法后,接着进入构造 ApplicationArguments 阶段:
public class SpringApplication {
//...
public ConfigurableApplicationContext run(String... args) {
//...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//...
}
}
//...
}
该类是用于简化 Spring Boot应用启动参数的封装接口,我们启动项目时输入的命令参数会封装在该类中。一种是通过 IDEA 输入的参数,如下:
另一种是 springboot jar包运行时传递的参数:cmd中运行java -jar xxx.jar name=卢俊义 pwa=abc 。
然后,可以通过 @Autowired 注入 ApplicationArguments 的方式进行使用:
public class Test {
@Autowired
private ApplicationArguments applicationArguments;
public void getArgs() {
// 获取 args 中的所有 non option 参数
applicationArguments.getNonOptionArgs();
// 获取 args 中所有的 option 参数的 name
applicationArguments.getOptionNames();
// 获取传递给应用程序的原始未处理参数
applicationArguments.getSourceArgs();
// 获取 args 中指定 name 的 option 参数的值
applicationArguments.getOptionValues("nmae");
// 判断从参数中解析的 option 参数是否包含指定名称的选项
applicationArguments.containsOption("name");
}
}
2.3 ConfigurableEnvironment 加载外部化配置
接着进入构造 ConfigurableEnvironment 的阶段,该类是用来处理我们外部化配置的,如 properties、YAML 等,提供对配置文件的基础操作。当然,它能处理的外部配置可不仅仅如此,详细的在下篇文章讨论,这里我们进行简要了解即可,进入创建该类的 prepareEnvironment 方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布ApplicationEnvironmentPreparedEvent,
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
这里通过 getOrCreateEnvironment 方法返回具体的 Environment:
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
可以看到,这里通过 webApplicationType 属性来判断当前应用的类型,有 Servlet 、 Reactive 、 非Web 3种类型,该属性也是在上篇文章中 SpringApplication 准备阶段确定的,这里我们通常都是 Servlet 类型,返回的是 StandardServletEnvironment 实例。
之后,还调用了 SpringApplicationRunListeners 的 environmentPrepared 阶段方法,表示 ConfigurableEnvironment 构建完成,同时向 Spring Boot 监听器发布 ApplicationEnvironmentPreparedEvent 事件。监听该事件的监听器有:
2.4 ConfigurableApplicationContext 创建 Spring 应用上下文
这里通过 createApplicationContext 方法创建 Spring 应用上下文,实际上 Spring 的应用上下文才是驱动 Spring Boot 的核心引擎:
public class SpringApplication {
//...
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
//...
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);
}
//...
}
这里也是通过 webApplicationType 属性来确定应用类型从而创建 String 上下文,上篇文章说到该属性值是在 Spring Boot 准备阶段推导出来的。这里我们的应用类型是 Servlet ,所以创建的是 AnnotationConfigServletWebServerApplicationContext 对象。创建完 Spring 应用上下文之后,执行 prepareContext 方法进入准备上下文阶段:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(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);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
我们来看看主要做了哪些操作:
- 设置了 Spring 应用上下文的 ApplicationArguments,上面说过是处理外部化配置的,具体类型为 StandardServletEnvironment 。
- Spring 应用上下文后置处理,主要是覆盖当前 Spring 应用上下文默认所关联的 ResourceLoader 和 ClassLoader 。
- 执行 Spring 的初始化器,上篇文章说过在 Spring Boot 准备阶段初始化了一批在 spring.factories 文件中定义好的 ApplicationContextInitializer ,这里就是执行它们的 initialize 方法,同时这里也是一个扩展点,后面详细讨论。
- 执行 SpringApplicationRunListeners 的 contextPrepared 阶段方法,表示 ApplicationContext 准备完成,同时向 Spring Boot 监听器发布 ApplicationContextInitializedEvent 事件 。
- 将 springApplicationArguments 和 springBootBanner 注册为 Bean。
- 加载 Spring 应用上下文的配置源,也是在上篇文章 Spring Boot 准备阶段获取的 primarySources 和 sources ,primarySources 来源于 SpringApplication 构造器参数,sources 则来源于自定义配置的 setSources 方法。
- 最后执行 SpringApplicationRunListeners 的 contextLoaded 阶段方法,表示 ApplicationContext 完成加载但还未启动,同时向 Spring Boot 监听器发布 ApplicationPreparedEvent 事件 。
接下来就是真正启动阶段,执行的是 refreshContext 方法:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
可以看到,底层调用的是 AbstractApplicationContext 的 refresh 方法,到这里 Spring 应用正式启动,Spring Boot 核心特性也随之启动,如自动装配。随后执行 SpringApplicationRunListeners 的 started 阶段方法,表示 ApplicationContext 已启动,同时向 Spring Boot 监听器发布 ApplicationStartedEvent 事件 。但还未启动完成,后面还有一个 callRunners 方法,一般来讲,里面执行一些我们自定义的操作。之后 Spring 应用才算启动完成,随后调用 running 方法,发布 ApplicationReadyEvent 事件。至此,SpringApplication 运行阶段结束。
3、总结
最后来对 SpringApplication 运行阶段做一个总结。这个阶段核心还是以启动 Spring 应用上下文为主,同时根据应用类型来初始化不同的上下文对象,但这些对象的基类都是 Spring 的 ConfigurableApplicationContext 类。且在启动的各个阶段中,使用 SpringApplicationRunListeners 进行事件广播,回调 Spring Boot 的监听器。同时还初始化了 ApplicationArguments 、ConfigurableEnvironment 等几个组件。下篇文章我们就来讨论 Spring Boot 的外部化配置部分,来看看为什么外部的各个组件,如 Redis、Dubbo 等在 properties 文件中进行相应配置后,就可以正常使用。
相关推荐
- 前端入门——css 网格轨道详细介绍
-
上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...
- Islands Architecture(孤岛架构)在携程新版首页的实践
-
一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...
- HTML中script标签中的那些属性
-
HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...
- CSS 中各种居中你真的玩明白了么
-
页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...
- CSS样式更改——列表、表格和轮廓
-
上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...
- 一文吃透 CSS Flex 布局
-
原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...
- css实现多行文本的展开收起
-
背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...
- css 垂直居中的几种实现方式
-
前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...
- WordPress固定链接设置
-
WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...
- 面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿
-
前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...
- 3种CSS清除浮动的方法
-
今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...
- 2025 年 CSS 终于要支持强大的自定义函数了?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...
- css3属性(transform)的一个css3动画小应用
-
闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...
- CSS基础知识(七)CSS背景
-
一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...
- CSS 水平居中方式二
-
<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)