配置的读取和写入

读取配置的类,包括手动从json中读取配置、将json配置与配置类绑定、从控制台读取配置、从环境变量读取配置

using System.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; namespace LearnConfigurationSystem; public static class ReadConfig
{
public static void ReadConfigManually()
{
// 获取ConfigurationBuilder实例
var configurationBuilder = new ConfigurationBuilder();
// configurationBuilder配置(path: 配置文件路径,optional: 配置文件是否可选,reloadOnChange: 配置文件改变时是否重新读取配置)
configurationBuilder.AddJsonFile(path: "config.json", optional: true, reloadOnChange: false); // 从ConfigurationBuilder实例构建实现了IConfigurationRoot接口的实例
IConfigurationRoot config = configurationBuilder.Build(); // 从配置文件中读取数据,读取到的数据均用string?类型表示,即使对应数据在json中不是字符串类型,如果没有获取到指定名称的数据则用null表示
string? user = config["userName"];
string? proxyAddress = config.GetSection("proxy:address").Value;
string? port = config["proxy:port"]; Debug.Assert(user != null);
Debug.Assert(proxyAddress != null);
Debug.Assert(port != null); Console.WriteLine($"{user} - {proxyAddress}:{port}");
} public static void ReadConfigurationThenMapToConfigurationModels()
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
IConfigurationRoot config = configurationBuilder.Build(); // 使用依赖注入容器管理依赖注入
var service = new ServiceCollection(); service.AddOptions()
// 将config.json中的dataBaseConfig绑定到配置类DataBaseSettings(自动小驼峰->大驼峰转换,配置类中的属性大驼峰,json中写小驼峰就行)
.Configure<DataBaseSettings>(e => config.GetSection("dataBaseConfig").Bind(e))
// 将config.json中的smtpConfig绑定到配置类SmtpSettings(自动小驼峰->大驼峰转换,配置类中的属性大驼峰,json中写小驼峰就行)
.Configure<SmtpSettings>(e => config.GetSection("smtpConfig").Bind(e)); // 将Demo注册为瞬态服务
service.AddTransient<Demo>(); // 创建ServiceProvider服务
using var serviceProvider = service.BuildServiceProvider(); // 循环3次,方便测试(更改配置文件后查看输出是否变换)
/* 注:修改和编译得到的exe文件处在同一文件夹下的config.json,不要修改源码下的config.json(程序读取的不是这个config.json) */
for (int i = 0; i < 3; i++)
{
// 创建Scope(每次循环一个Scope,防止上次循环的环境干扰)
using var scope = serviceProvider.CreateScope();
var serviceProviderOfScope = scope.ServiceProvider;
// 从serviceProvider获取对应的服务,这一服务只在当前scope范围内生效
var demo = serviceProviderOfScope.GetRequiredService<Demo>();
demo.Test();
Console.WriteLine("可以改配置了");
Console.ReadKey();
}
} // args从Main函数传递
public static void ReadConfigurationFromCommandLine(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine($"{nameof(args)} is null. 没有从控制台接收额外的配置参数");
return;
} // 显示从控制台传入的所有参数
for (var index = 0; index < args.Length; index++)
{
var arg = args[index];
Console.WriteLine($"ArgumentFromConsole: No.{index}, Content: {arg}");
} var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddCommandLine(args);
var config = configurationBuilder.Build();
var serverAddress = config["serverAddress"];
Console.WriteLine($"serverAddress:{serverAddress ?? "null"}");
} public static void ReadConfigurationFromEnvironmentVariables()
{
var configBuilder = new ConfigurationBuilder();
// 读取所有环境变量(不推荐,环境变量太多,全部读入浪费资源,并且容易和其他程序的环境变量冲突)
//configBuilder.AddEnvironmentVariables();
// 读取前缀为 PROCESSOR_ 的环境变量
configBuilder.AddEnvironmentVariables("PROCESSOR_");
IConfigurationRoot config = configBuilder.Build(); // 获取环境变量PROCESSOR_IDENTIFIER
// 注意:去除前缀,例如:PROCESSOR_IDENTIFIER去除前缀PROCESSOR_后变为IDENTIFIER
var processorIdentifier = config["IDENTIFIER"];
Console.WriteLine($"{nameof(processorIdentifier)}: {processorIdentifier ?? "null"}");
} // 模型类
public class DataBaseSettings
{
public string? DataBaseType { get; set; }
public string? ConnectionString { get; set; }
} public class SmtpSettings
{
public string? Server { get; set; }
public string? UserName { get; set; }
public string? Password { get; set; }
} // 用于测试读取配置的Demo类
public class Demo
{
/* 类似的接口有:
* 1. IOption<T>: 在配置改变后无法自动读取新值,除非重启程序
* 2. IOptionMonitor<T>: 配置改变后,新值会立即生效,可能会造成数据不一致,
* 例如:A、B先后读取某个配置,配置在A执行后改变,A使用了旧值,B使用了新值
* 3. IOptionSnapshot<T>: 配置改变后,新值会在下次进入这个范围时生效,
* 例如:A、B先后读取某个配置,配置在A执行后改变,A使用了旧值,B也使用了旧值,但下次A、B再读取配置时,读取的都是新值,保证数据的一致性
*/
/* 综上,这三个接口优先使用IOptionSnapshot<T>接口 */
private readonly IOptionsSnapshot<DataBaseSettings> _optionDataBaseSettings;
private readonly IOptionsSnapshot<SmtpSettings> _optionSmtpSettings; public Demo(IOptionsSnapshot<DataBaseSettings> optionDataBaseSettings,
IOptionsSnapshot<SmtpSettings> optionSmtpSettings)
{
_optionDataBaseSettings = optionDataBaseSettings;
_optionSmtpSettings = optionSmtpSettings;
} public void Test()
{
var db = _optionDataBaseSettings.Value;
var smtp = _optionSmtpSettings.Value;
Console.WriteLine($"Database: {db.DataBaseType ?? "null"}, {db.ConnectionString ?? "null"}");
Console.WriteLine($"Smtp: {smtp.Server ?? "null"}, {smtp.UserName ?? "null"}, {smtp.Password ?? "null"}");
}
}
}

项目文件(LearnConfigurationSystem.csproj)

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<!-- 程序在运行时默认加载EXE文件同文件夹下的配置文件,而不是项目中的config.json文件。
所以需要设置这一属性,在生成项目时自动将config.json文件复制到生成目录。
-->
<None Update="config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup> <ItemGroup>
<!-- 读取配置依赖的安装包,其中:
Microsoft.Extensions.Configuration是基础包,
Microsoft.Extensions.Configuration.Json用于读取Json配置
Microsoft.Extensions.Configuration.CommandLine用于从命令行读取配置
Microsoft.Extensions.Configuration.EnvironmentVariables用于从环境变量读取配置
Microsoft.Extensions.Options用于映射配置项,它可以辅助处理容器生命周期、配置刷新等。
Microsoft.Extensions.DependencyInjection用于依赖注入,配合Microsoft.Extensions.Options使用
Microsoft.Extensions.Configuration.Binder用于将配置项与配置类绑定(映射)
-->
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
</ItemGroup> </Project>

从数据库读取配置

说明

  1. 依据Zack.AnyDBConfigProvider项目,按照自己的编码习惯重新构建。
  2. 该项目可从数据库中读取配置信息

重点内容

  1. 任何自定义的ConfigurationProvider都要实现IConfigurationProvider接口,由于.NET中的抽象类ConfigurationProvider已经实现了IConfigurationProvider接口,所以一般的做法是继承ConfigurationProvider,然后override抽象类ConfigurationProvider中的方法。
  2. Load方法用于加载配置数据,加载的数据按照键值对的形式保存到Data属性中。
  3. Data属性是IDictionary<string,string>类型,其中键为配置的名字。
  4. 如果配置项发生变化,则需要调用OnReload方法通知订阅者配置项发生改变。

DbConfigurationProvider类的构造方法

  1. 如果启用了ReloadOnChange选项,那么将一个委托方法送入队列,等到线程池有线程可用时执行该委托方法(ThreadPool.QueueUserWorkItem)。
  2. 该委托方法在方法体内每间隔一段时间(通过Thread.Sleep实现)执行一次Load方法,直到DbConfigurationProvider类的实例被释放。

Load方法

  1. Load方法首先创建了一个Data属性的副本clonedData,用于稍后比较数据是否修改了。
  2. 读取配置的代码最终会调用TryGet方法读取配置,为了避免TryGet读取到Load加载一半的数据,使用读写锁控制读写同步。
  3. 由于读的频率高于写的频率,为了避免使用普通的锁造成性能问题,这里使用ReaderWriteLockSlim类(.NET自带)实现“只允许一个线程写入,允许多个线程读取”。
  4. 为了实现3的写锁,需要把“将配置项写入Data属性“的代码放到EnterWriteLock和ExitWriteLock之间。
  5. 同时一定要把OnReload方法放到ExitWriteLock之后。这是因为OnReload方法中调用了TryGet方法,TryGet方法中有读锁,写锁中嵌套读锁是不被允许的
  6. Load中调用的DoLoad方法从数据库中读取配置,然后将数据加载到Data属性中。DoLoad方法遵循”多层级数据扁平化规则“来解析和加载数据。
  7. 在6之后调用DataIsChanged方法将旧数据和从数据库中读取的新数据比较,如果发现数据有变化就返回true,否则返回false。
  8. 如果7中的DataIsChanged方法返回true就调用OnReload方法向订阅者通知数据的变化。

代码实现

ConfigurationBuilderInterfaceExtension.cs

using System.Data;
using Microsoft.Extensions.Configuration; namespace ReadConfigurationsFromDatabase; // 扩展IConfigurationBuilder接口,提供AddDbConfiguration方法
public static class ConfigurationBuilderInterfaceExtension
{
public static IConfigurationBuilder AddDbConfiguration(this IConfigurationBuilder builder,
DbConfigOptions options) => builder.Add(new DbConfigurationSource(options)); public static IConfigurationBuilder AddDbConfiguration(this IConfigurationBuilder builder,
Func<IDbConnection> createDbConnection, string tableName = "T_DbConfigurations", bool reloadOnChange = false,
TimeSpan? reloadInterval = null)
{
return AddDbConfiguration(builder, new DbConfigOptions(createDbConnection)
{
TableName = tableName,
ReloadOnChange = reloadOnChange,
ReloadInterval = reloadInterval
});
}
}

DbConfigOptions.cs

using System.Data;

namespace ReadConfigurationsFromDatabase;

public sealed class DbConfigOptions
{
public DbConfigOptions(Func<IDbConnection> createDbConnection)
{
CreateDbConnection = createDbConnection;
} // Func委托不能为空,又没有默认值,所以必须使用构造函数初始化
public Func<IDbConnection> CreateDbConnection { get; }
public string TableName { get; init; } = "T_DbConfigurations";
public bool ReloadOnChange { get; init; } = false;
public TimeSpan? ReloadInterval { get; init; }
}

DbConfigurationProvider.cs

using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Text.Json;
using Microsoft.Extensions.Configuration; namespace ReadConfigurationsFromDatabase; public class DbConfigurationProvider : ConfigurationProvider,
IDisposable, IAsyncDisposable
{
private readonly DbConfigOptions _dbConfigOptions; // 默认值为false,可以不用显式赋值
private bool _isDisposed = false; // 读写锁
private readonly ReaderWriterLockSlim _lockObj = new(); # region Constructor and DisposePattern public DbConfigurationProvider(DbConfigOptions dbConfigOptions)
{
_dbConfigOptions = dbConfigOptions;
// 如果option中没有设置“在配置改变时重新加载,那么直接返回”
if (!_dbConfigOptions.ReloadOnChange) return; // 默认reload的时间间隔
var interval = TimeSpan.FromSeconds(3);
// 如果option设置了reload的时间间隔,那么就应用这一间隔
if (_dbConfigOptions.ReloadInterval != null) interval = _dbConfigOptions.ReloadInterval.Value; // 将委托扔进线程池队列,线程池有空闲线程就执行
ThreadPool.QueueUserWorkItem(_ =>
{
// 如果资源被回收了,直接返回
if (_isDisposed) return; // 通过线程休眠的方式定时加载
Load();
Thread.Sleep(interval);
});
} public void Dispose()
{
if (_isDisposed) return;
_lockObj.Dispose();
_isDisposed = true;
GC.SuppressFinalize(this);
} public async ValueTask DisposeAsync()
{
if (_isDisposed) return;
await Task.Run(Dispose);
GC.SuppressFinalize(this);
} ~DbConfigurationProvider()
{
Dispose();
} #endregion Constructor and DisposePattern # region override Methods in ConfigurationProvider public override IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string? parentPath)
{
_lockObj.EnterReadLock();
try
{
return base.GetChildKeys(earlierKeys, parentPath);
}
finally
{
_lockObj.ExitReadLock();
}
} public override bool TryGet(string key, out string? value)
{
_lockObj.EnterReadLock();
try
{
return base.TryGet(key, out value);
}
finally
{
_lockObj.ExitReadLock();
}
} public override void Load()
{
_lockObj.EnterWriteLock();
var tableName = _dbConfigOptions.TableName;
IDictionary<string, string?> clonedData = Data.Clone();
Data.Clear(); try
{
using var dbConnection = _dbConfigOptions.CreateDbConnection.Invoke();
dbConnection.Open();
DoLoad(tableName, dbConnection);
}
catch (DbException)
{
Data = clonedData;
throw;
}
finally
{
_lockObj.ExitWriteLock();
} // 如果数据改变,则发出通知
if (DataIsChanged(clonedData, Data)) OnReload();
} private static bool DataIsChanged(IDictionary<string, string?> oldData, IDictionary<string, string?> newData)
{
if (ReferenceEquals(oldData, newData) || oldData.Count != newData.Count) return true;
foreach (var (oldKey, oldValue) in oldData)
{
if (!newData.ContainsKey(oldKey)) return true;
if (newData[oldKey] != oldData[oldKey]) return true;
} return false;
} private void DoLoad(string tableName, IDbConnection dbConnection)
{
using var dbCommand = dbConnection.CreateCommand();
dbCommand.CommandText =
// 子查询的作用是通过Id筛选最新的配置信息
$"select name,value from {tableName} where id in (select MAX(id) from {tableName} group by name)";
using var dbReader = dbCommand.ExecuteReader(); while (dbReader.Read())
{
// 索引对应的列和查询语句(dbCommand.CommandText)有关,这里第一列是"名称",第二列是"值"
var name = dbReader.GetString(0);
var value = dbReader.GetString(1); // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (value is null)
{
Data[name] = value;
continue;
} // 去除多余空格
value = value.Trim(); // 处理value是json的情况
if (value.StartsWith("[") && value.EndsWith("]") || value.StartsWith("{") && value.EndsWith("}"))
{
TryLoadAsJson(name, value);
continue;
} Data[name] = value;
}
} private void TryLoadAsJson(string name, string value)
{
var jsonOptions = new JsonDocumentOptions
{
// 允许json列表或数组末尾存在额外逗号(json默认的行为是不能存在逗号)
AllowTrailingCommas = true,
// 允许json存在注释并跳过这些注释不做任何处理(json的默认行为是不允许注释)
CommentHandling = JsonCommentHandling.Skip
};
try
{
// 将字符串的值解析为JsonDocument,并获取JsonDocument的根元素
var jsonRoot = JsonDocument.Parse(value, jsonOptions).RootElement;
if (jsonRoot.ValueKind is not (JsonValueKind.Array or JsonValueKind.Object))
{
Data[name] = GetValueForConfig(jsonRoot);
return;
} var traceStack = new Stack<KeyValuePair<string, JsonElement>>();
traceStack.Push(new KeyValuePair<string, JsonElement>(name, jsonRoot));
while (traceStack.Count > 0) LoadJsonElement(traceStack);
}
catch (JsonException e)
{
// 如果不能转换为json字符串,将该字符串当作原始字符串对待
Data[name] = value;
Debug.WriteLine($"将{value}转换为json时出现异常,异常信息:{e}");
}
} private void LoadJsonElement(Stack<KeyValuePair<string, JsonElement>> traceStack)
{
var (name, jsonRoot) = traceStack.Pop();
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (jsonRoot.ValueKind)
{
case JsonValueKind.Array:
{
int index = 0;
foreach (var item in jsonRoot.EnumerateArray())
{
string path = name + ConfigurationPath.KeyDelimiter + index;
traceStack.Push(new KeyValuePair<string, JsonElement>(path, item));
index++;
} break;
}
case JsonValueKind.Object:
{
foreach (var jsonObj in jsonRoot.EnumerateObject())
{
string pathOfObj = name + ConfigurationPath.KeyDelimiter + jsonObj.Name;
traceStack.Push(new KeyValuePair<string, JsonElement>(pathOfObj, jsonObj.Value));
} break;
}
default:
Data[name] = GetValueForConfig(jsonRoot);
break;
}
} private static string? GetValueForConfig(JsonElement jsonRoot) => jsonRoot.ValueKind switch
{
JsonValueKind.String =>
//remove the quotes, "ab"-->ab
jsonRoot.GetString(),
JsonValueKind.Null =>
//remove the quotes, "null"-->null
null,
JsonValueKind.Undefined =>
//remove the quotes, "null"-->null
null,
_ => jsonRoot.GetRawText()
}; #endregion override Methods in ConfigurationProvider
}

DbConfigurationSource.cs

using Microsoft.Extensions.Configuration;

namespace ReadConfigurationsFromDatabase;

// 声明如何创建实现了IConfigurationProvider接口的对象DbConfigurationProvider
// DbConfigurationSource类似于:IConfigurationProvider系列产品中DbConfigurationProvider产品的生产说明
public sealed class DbConfigurationSource : IConfigurationSource
{
private readonly DbConfigOptions _dbConfigOptions; public DbConfigurationSource(DbConfigOptions dbConfigOptions) => _dbConfigOptions = dbConfigOptions; // 这里引入IConfigurationBuilder类型的形参builder的作用可类比于:生产本产品需要其他工厂生产出来的其他产品。
// 不过这里没有用到其他产品,所以没有正在使用形参builder(和产品生产线一样,要预留些可拓展部件,方便产线的升级改造)
public IConfigurationProvider Build(IConfigurationBuilder builder) => new DbConfigurationProvider(_dbConfigOptions);
}

DictionaryInterfaceExtension.cs

namespace ReadConfigurationsFromDatabase;

public static class DictionaryInterfaceExtension
{
public static IDictionary<string, string?> Clone(this IDictionary<string, string?> dictionary) =>
dictionary.ToDictionary(item => item.Key, item => item.Value);
}

测试代码如下:

using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
using ReadConfigurationsFromDatabase;
using Xunit.Abstractions; namespace ConfigurationSystem.Test; public class ReadConfigurationsFromDatabaseTest
{
private const string ConnectionString =
"Data Source=localhost;Database=Test;User ID=root;Password=Aa123456+;pooling=true"; private readonly ITestOutputHelper _output; public ReadConfigurationsFromDatabaseTest(ITestOutputHelper output)
{
_output = output;
} [Fact]
public void DbConnectionTest()
{
using var connection = new MySqlConnection(ConnectionString);
connection.Open(); using var command = connection.CreateCommand();
command.CommandText = "select * from Test.config";
using var dbReader = command.ExecuteReader(); while (dbReader.Read())
{
var name = dbReader.GetString(1);
var value = dbReader.GetString(2);
_output.WriteLine($"{name ?? "null"}: {value ?? "null"}");
} connection.Close();
} [Fact]
public void ReadConfigFromDb()
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddDbConfiguration(() =>
{
var connection = new MySqlConnection(ConnectionString);
return connection;
}, tableName: "config", reloadOnChange: false); var config = configurationBuilder.Build();
var userName = config["userName"];
Assert.Equal("dbUserName", userName ?? "null");
var connectionString = config["databaseConfig:connectionString"];
Assert.Equal("mysqlConnectionString", connectionString ?? "null");
}
}

多配置源问题

.NET Core中的配置系统支持“可覆盖的配置”,可以向ConfigurationBuilder中注册多个配置提供程序,后添加的配置提供程序可以覆盖先添加的配置提供程序

现在从多个配置源读取配置,配置顺序如下:

添加顺序 配置来源(右边的列是配置内容) server userName password port
1 数据库 smtpFromDb.example.com dbUserName 80
2 JSON文件 smtp.example.com userNameFromJson passwordFromJson
3 命令行 passwordFromFromCommandLine

按照顺序读取配置后,各个配置项的实际值如下(后添加的会覆盖先添加的):

  • server=smtp.example.com
  • userName=userNameFromJson
  • password=passwordFromFromCommandLine
  • port=80

代码如下:

using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
using ReadConfigurationsFromDatabase; namespace GetConfigUsingMultipleWaysSimultaneously; public static class ReadConfigFromMultipleSource
{
private const string ConnectionString =
"Data Source=localhost;Database=Test;User ID=root;Password=Aa123456+;pooling=true"; public static IConfigurationRoot GetConfigurationRoot(string[] args)
{
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder
// 配置来源:数据库
.AddDbConfiguration(() =>
{
var connection = new MySqlConnection(ConnectionString);
return connection;
}, tableName: "config", reloadOnChange: false)
// 配置来源Json
.AddJsonFile("config.json")
// 配置来源CommandLine
.AddCommandLine(args);
return configurationBuilder.Build();
}
}

其中,config.json内容如下:

{
"server": "smtp.example.com",
"userName": "userNameFromJson",
"password": "passwordFromJson"
}

测试代码如下:

using GetConfigUsingMultipleWaysSimultaneously;

namespace ConfigurationSystem.Test;

public class GetConfigUsingMultipleWaysSimultaneouslyTest
{
[Fact]
public void Test()
{
string[] args = new[] { "password=passwordFromFromCommandLine" };
var configRoot = ReadConfigFromMultipleSource.GetConfigurationRoot(args);
var server = configRoot["server"] ?? "null";
var userName = configRoot["userName"] ?? "null";
var password = configRoot["password"] ?? "null";
var port = configRoot["port"] ?? "null"; Assert.Equal("smtp.example.com", server);
Assert.Equal("userNameFromJson", userName);
Assert.Equal("passwordFromFromCommandLine", password);
Assert.Equal("80", port);
}
}

dotnet学习笔记-专题04-配置的读取和写入-01的更多相关文章

  1. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  2. go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])

    目录 go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时]) 静态配置 flag注入 在线热加载配置 远程配置中心 go微 ...

  3. Java学习笔记(04)

    Java学习笔记(04) 如有不对或不足的地方,请给出建议,谢谢! 一.对象 面向对象的核心:找合适的对象做合适的事情 面向对象的编程思想:尽可能的用计算机语言来描述现实生活中的事物 面向对象:侧重于 ...

  4. 学习笔记_J2EE_SpringMVC_03_注解配置_@RequestMapping用法

    @RequestMappingde的用法 摘要: 主要介绍注解@RequestMapping的用法 一.@RequestMapping 简介 在Spring MVC 中使用 @RequestMappi ...

  5. Qt5学习笔记(1)-环境配置(win+64bit+VS2013)

    Qt5学习笔记(1)-环境配置 工欲善其事必先-不装-所以装软件 久不露面,赶紧打下酱油. 下载 地址:http://download.qt.io/ 这个小网页就可以下载到跟Qt有关的几乎所有大部分东 ...

  6. 驱动开发学习笔记. 0.04 linux 2.6 platform device register 平台设备注册 1/2 共2篇

    驱动开发读书笔记. 0.04  linux 2.6 platform device register 平台设备注册  1/2 共2篇下面这段摘自 linux源码里面的文档 : Documentatio ...

  7. MVC缓存OutPutCache学习笔记 (一) 参数配置

    OutPutCache 参数详解 Duration : 缓存时间,以秒为单位,这个除非你的Location=None,可以不添加此属性,其余时候都是必须的. Location : 缓存放置的位置; 该 ...

  8. 树莓派学习笔记——USB wifi配置指南

    0 前言     树莓派既能够使用有线网络又能够无线网络,假设使用有线网络不方便的话能够借助USB wifi无线网卡让树莓派也插上无线"翅膀". 可是和使用有线网络即插即用的方式不 ...

  9. Solr 6.7学习笔记(04)-- Suggest

    当我们使用baidu或者Google时,你输入很少的字符,就会自动跳出来一些建议选项,在Solr里,我们称之为Suggest,在solrconfig.xml里做一些简单的配置,即可实现这一功能.配置如 ...

  10. Python学习笔记之从文件中读取数据

    10-1 Python 学习笔记:在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的Python 知识,其中每一行都以“In Python you can”打头.将这个文件命名为learnin ...

随机推荐

  1. 关于人工智能的思考,写在chatGPT爆火之时

    今天是2023年3月22日,今天思维比较活跃,故作文一篇,以记录当下所想. 先是回家询问了未婚妻的想法,然后记录自己的想法. 未婚妻的想法: 1.在AI领域已经滞后于世界了.因为在墙头上看到过一个加拿 ...

  2. .Net 5.0 WebAPI 发布至 CentOS 7 系统

    〇.前言 本文主要介绍了在 CentOS 7 上部署 WebAPI 项目的过程. 先安装 .net 5.0 的环境,再创建一个示例项目并发布至 CentOS 上,同时列明了一些注意的点:最后将 dot ...

  3. sql server 将数据库表里面的数据,转为insert语句,方便小批量转移数据

    create proc [dbo].[proc_insert] (@tablename varchar(256)) as begin set nocount on declare @sqlstr va ...

  4. k8s 知识

    命令 Pod 管理 kubectl get pods 查看pod在哪个node上 kubectl get pods -o wide kubectl describe pod pod_name 创建新的 ...

  5. C++性能优化——能用array就不要用unordered_map作为查询表

    unordered_map需要哈希值计算和表查询的开销,当key值为整数且连续,直接用数组作为查询表具有更高的效率. #include <iostream> #include <ch ...

  6. 【赵渝强老师】Kubernetes平台中日志收集方案

    一.K8s整体日志收集方案 整体的日志收集方案,如下图所示: Filebeat是本地文件的日志数据采集器,可监控日志目录或特定日志文件(tail file),并将它们转发给Elasticsearch或 ...

  7. 【问题解决】remote: parse error: Invalid numeric literal at line 1, column 20,解决思路

    问题现象 某同事出现过同样的推送到git仓库报错的问题,报错信息详情如下: Delta compresion using up to 20 threads Compressing objects: 1 ...

  8. uprobe的使用浅析

    uprobe是linux内核提供的一种trace用户态函数的机制 可以在不对二进制重新编译的情况下进行trace特定函数 本文描述了uprobe的基本使用方法 使用方法 官方的指引是这样的, 详细的可 ...

  9. Vue 中的key 值的作用是什么?

    使用 v-for 渲染列表的时候会使用 key 给标签一个唯一标识符  ,为了能够高效的跟新虚拟 dom  : 如果没有使用 key 或者key值相同,就不能准确复用虚拟 dom :

  10. 22. uni-app 怎么跳转界面

    methods: { //gonavigate()为点击响应事件,可在HTML部分设置 @tap="gonavigate()" gonavigate(){ uni.navigate ...