SpringBoot和SpringCloud的配置文件的加载(源码级解读)

简介: SpringBoot和SpringCloud的配置文件的加载(源码级解读)

SpringBoot版本 2.3.5.RELEASE

SpringCloud版本 Hoxton.SR9


本文只讨论配置文件加载,以bootstrap.yml和application.yml为例,后缀名的加载顺序可以通过源码看到.


bootstrap.yml是SpringCloud使用的配置文件,SpringBoot中其实并没有加载bootStrap.yml的默认实现



1. 概述

SpringBoot加载配置文件的方式是使用了观察者模式,在启动时发出一个事件(ApplicationEnvironmentPreparedEvent),然后基于这个事件,来做配置文件的加载或者其他的一些操作,这种模式扩展性较强.

而bootstrap.yml的加载就借助了这种模式,SpringCloud扩展了一个BootstrapApplicationListener监听器,来处理该事件,在这个监听器里做加载.

2. application.yml的加载

先看Springboot配置文件的加载

从springBoot启动起

2.1 启动类xxxApplication中 SpringApplication.run()

publicstaticvoidmain(String[] args) {
SpringApplication.run(Demo1Application.class, args);
    }


2.2 进入run方法 内部实现再次进入run方法,再次进入run方法

publicstaticConfigurableApplicationContextrun(Class<?>primarySource, String... args) {
returnrun(newClass<?>[] { primarySource }, args);
 }
/*** Static helper that can be used to run a {@link SpringApplication} from the* specified sources using default settings and user supplied arguments.* @param primarySources the primary sources to load* @param args the application arguments (usually passed from a Java main method)* @return the running {@link ApplicationContext}*/publicstaticConfigurableApplicationContextrun(Class<?>[] primarySources, String[] args) {
// 此处new SpringApplication 进行一些默认的初始化returnnewSpringApplication(primarySources).run(args);
 }


2.3 prepareEnvironment

/*** 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}*/publicConfigurableApplicationContextrun(String... args) {
StopWatchstopWatch=newStopWatch();
stopWatch.start();
ConfigurableApplicationContextcontext=null;
Collection<SpringBootExceptionReporter>exceptionReporters=newArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListenerslisteners=getRunListeners(args);
listeners.starting();
try {
ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(args);
// 在此处进行环境的处理 也就是配置文件的的读取和加载ConfigurableEnvironmentenvironment=prepareEnvironment(listeners, applicationArguments); 
configureIgnoreBeanInfo(environment);
BannerprintedBanner=printBanner(environment);
context=createApplicationContext();
exceptionReporters=getSpringFactoriesInstances(SpringBootExceptionReporter.class,
newClass[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
   }
listeners.started(context);
callRunners(context, applicationArguments);
    }
catch (Throwableex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
thrownewIllegalStateException(ex);
    }
try {
listeners.running(context);
    }
catch (Throwableex) {
handleRunFailure(context, ex, exceptionReporters, null);
thrownewIllegalStateException(ex);
    }
returncontext;
 }


2.4 进入prepareEnvironment方法,此时会进入执行监听器的environmentPrepared方法

privateConfigurableEnvironmentprepareEnvironment(SpringApplicationRunListenerslisteners,
ApplicationArgumentsapplicationArguments) {
// Create and configure the environmentConfigurableEnvironmentenvironment=getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 执行监听器的方法listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment=newEnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
    }
ConfigurationPropertySources.attach(environment);
returnenvironment;
 }


2.5 进入environmentPrepared方法,此处循环所有的监听器,并执行方法

voidenvironmentPrepared(ConfigurableEnvironmentenvironment) {
for (SpringApplicationRunListenerlistener : this.listeners) {
listener.environmentPrepared(environment);
    }
 }


可以通过debug看到只有一个监听器

2.6 进入listener.environmentPrepared,创建一个事件并执行事件

@OverridepublicvoidenvironmentPrepared(ConfigurableEnvironmentenvironment) {
this.initialMulticaster    .multicastEvent(newApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
 }


2.7 进入该方法 multicastEvent,并再次进入重载方法 multicastEvent


@OverridepublicvoidmulticastEvent(finalApplicationEventevent, @NullableResolvableTypeeventType) {
ResolvableTypetype= (eventType!=null?eventType : resolveDefaultEventType(event));
Executorexecutor=getTaskExecutor();
// getApplicationListeners 获取监听器列表for (ApplicationListener<?>listener : getApplicationListeners(event, type)) {
if (executor!=null) {
executor.execute(() ->invokeListener(listener, event));
   }
else {
// 执行处理方法invokeListener(listener, event);
   }
    }
 }


可以通过debug进入getApplicationListeners看一下,这里根据事件的类型查询可以处理该事件的监听器

其中ConfigFileApplicationListener就是重点对象,就是这个监听器加载了配置文件

2.8 invokeListener 调用监听器

继续进入重载方法

/*** Invoke the given listener with the given event.* @param listener the ApplicationListener to invoke* @param event the current event to propagate* @since 4.1*/protectedvoidinvokeListener(ApplicationListener<?>listener, ApplicationEventevent) {
ErrorHandlererrorHandler=getErrorHandler();
if (errorHandler!=null) {
try {
doInvokeListener(listener, event);
   }
catch (Throwableerr) {
errorHandler.handleError(err);
   }
    }
else {
doInvokeListener(listener, event);
    }
 }
// 重载方法privatevoiddoInvokeListener(ApplicationListenerlistener, ApplicationEventevent) {
try {
// 可以看到是调用了对应监听器的onApplicationEvent方法listener.onApplicationEvent(event);
    }
catch (ClassCastExceptionex) {
Stringmsg=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.Loglogger=LogFactory.getLog(getClass());
if (logger.isTraceEnabled()) {
logger.trace("Non-matching event type for listener: "+listener, ex);
    }
   }
else {
throwex;
   }
    }
 }


2.9 ConfigFileApplicationListener$onApplicationEvent方法

ConfigFileApplicationListener就是最终进行操作的类,在这个类里定义了配置文件默认目录和默认名字

进入onApplicationEvent方法

看到基于不同的事件,进行不同的处理

@OverridepublicvoidonApplicationEvent(ApplicationEventevent) {
if (eventinstanceofApplicationEnvironmentPreparedEvent) {
// 进入此方法,加载配置文件onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    }
if (eventinstanceofApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
    }
 }


2.10 配置文件的加载

接下来就是配置文件的加载,接下面的源码就是分析SpringBoot如何加载application-xxx.yml

2.10.1 onApplicationEnvironmentPreparedEvent方法

紧跟上文进入onApplicationEnvironmentPreparedEvent方法

privatevoidonApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEventevent) {
// 根据spring.factories文件加载处理器. 有兴趣可以debug进去看下List<EnvironmentPostProcessor>postProcessors=loadPostProcessors();
// 把ConfigFileApplicationListener也加进去postProcessors.add(this);
// 根据Order排序处理器AnnotationAwareOrderComparator.sort(postProcessors);
// 循环执行处理器的处理方法for (EnvironmentPostProcessorpostProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
    }
 }


此处重点在于把ConfigFileApplicationListener加载到了postProcessors中,

可以debug看一下postProcessors

然后接下来就会执行ConfigFileApplicationListener.postProcessEnvironment方法

2.10.2 postProcessEnvironment

进入ConfigFileApplicationListener.postProcessEnvironment方法

在进入addPropertySources方法,

可以看到new Loader(environment, resourceLoader).load();

Loader是ConfigFileApplicationListener的一个内部类,在load方法内进行配置文件的加载

@OverridepublicvoidpostProcessEnvironment(ConfigurableEnvironmentenvironment, SpringApplicationapplication) {
addPropertySources(environment, application.getResourceLoader());
}
/*** Add config file property sources to the specified environment.* @param environment the environment to add source to* @param resourceLoader the resource loader* @see #addPostProcessors(ConfigurableApplicationContext)*/protectedvoidaddPropertySources(ConfigurableEnvironmentenvironment, ResourceLoaderresourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
newLoader(environment, resourceLoader).load();
}


2.10.3 load方法

这串代码就是配置文件的加载

voidload() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
                                 (defaultProperties) -> {
// 初始化配置this.profiles=newLinkedList<>();
this.processedProfiles=newLinkedList<>();
// 默认启用false this.activatedProfiles=false;
this.loaded=newLinkedHashMap<>();
// 初始化配置, 也就是defaultinitializeProfiles();
// 初始化后this.profiles会有一个default的配置while (!this.profiles.isEmpty()) {
Profileprofile=this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
                                         }
// 进入此方法就可以看到配置文件的加载了 addToLoaded是一个回调,主要是配置属性的合并load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
                                     }
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
                                 });
}


initializeProfiles后会默认初始化一个default的配置

循环配置调用重载的load方法

2.10.4 根据profile获取配置文件目录和配置文件名

进入上文中的load方法

privatevoidload(Profileprofile, DocumentFilterFactoryfilterFactory, DocumentConsumerconsumer) {
// getSearchLocations这个会获取默认的配置文件路径 getSearchLocations().forEach((location) -> {
// 是否是目录,默认配置文件路径都是以/结尾的booleanisDirectory=location.endsWith("/");
// 获取默认的配置文件名称也就是spring.config.name属性 默认applicationSet<String>names=isDirectory?getSearchNames() : NO_SEARCH_NAMES;
// 再次循环 调用load重载方法names.forEach((name) ->load(location, name, profile, filterFactory, consumer));
    });
}


可以看到getSearchLocations方法会获取配置文件路径,如下,正是SpringBoot默认的配置文件加载顺序,但是这个顺序是反过来的


2.10.5 循环配置文件后缀名

进入load重载方法

privatevoidload(Stringlocation, Stringname, Profileprofile, DocumentFilterFactoryfilterFactory,
DocumentConsumerconsumer) {
// 如果配置文件名称为空进行处理,默认都是有值的 applicationif (!StringUtils.hasText(name)) {
for (PropertySourceLoaderloader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
            }
        }
thrownewIllegalStateException("File extension of config file location '"+location+"' is not known to any PropertySourceLoader. If the location is meant to reference "+"a directory, it must end in '/'");
    }
Set<String>processed=newHashSet<>();
// this.propertySourceLoaders 配置文件加载器默认有两种实现 yaml和propertiesfor (PropertySourceLoaderloader : this.propertySourceLoaders) {
// 根据配置文件加载器循环for (StringfileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
// 根据路径+名字+文件后缀名加载配置文件 loadForFileExtension(loader, location+name, "."+fileExtension, profile, filterFactory,
consumer);
            }
        }
    }
}


this.propertySourceLoaders看一下


2.10.6 loadForFileExtension方法

继续重载

privatevoidloadForFileExtension(PropertySourceLoaderloader, Stringprefix, StringfileExtension,
Profileprofile, DocumentFilterFactoryfilterFactory, DocumentConsumerconsumer) {
DocumentFilterdefaultFilter=filterFactory.getDocumentFilter(null);
DocumentFilterprofileFilter=filterFactory.getDocumentFilter(profile);
// 当profile不为空时处理即 active.profiles=xxx时 第一次进行不走这,还是默认的if (profile!=null) {
// Try profile-specific file & profile section in profile file (gh-340)StringprofileSpecificFile=prefix+"-"+profile+fileExtension;
load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processedfor (ProfileprocessedProfile : this.processedProfiles) {
if (processedProfile!=null) {
StringpreviouslyLoaded=prefix+"-"+processedProfile+fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
            }
        }
    }
// 默认的配置文件处理 default// Also try the profile-specific section (if any) of the normal fileload(loader, prefix+fileExtension, profile, profileFilter, consumer);
}


2.10.7 配置文件的读取和属性合并

这次重载就是到头了,在这个方法里就会进行属性的读取

privatevoidload(PropertySourceLoaderloader, Stringlocation, Profileprofile, DocumentFilterfilter,
DocumentConsumerconsumer) {
// 根据2.10.6 中拼接的路径加载Resource[] resources=getResources(location);
for (Resourceresource : resources) {
try {
// 当该拼接的文件不存在时,会直接进行下一次循环if (resource==null||!resource.exists()) {
if (this.logger.isTraceEnabled()) {
StringBuilderdescription=getDescription("Skipped missing config ", location, resource,
profile);
this.logger.trace(description);
                }
continue;
            }
// 配置文件为空时也不加载if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) {
if (this.logger.isTraceEnabled()) {
StringBuilderdescription=getDescription("Skipped empty config extension ", location,
resource, profile);
this.logger.trace(description);
                }
continue;
            }
// 隐藏路径时返回 安全?if (resource.isFile() &&hasHiddenPathElement(resource)) {
if (this.logger.isTraceEnabled()) {
StringBuilderdescription=getDescription("Skipped location with hidden path element ",
location, resource, profile);
this.logger.trace(description);
                }
continue;
            }
// 开始正式加载Stringname="applicationConfig: ["+getLocationName(location, resource) +"]";
// 加载配置文件的元素List<Document>documents=loadDocuments(loader, name, resource);
// 如果配置文件里没有解析出元素if (CollectionUtils.isEmpty(documents)) {
if (this.logger.isTraceEnabled()) {
StringBuilderdescription=getDescription("Skipped unloaded config ", location, resource,
profile);
this.logger.trace(description);
                }
continue;
            }
List<Document>loaded=newArrayList<>();
// 默认active profile 为defaultfor (Documentdocument : documents) {
if (filter.match(document)) {
addActiveProfiles(document.getActiveProfiles());
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
                }
            }
// 反转属性顺序 目前没看到啥作用.Collections.reverse(loaded);
if (!loaded.isEmpty()) {
// consumer.accept(profile, document)在回调里合并属性 即高优先级的覆盖低优先级的属性loaded.forEach((document) ->consumer.accept(profile, document));
if (this.logger.isDebugEnabled()) {
StringBuilderdescription=getDescription("Loaded config file ", location, resource,
profile);
this.logger.debug(description);
                }
            }
        }
catch (Exceptionex) {
StringBuilderdescription=getDescription("Failed to load property source from ", location,
resource, profile);
thrownewIllegalStateException(description.toString(), ex);
        }
    }
}


  1. loadDocuments方法可以进去看下,PropertiesPropertySourceLoader 和 YamlPropertySourceLoader加载配置文件的详细代码,其实就是读取文件
  2. Collections.reverse(loaded);这个方法印证了SpringBoot配置文件的加载顺序,在上文中我们看到了SpringBoot读取配置文件目录的顺序是
  1. 按这个顺序加载文件.这也印证了SpringBoot配置文件的加载优先级
  2. consumer.accept(profile, document),回调中合并

2.10.8 回调合并属性 addToLoaded

privateDocumentConsumeraddToLoaded(BiConsumer<MutablePropertySources, PropertySource<?>>addMethod,
booleancheckForExisting) {
return (profile, document) -> {
if (checkForExisting) { // 检查属性是否存在,如果是第一次加载默认的配置文件这个参数为false,2.10.3中可以看到for (MutablePropertySourcesmerged : this.loaded.values()) {
// 如果参数已存在 就不在加载了if (merged.contains(document.getPropertySource().getName())) {
return;
                }
            }
        }
// 第一次加载或者属性不存在的化直接放入this.loadedMutablePropertySourcesmerged=this.loaded.computeIfAbsent(profile,
                                                                    (k) ->newMutablePropertySources());
// 调用回调 MutablePropertySources::addLastaddMethod.accept(merged, document.getPropertySource());
    };
}


addToLoaded 主要就是为了高优先级的属性覆盖低优先级的属性

2.11 总结

SpringBoot基于观察者模式,在ApplicationEnvironmentPreparedEvent事件中,对配置文件加载,配置文件的加载主要在ConfigFileApplicationListener这个类中,基于PropertiesPropertySourceLoader 和 YamlPropertySourceLoader加载配置文件的属性,并根据加载文件的顺序来做高优先级的覆盖低优先级的属性.


3.bootstrap.yml的加载

bootstrap.yml的加载其实还是使用SpringBoot加载配置文件,只不过在SpringBoot的加载之前,先创建了一个名为bootstrap(默认)的context

3.1 BootstrapApplicationListener

引入SpringCloud的依赖后

从上文中2.7开始,会发现listeners多了一个,并且优先级相当高

进入该监听器

3.2 BootstrapApplicationListener的onApplicationEvent

进入BootstrapApplicationListener的onApplicationEvent方法,在此方法中进行bootsrap.yml的读取


StringconfigLocation=environment    .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
StringconfigAdditionalLocation=environment    .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
Map<String, Object>bootstrapMap=newHashMap<>();
// 给配置文件一个名字 加载bootstrap,yml就是靠这个bootstrapMap.put("spring.config.name", configName);
@OverridepublicvoidonApplicationEvent(ApplicationEnvironmentPreparedEventevent) {
// 获取上下文信息ConfigurableEnvironmentenvironment=event.getEnvironment();
// 判断是否启用spring.cloud.bootstrap.enabled属性,默认启用,如果不启用就不加载bootstrap.yml文件,直接结束if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
true)) {
return;
    }
// 如果已经执行过bootstrap的监听事件,就不再重复执行了,这也是为什么bootstrap.yml属性不变的原因.// don't listen to events in a bootstrap contextif (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
return;
    }
// 开始初始化上下文ConfigurableApplicationContextcontext=null;
// 获取配置文件名称 默认bootstrap (bootstrap.yml) 就是这里给配置文件复制了StringconfigName=environment    .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
// 寻找是否有父容器上下文的初始化器 ParentContextApplicationContextInitializer 正常情况下是没有的 直接往下走for (ApplicationContextInitializer<?>initializer : event.getSpringApplication()
    .getInitializers()) {
if (initializerinstanceofParentContextApplicationContextInitializer) {
context=findBootstrapContext(
        (ParentContextApplicationContextInitializer) initializer,
configName);
   }
    }
// 没有ParentContextApplicationContextInitializer 父容器初始化的化,就创建一个if (context==null) {
context=bootstrapServiceContext(environment, event.getSpringApplication(),
configName);
event.getSpringApplication()
     .addListeners(newCloseContextOnFailureApplicationListener(context));
    }
apply(context, event.getSpringApplication(), environment);
 }


进入bootstrapServiceContext方法,方法太长不再全部粘贴,这个方法里最重要的就是根据bootstrap.yml创建出一个SpringApplication对象

// 创建SpringApplication对象 SpringApplicationBuilderbuilder=newSpringApplicationBuilder()
    .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
    .environment(bootstrapEnvironment)
// Don't use the default properties in this builder    .registerShutdownHook(false).logStartupInfo(false)
    .web(WebApplicationType.NONE);
finalSpringApplicationbuilderApplication=builder.application();
// 创建context对象 注意: 此处是又执行了一次 SpringApplication.run()方法.builder.sources(BootstrapImportSelectorConfiguration.class);
finalConfigurableApplicationContextcontext=builder.run();
// 设置父容器对象addAncestorInitializer(application, context);
// It only has properties in it now that we don't want in the parent so remove// it (and it will be added back later)bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
// 合并属性mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);


3.3 加载bootStarp.yml配置文件原理

加载配置文件还是上面SpringBoot加载application那一套,但是不同的是SpringBoot给的默认的application在这里并没有使用,而是使用了BootStrapApplicationListener中设置的spring.config.name 如下图.

在上文中2.10.4中使用getSearchNames获取要加载的文件名,看下这个方法

privateSet<String>getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { //SpringBoot默认不走这个 bootstrap.yml的加载就是依赖这里Stringproperty=this.environment.getProperty(CONFIG_NAME_PROPERTY); 
Set<String>names=asResolvedSet(property, null);
names.forEach(this::assertValidConfigName);
returnnames;
    }
returnasResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); // 返回默认的Application}


这样一来,除了加载文件的名字改变了,其他的都没变,还是SpringBoot这一套.



4. 总结

通过对SpringBoot2.3.5的源码的阅读,学习到如下:

  1. SpringBoot启动时基于事件处理,增加了扩展性,SpringCloud配置文件的加载就是这样
  2. SpringBoot和SpringCloud配置文件的加载,从源码角度上分析配置文件的加载,还有顺序,到底是如何覆盖的
  3. SpringBoot的代码写的真好呀!!
目录
相关文章
|
1月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
7天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
52 13
|
14天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
19天前
|
负载均衡 Java 开发者
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
62 5
|
25天前
|
监控 IDE Java
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
如何在无需重新启动服务器的情况下在 Spring Boot 上重新加载我的更改?
43 8
|
1月前
|
Java 数据库连接 数据库
springboot启动配置文件-bootstrap.yml常用基本配置
以上是一些常用的基本配置项,在实际应用中可能会根据需求有所变化。通过合理配置 `bootstrap.yml`文件,可以确保应用程序在启动阶段加载正确的配置,并顺利启动运行。
118 2
|
1月前
|
Java Spring 容器
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
SpringBoot读取配置文件的6种方式,包括:通过Environment、@PropertySource、@ConfigurationProperties、@Value读取配置信息
97 3
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
2月前
|
Java 测试技术 Spring
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
这篇文章介绍了Spring Boot中配置文件的语法、如何读取配置文件以及如何通过静态工具类读取配置文件。
148 0
springboot学习三:Spring Boot 配置文件语法、静态工具类读取配置文件、静态工具类读取配置文件
下一篇
DataWorks