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的代码写的真好呀!!
目录
相关文章
|
4月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
428 1
|
5月前
|
前端开发 Java 关系型数据库
基于Java+Springboot+Vue开发的鲜花商城管理系统源码+运行
基于Java+Springboot+Vue开发的鲜花商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的鲜花商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。技术学习共同进步
413 7
|
6月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1514 1
|
5月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
376 70
|
4月前
|
供应链 JavaScript BI
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
这是一款专为小微企业打造的 SaaS ERP 管理系统,基于 SpringBoot+Vue+ElementUI+UniAPP 技术栈开发,帮助企业轻松上云。系统覆盖进销存、采购、销售、生产、财务、品质、OA 办公及 CRM 等核心功能,业务流程清晰且操作简便。支持二次开发与商用,提供自定义界面、审批流配置及灵活报表设计,助力企业高效管理与数字化转型。
433 2
ERP系统源码,基于SpringBoot+Vue+ElementUI+UniAPP开发
|
3月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
240 0
|
6月前
|
小程序 Java 关系型数据库
weixin117新闻资讯系统设计+springboot(文档+源码)_kaic
本文介绍了一款基于微信小程序的新闻资讯系统,涵盖其开发全过程。该系统采用Java的SSM框架进行后台管理开发,使用MySQL作为本地数据库,并借助微信开发者工具确保稳定性。管理员可通过个人中心、用户管理等功能模块实现高效管理,而用户则能注册登录并查看新闻与视频内容。系统设计注重可行性分析(技术、经济、操作),强调安全性与数据完整性,界面简洁易用,功能全面,极大提升了信息管理效率及用户体验。关键词包括基于微信小程序的新闻资讯系统、SSM框架和MYSQL数据库。
|
7月前
|
小程序 JavaScript Java
基于SpringBoot的智慧停车场微信小程序源码分享
智慧停车场微信小程序主要包含管理端和小程序端。管理端包括停车场管理,公告信息管理,用户信息管理,预定信息管理,用户反馈管理等功能。小程序端包括登录注册,预约停车位,停车导航,停车缴费,用户信息,车辆信息,钱包充值,意见反馈等功能。
274 5
基于SpringBoot的智慧停车场微信小程序源码分享
|
7月前
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
2875 14
|
7月前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
1154 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践