SpringBoot运行流程源码分析------阶段三(Spring Boot外化配置源码解析)

Spring Boot外化配置源码解析

外化配置简介

Spring Boot设计了非常特殊的加载指定属性文件(PropertySouce)的顺序,允许属性值合理的覆盖,属性值会以下面的优先级进行配置。
  • home目录下的Devtool全局设置属性(~/.spring-boot-devtools.properties,条件是当devtools激活时)
  • @TestPropertySource注解的测试用例。
  • @SpringBootTest#properties注解的测试用例。
  • 命令行参数。
  • 来自SPRING_APPLICATION_JSON的属性(内嵌在环境变量或系统属性中的内联JSON)
  • ServletConfig初始化参数
  • ServletContext初始化参数
  • java:comp/env的JNDI属性
  • Java系统属性(System.getProperties())
  • 操作系统环境变量
  • RandomValuePropertySource,只包含random.*中的属性
  • jar包外的Profile_specific应用属性(application-{profile}.propertis和YAML变量)
  • jar包内的Profile_specific应用属性(application-{profile}.propertis和YAML变量)
  • jar包外的应用配置(application.properties和YAML变量)
  • jar包内的应用配置(application.properties和YAML变量)
  • @Configuration类上的@PropertySource注解
  • 默认属性(通过SpringApplication.setDefaultProperties指定)

在以上配置方式中,我们经常使用的包括:命令参数,属性文件,YAML文件等内容,以下将围绕他们的运行及相关代码进行讲解。

ApplicationArguments参数处理

ApplicationArguments提供了针对参数的解析和查询功能。在Spring Boot运行阶段的章节中我们提到过,通过SpringApplication.run(args)传递的参数会被封装在ApplicationArguments接口中。本节我们来详细了解下ApplicationArguments接口。
接口定义及初始化

首先看一下ApplicationArguments接口的具体方法定义及功能介绍。

package org.springframework.boot;import java.util.List;
import java.util.Set;public interface ApplicationArguments {//返回原始未处理的参数(通过application传入的)String[] getSourceArgs();//返回所有参数的集合,如参数为:--foo=bar --debug,则返回【"foo","debug"】Set<String> getOptionNames();//选项参数中是否包含指定名称的参数boolean containsOption(String name);//根据选项参数的名称获取选项参数的值列表List<String> getOptionValues(String name);//返回非选项参数列表List<String> getNonOptionArgs();}

通过接口定义可以看出,ApplicationArguments主要提供了针对参数名称和值的查询,以及判断是否存在指定参数的功能。

在Spring Boot的初始化运行过程中,ApplicationArguments接口的实例化操作默认是通过实现类DefaultApplicationArguments来完成的。DefaultApplicationArguments的底层又是基于Spring框架中的命令行配置源SimpleCommandLinePropertySource实现的,SpringCommandLinePropertySource是PropertySource抽象类的派生类。

以下代码中内部类Source便是SimppleCommandLinePropertySource的子类。

public class DefaultApplicationArguments implements ApplicationArguments {private final Source source;private final String[] args;public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;}......private static class Source extends SimpleCommandLinePropertySource {......}}

我们再来看SimpleCommandLinePropertySource的构造方法,通过代码会发现默认使用spring的SimpleCommandLineArgsParser对args参加进行解析。

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {public SimpleCommandLinePropertySource(String... args) {super((new SimpleCommandLineArgsParser()).parse(args));}//重载的构造方法public SimpleCommandLinePropertySource(String name, String[] args) {super(name, (new SimpleCommandLineArgsParser()).parse(args));}......
}

除了构造方法之外,SimpleCommandLinePropertySource还提供了不同类型参数信息的获取和检查是否存在的功能,代码如下:

public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {......//获取选项参数数组    public String[] getPropertyNames() {return StringUtils.toStringArray(((CommandLineArgs)this.source).getOptionNames());}//获取是否包含指定name的参数protected boolean containsOption(String name) {return ((CommandLineArgs)this.source).containsOption(name);}//获取指定name的选项参数列表@Nullableprotected List<String> getOptionValues(String name) {return ((CommandLineArgs)this.source).getOptionValues(name);}//获取非选项参数列表protected List<String> getNonOptionArgs() {return ((CommandLineArgs)this.source).getNonOptionArgs();}
}

ApplicatinArguments,或者更进一步说是SimpleCommandLinePropertySource对参数类型是有所区分的,即选项参数和非选项参数。

选项参数必须以“–”为前缀,参数值可为空,该参数可以通过Spring Boot属性处理后使用,比如在执行jar -jar命令时,添加选项参数“–app.name=spring boot start",在代码中可以通过注解@Value属性及其他方式获取到该参数的值。该参数可以通过逗号分隔多个参数值,或者多次使用同一个参数来包含多个参数的值。

非选项参数并不要求以“–”前缀开始,可自行定义。非选项参数可以直接在jar -jar命令中定义参数为“non-option"的参数值。

以上所说的选项参数和非选项参数的解析是在SimpleCommandLinePropertySource构造方法中调用SimpleCommandLineArgsParser中完成的,代码如下:

class SimpleCommandLineArgsParser {SimpleCommandLineArgsParser() {}//解析args参数,返回一个完整的CommandLineArgs对象public CommandLineArgs parse(String... args) {CommandLineArgs commandLineArgs = new CommandLineArgs();String[] var3 = args;int var4 = args.length;//遍历参数for(int var5 = 0; var5 < var4; ++var5) {String arg = var3[var5];//解析选项参数,以"--"开头if (arg.startsWith("--")) {String optionText = arg.substring(2, arg.length());String optionValue = null;String optionName;//判断是--foo=bar参数格式,还是-foo参数格式,并分别处理获取值if (optionText.contains("=")) {optionName = optionText.substring(0, optionText.indexOf(61));optionValue = optionText.substring(optionText.indexOf(61) + 1, optionText.length());} else {optionName = optionText;}if (optionName.isEmpty() || optionValue != null && optionValue.isEmpty()) {throw new IllegalArgumentException("Invalid argument syntax: " + arg);}commandLineArgs.addOptionArg(optionName, optionValue);} else {//处理非选项参数commandLineArgs.addNonOptionArg(arg);}}return commandLineArgs;}
}

通过SimpleCommandLineArgsParser的代码可以看出,Spring对参数的解析是按照指定的参数格式分别解析字符串中的值来实现的。最终,解析的结果均封装在CommandLineArgs中。而CommandLineArgs类只是命令行参数的简单表示形式,内部分为“选项参数”和"非选项参数"

class CommandLineArgs {private final Map<String, List<String>> optionArgs = new HashMap();private final List<String> nonOptionArgs = new ArrayList();CommandLineArgs() {}......
}

CommandLineArgs的核心存储结构包括:存储选项参数的Map<String,List>optionArgs和存储非选项参数的ListnonOptionsArgs。同时,针对这两个核心存储接口,SpringBoot也提供了相关的读写操作的方法。

SimpleCommandLineArgsParser解析获得的CommandLineArgs对象,最终会被SimpleCommandLinePropertySource的构造方法通过parser调用,一层层地传递到PropertySource类的构造方法中,最终封装到相应的属性当中。

public abstract class PropertySource<T> {protected final Log logger;//参数类别名称protected final String name;//参数封装类protected final T source;......
}

以在SimpleCommandLinePropertySource中的使用为例,最终封装在PropertySource中的结构为:name为“commandLineArgs”,source为解析出的CommandLineArgs对象。

而DefaultApplicationArguments的内部类Source作为SimpleCommandLinePropertySource的子类存储了以上解析的数据内容。同时,args参数的原始值储存在DefaultApplicationArguments的String[]args属性中。

命令行参数的获取

命令行参数就是在启动Spring Boot项目时通过命令行传递的参数。比如通过一下命令来启动一个Spring Boot项目。

jar -jar app.jar --name=SpringBoot

那么–name=SpringBoot是如何一步步传递到Spring内部的呢?

默认情况下,SpringApplication会将以上类似name的命令行参数(以“–”开头)解析封装成一个PropertySource对象,并将其添加到Spring-Environment当中,而命令行参数的优先级要高于其他配置源。

下面我们来通过代码来追踪启动过程中整个参数的获取,解析和分装过程。首先,参数是通过SpringApplication的run方法的args传递参数。

在SpringApplication的run方法中,通过以下操作先将args封装于ApplicationArguments中,然后又将封装之后的对象传递入prepareEnvironment方法。

public ConfigurableApplicationContext run(String... args) {......try {ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);......}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}......}

在prepareEnvironment方法中,通过applicationArguments.getSourceArgs()获得传递的参数数组,并作为参数调用configureEnvironment方法,此处获得的args依旧是未解析的参数值,代码如下:

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {......configureEnvironment(environment, applicationArguments.getSourceArgs());......}

在configureEnvironment方法中又将参数传递给configurePropertySource方法。

	protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {......configurePropertySources(environment, args);configureProfiles(environment, args);}

而在configurePropertySources方法中才对参数进行了真正的解析和封装

	protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {//获取环境变量中的属性资源信息MutablePropertySources sources = environment.getPropertySources();//如果默认属性配置存在,则将其放置在属性资源的最后位置if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));}//如果命令行属性未被禁用且存在if (this.addCommandLineProperties && args.length > 0) {String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;//如果默认属性资源中不包含该命令则将命令行属性放置在第一位//如果包含则通过CompositePropertySource进行处理if (sources.contains(name)) {PropertySource<?> source = sources.get(name);CompositePropertySource composite = new CompositePropertySource(name);composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));composite.addPropertySource(source);sources.replace(name, composite);}else {//不存在,则添加并放置在第一位sources.addFirst(new SimpleCommandLinePropertySource(args));}}}

因为configurePropertySources方法在之前章节中介绍过,下面针对命令行参数再次进行讲解和深入分析,重点介绍两个内容:参数的优先级和命令行参数的解析。

参数的优先级,从上面的代码注解中可以看到,configurePropertySources方法

  • 第一步获得环境变量中存储配置信息的sources;
  • 第二步判断默认参数是否为空,如果不为空,则将默认参数放置在sources的最后位置,这里已经明显反应了参数的优先级是通过顺序来体现的;
  • 第三步如果命令行参数未被禁用,且不为空,则要么将原有默认参数替换掉,要么直接放在第一位,这一步中的替换操作也是另外一种优先级形式的体现。

在上面代码中,可以通过SpringApplication的setAddCommandLineProperties方法将其设置为false来禁用。命令行参数的解析用到了SimpleCommandLinePropertySource类,而该类的相关使用在上面以详细介绍过。下面将分析配置文件中的参数获取。

配置文件的加载

Spring Boot启动时默认会去加载classpah下的application.yml或application.properties文件。配置文件的加载过程中主要是利用了Spring Boot的事件机制来完成的,也就是我们之前说的SpringApplicationRunListeners中的environmentPrepared方法来启动加载配置文件的事件。通过该方法发布的事件会被注册到ConfigFileApplicationListener监听到,而实现资源的加载。

下面来通过源码的追踪分析这一过程。该事件同样是SpringApplication的run方法来完成的。前半部分的调用过程与上面命令行获取参数的方法调用一样,不同的是当执行到prepareEnvironment中,当执行完configureEnvironment方法之后,便通过事件发布来通知监听器加载资源。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environmentConfigurableEnvironment environment = getOrCreateEnvironment();// 配置环境,主要包括PropertySources和activeProfiles的配置configureEnvironment(environment, applicationArguments.getSourceArgs());ConfigurationPropertySources.attach(environment);//listeners环境准备listeners.environmentPrepared(environment);......}

该事件监听器通过EventPublishingRunListener的environmentPrepared方法发布一个ApplicationEnvironmentPreparedEvent事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {......@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));}......
}

在META-INF/spring.factories中注册的ConfigFileApplicationListener会监听到对应事件,并进行相应的处理。spring.factories中ConfigFileApplicationListener的注册配置如下:

# 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

在ConfiFileApplicationListener类中我们会看到很多与配置文件加载相关的常量。

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {private static final String DEFAULT_PROPERTIES = "defaultProperties";// 默认的加载配置文件路径private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";//默认的配置文件名称private static final String DEFAULT_NAMES = "application";......//激活配置文件的属性名public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";......

通过这些基本的常量,可以看出默认加载配置文件的路径和默认的名称。再回到刚才的事件监听,入口方法为ConfigFileApplicationListener的onApplicationEvent方法。

	@Overridepublic void onApplicationEvent(ApplicationEvent event) {//对应当前发布的事件,执行次业务逻辑if (event instanceof ApplicationEnvironmentPreparedEvent) {onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);}if (event instanceof ApplicationPreparedEvent) {onApplicationPreparedEvent(event);}}

上面调用onApplicationEnvironmentPreparedEvent方法如下,该方法会获得注册的处理器,遍历并依次调用postPropcessEnvironment方法。

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();postProcessors.add(this);AnnotationAwareOrderComparator.sort(postProcessors);//遍历并依次调用postProcessEnvironment方法for (EnvironmentPostProcessor postProcessor : postProcessors) {postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());}}

其中EnvironmentPostProcessor接口的实现类也是在META-INF/spring.factories文件中注册的。

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

ConfigFileApplicationListener本身也是EvironmentPostProcessor接口的实现类,可以跟着ConfigFileApplicationListener中postProcessEnvironment的调用链路代码一直往下看,会发现最后在其内部类Loader的load方法进行配置文件的加载操作。其中关于文件路径及其名称的组合代码如下:

// 1接口类,查找实现类ConfiFileApplicationListener
public interface EnvironmentPostProcessor {void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}// 2 ConfiFileApplicationListener实现类
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {addPropertySources(environment, application.getResourceLoader());
}
// 3 addPropertySources
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {RandomValuePropertySource.addToEnvironment(environment);new Loader(environment, resourceLoader).load();
}
// 4 load()
void load() {FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,(defaultProperties) -> {this.profiles = new LinkedList<>();this.processedProfiles = new LinkedList<>();this.activatedProfiles = false;this.loaded = new LinkedHashMap<>();initializeProfiles();while (!this.profiles.isEmpty()) {Profile profile = this.profiles.poll();if (isDefaultProfile(profile)) {addProfileToEnvironment(profile.getName());}load(profile, this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast, false));this.processedProfiles.add(profile);}load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));addLoadedPropertySources();applyActiveProfiles(defaultProperties);});
}
// 5 load()
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {getSearchLocations().forEach((location) -> {boolean isFolder = location.endsWith("/");Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;names.forEach((name) -> load(location, name, profile, filterFactory, consumer));});
}
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,DocumentConsumer consumer) {......Set<String> processed = new HashSet<>();for (PropertySourceLoader loader : this.propertySourceLoaders) {for (String fileExtension : loader.getFileExtensions()) {if (processed.add(fileExtension)) {loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,consumer);}}}}

在该方法中可以看到loadForFileExtension的第二个参数"文件路径+名称"和第三个参数"扩展名称"的拼接组成方式。location默认值就是常量DEFAULT_SEARCH_LOCATIONS的值。

在for循环中遍历的PropertySourceLoader也是在META-INF/spring.factories中注册的,并且在Loader的构造方法中通过SpringFactoriesLoader的loadFactories方法来获得。

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

当查看PropertiesPropertySourceLoader和YamlPropertySourceLoader两个加载器代码时,就会发现他们分别定义了所支持文件类型及其加载方法。PropertiesPropertySourceLoader支持配置文件类型的定义代码如下:

public class PropertiesPropertySourceLoader implements PropertySourceLoader {private static final String XML_FILE_EXTENSION = ".xml";@Overridepublic String[] getFileExtensions() {return new String[] { "properties", "xml" };}@Overridepublic List<PropertySource<?>> load(String name, Resource resource) throws IOException {Map<String, ?> properties = loadProperties(resource);if (properties.isEmpty()) {return Collections.emptyList();}return Collections.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));}@SuppressWarnings({ "unchecked", "rawtypes" })private Map<String, ?> loadProperties(Resource resource) throws IOException {String filename = resource.getFilename();if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {return (Map) PropertiesLoaderUtils.loadProperties(resource);}return new OriginTrackedPropertiesLoader(resource).load();}}

YamlPropertySourceLoader支持配置文件类型的定义代码如下:

public class YamlPropertySourceLoader implements PropertySourceLoader {@Overridepublic String[] getFileExtensions() {return new String[] { "yml", "yaml" };}@Overridepublic List<PropertySource<?>> load(String name, Resource resource) throws IOException {if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {throw new IllegalStateException("Attempted to load " + name + " but snakeyaml was not found on the classpath");}List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();if (loaded.isEmpty()) {return Collections.emptyList();}List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());for (int i = 0; i < loaded.size(); i++) {String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber,Collections.unmodifiableMap(loaded.get(i)), true));}return propertySources;}}

其中PropertiesPropertySourceLoader对文件的加载通过PropertiesLoaderUtils类(加载xml文件)和OriginTrackedYamlLoader类来完成,而YamlPropertySourceLoader对文件的加载主要通过OriginrackedYamlLoader来完成。

下面以PropertiesPropertySourceLoader使用的OriginTrackedPropertiesLoader为例进行源码分析。

PropertiesPropertySourceLoader中加载相关的代码如下:

public class PropertiesPropertySourceLoader implements PropertySourceLoader {private static final String XML_FILE_EXTENSION = ".xml";@Overridepublic String[] getFileExtensions() {return new String[] { "properties", "xml" };}//加载指定的配置文件@Overridepublic List<PropertySource<?>> load(String name, Resource resource) throws IOException {//调用load方法进行加载并返回Map形式的数据Map<String, ?> properties = loadProperties(resource);if (properties.isEmpty()) {return Collections.emptyList();}//对返回结果进行处理和转换return Collections.singletonList(new OriginTrackedMapPropertySource(name, Collections.unmodifiableMap(properties), true));}//具体加载过程@SuppressWarnings({ "unchecked", "rawtypes" })private Map<String, ?> loadProperties(Resource resource) throws IOException {String filename = resource.getFilename();//加载xml格式if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {return (Map) PropertiesLoaderUtils.loadProperties(resource);}//加载properties格式return new OriginTrackedPropertiesLoader(resource).load();}}

OriginTrackedPropertiesLoader的构造方法非常简单,只是把resource设置给其成员变量Resource。

class OriginTrackedPropertiesLoader {private final Resource resource;OriginTrackedPropertiesLoader(Resource resource) {Assert.notNull(resource, "Resource must not be null");this.resource = resource;}Map<String, OriginTrackedValue> load() throws IOException {return load(true);}//加载properties文件的数据并返回map类型//其中expandLists用于指定参数为"name[]=a,b,c"的列表是否进行扩展,默认为trueMap<String, OriginTrackedValue> load(boolean expandLists) throws IOException {//创建配置文件的readertry (CharacterReader reader = new CharacterReader(this.resource)) {Map<String, OriginTrackedValue> result = new LinkedHashMap<>();StringBuilder buffer = new StringBuilder();//读取文件中数据while (reader.read()) {//读取文件中的keyString key = loadKey(buffer, reader).trim();//可扩展列表的处理if (expandLists && key.endsWith("[]")) {key = key.substring(0, key.length() - 2);int index = 0;do {OriginTrackedValue value = loadValue(buffer, reader, true);put(result, key + "[" + (index++) + "]", value);if (!reader.isEndOfLine()) {reader.read();}}while (!reader.isEndOfLine());}else {//读取文件中value并封装为OriginTrackedValueOriginTrackedValue value = loadValue(buffer, reader, false);put(result, key, value);}}return result;}}
}

以上代码展示了OriginTrackedPropertiesLoader的load方法的核心功能:创建reader读取配置文件,获得配置文件中配置的key,获取配置文件中的value,封装key-value到map中并返回。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用 Go 访问 MySql 数据库

所有代码样例 package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" )var db *sql.DB// 初始化连接 func initDB() (err error) {db, err sql.Open("mysql", "root:mm..1213tcp(127.0.0.1:3306)/chapte…

【JavaEE】网络原理---TCP协议的易懂图文详解(确认应答、超时重传、连接管理、滑动窗口、流量控制)

一、TCP协议 TCP&#xff0c;即Transmission Control Protocol&#xff0c;传输控制协议。人如其名&#xff0c;要对数据的传输进行一个详细的控制。 1.1 TCP协议格式 &#xff08;为了方便排版这样化的&#xff0c;我们从上到下依次理解&#xff09; 二、TCP原理 2.1 确…

【linux系列】创建软连接

文章目录 作用命令创建软连接删除软链接修改软链接 参数 作用 现在服务器使用的时候&#xff0c;可视化界面仅显示固定目录下的内容&#xff0c;无法访问前序目录&#xff0c;导致查看内容非常麻烦&#xff0c;这时候软连接的作用就显现出来了&#xff0c;在当前目录下连接上&…

【Spring】使用aop切面编程时要给那些类加注解

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理 Spring 中使用aop切面编程时要给那些类加注解 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一…

C++入门(4):auto、范围for、nullptr

一、关键词 auto 1.1 概念 auto 作为一个新的类型指示符来指示编译器&#xff0c;auto 声明的变量必须由编译器在编译时期推导而得。 #include <iostream> using namespace std;int main() {int a 0;auto b a;auto c &a;auto* d &a;auto& e a;cout &…

使用达梦数据库的总结

–修改当前会话所在模式&#xff1a; set schema 模式名;–创建表空间、用户名并为用户指定表空间&#xff0c;并为用户授权 create tablespace "RSGL_BZK" datafile REGL_BZK.DBF size 7488 autoextend on next 128 maxsize 33554431 CACHE NORMAL; create user …

【工具】Java请求带http重定向的地址 自动进行重定向

【工具】Java请求带http重定向的地址 自动进行重定向 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL;public class HTTPGETWithMultipleHeaders {public static voi…

嵌入式实时操作系统的设计与开发(信号量学习)

信号量 除了临界点机制、互斥量机制可实现临界资源的互斥访问外&#xff0c;信号量&#xff08;Semaphore&#xff09;是另一选择。 信号量与互斥量的区别 对于互斥量来说&#xff0c;主要应用于临界资源的互斥访问&#xff0c;并且能够有效地避免优先级反转问题。对于信号量…

Linux下导出dump文件(Oracle和PG数据)

dump文件可以快速的导入导出&#xff0c;所以在数据量较大的情况下用其他方法导出数据都不如dump。 不管是什么数据库&#xff0c;第一步都需要登录Oracle用户 su - oracle登录之后可以选择导出文件到当前目录 Oracle数据库导出&#xff1a; exp 数据库用户名/密码localhos…

UE4 中可全局获取的变量(例如游戏实例、玩家控制器等) 详解

目录 0 引言1 全局对象&#xff08;全局变量&#xff09;1.1 游戏实例 GameInstance1.1.1 介绍1.1.2 使用 GameInstance 1.2 玩家控制器 PlayerController1.3 游戏世界类 UWorld &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&…

优雅的使用String字符串处理各种类型转换

文章目录 &#x1f31f; 优雅的使用String字符串处理各种类型转换&#x1f34a; 基本类型转字符串&#x1f34a; 字符串转基本类型&#x1f34a; 字符串与字符数组的转换&#x1f34a; 字符串与字节数组的转换&#x1f34a; 其他类型转字符串&#x1f34a; 总结 &#x1f4d5;我…

Flask后端开发(一)-基础知识和前期准备

目录 1.背景介绍1.1. 项目背景1.2. 项目难点1.3. 项目环境 2. flask后端开发实现的功能3. flask部署和前后端对接3.1. flask运行配置和服务器部署3.2. flask前后端传参 4. 后端测试工具4.1. 工具介绍4.2. 工具使用 后记 1.背景介绍 1.1. 项目背景 就是前几个月临时接手了一个…

CPU眼里的C/C++: 1.3 汇编级单步调试函数执行过程

1. 目的 2. 基于 GDB 的汇编级单步调试 原始代码 #include <stdio.h>long test() {long a 1;a 2;return a; }int main() {int ret test();printf("test return %d\n", ret);return 0; }关键 gdb 命令 si 指令执行汇编级的单步调试info registers 读取寄…

【RocketMQ系列十四】RocketMQ中消息堆积如何处理

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

RabbitMQ原理(五):消费者的可靠性

文章目录 3.消费者的可靠性3.1.消费者确认机制3.2.失败重试机制3.3.失败处理策略3.4.业务幂等性3.4.1.唯一消息ID3.4.2.业务判断 3.5.兜底方案 3.消费者的可靠性 当RabbitMQ向消费者投递消息以后&#xff0c;需要知道消费者的处理状态如何。因为消息投递给消费者并不代表就一定…

Unsupervised Medical Image Translation with Adversarial Diffusion Models

基于对抗扩散模型的无监督医学图像翻译 论文链接&#xff1a;https://arxiv.org/abs/2207.08208 项目链接&#xff1a;https://github.com/icon-lab/SynDiff Abstract 通过源-目标模态转换对缺失图像进行补全可以提高医学成像方案的多样性。利用生成对抗网络(GAN)进行一次映…

Leetcode—104.二叉树的最大深度【简单】

2023每日刷题&#xff08;六&#xff09; Leetcode—104.二叉树的最大深度 递归实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/int maxDepth(struct TreeNode* root){…

分类预测 | MATLAB实现SSA-CNN-GRU-Attention数据分类预测(SE注意力机制)

分类预测 | MATLAB实现SSA-CNN-GRU-Attention数据分类预测&#xff08;SE注意力机制&#xff09; 目录 分类预测 | MATLAB实现SSA-CNN-GRU-Attention数据分类预测&#xff08;SE注意力机制&#xff09;分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现…

python 正则表达式

re.match 在起始位置开始匹配 # 正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配# re.match() 尝试从字符串的起始位置匹配一个模式&#xff0c;如果不是起始位置匹配成功的话import rehhre.match(我爱你,我爱你-我爱你) # 在起…

C#,数值计算——分类与推理Phylo_upgma的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public class Phylo_upgma : Phylagglom { public override void premin(double[,] d, int[] nextp) { } public override double dminfn(doubl…