源码解析.Net中Host主机的构建过程
前言
本篇文章着重讲一下在.Net中Host主机的构建过程,依旧延续之前文章的思路,着重讲解其源码,如果有不知道有哪些用法的同学可以点击这里,废话不多说,咱们直接进入正题
Host构建过程
下图是我自己整理的Host构建过程以及里面包含的知识点我都以链接的形式放上来,大家可以看下图,大概了解下过程(由于知识点过多,所以只能分上下两张图了):
图中标识的相关知识点连接如下(ps:与编号对应):
- 1 环境变量 点击这里
- 2 命令行参数 点击这里
- 3 默认配置 点击这里
- 4 用户机密数据 点击这里
- 5 默认logging 点击这里
- 6 使用承载启动程序集 点击这里
- 7 自定义配置 点击这里
- 8 依赖注入 点击这里
- 9 应用启动后台任务 点击这里
- 10 中间件构建 点击这里
- 11 线程spinwait 点击这里
以上就是笔者在源码阅读阶段,其总结的自我感觉重要的知识点在微软文档中的对应位置。
源码解析
这部分笔者根据上图中的四大块分别进行源码解析,可能篇幅比较长,其主要是对源代码增加了自己理解的注释,所以读者在阅读的过程中,要多注意源码中的注释(ps:展示出的代码不是全部代码,而只是重要代码哦,每个小节总结的点都是按照代码顺序解释)
初始化默认配置ConfigDefaultBuilder
public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args)
{
//设置程序运行路径
builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
//添加获取环境变量的前缀
config.AddEnvironmentVariables(prefix: "DOTNET_");
//添加命令行参数
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
//宿主机环境信息
IHostEnvironment env = hostingContext.HostingEnvironment;
//是否在文件改变时重新加载,默认是True
bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext);
//默认添加的配置文件(格外添加以环境变量为名称的文件)
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
//如果是开发环境,并且应用程序的应用名称不是空字符串,则加载用户机密,默认true(主要是为了不同开发人员的配置不同)
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly is not null)
{
config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
}
}
//这里再次执行是为了让环境变量和命令行参数的配置优先级提高(后加载的key/value获取时优先级最高)
//添加其他环境变量
config.AddEnvironmentVariables();
//添加命令行参数
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
//判断操作系统
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
//添加过滤规则,捕获warning日志
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
//获取Logging配置
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
//添加输出到控制台
logging.AddConsole();
//添加将debug日志输出到控制台
logging.AddDebug();
//添加写入的事件源
logging.AddEventSourceLogger();
if (isWindows)
{
//添加事件日志
logging.AddEventLog();
}
//添加链路追踪选项
logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
//依赖注入相关校验
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
源码总结:
- 设置程序执行路径以及获取环境变量和加载命令行参数。
- 根据环境变量加载appsettings.json,加载用户机密数据(仅开发环境)。
- 接着又加载环境变量和命令行参数(这里为什么又加载了一次呢?是因为这它们执行的顺序是不一样的,而后加载的会覆盖前面加载的Key/Value,前面加载主要是确定当前运行的环境变量以及用户自定义的命令行参数,后面是为确保通过key获取value的时候能够获取到准确的值)。
- 接下来就主要是配置默认Log,如果是开发环境,依赖注入相关的配置默认开启(验证scope是否被用于singleton,验证是否在调用期间就创建所有服务至缓存)。
初始化主机启动配置ConfigureWebHostDefaults
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
{
_builder = builder;
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
//添加以ASPNETCORE_开头的环境变量(ps:判断当前环境是那个环境)
if (!options.SuppressEnvironmentConfiguration)
{
configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
}
//这里主要加载环境变量
_config = configBuilder.Build();
_builder.ConfigureHostConfiguration(config =>
{
//将上面的配置加载进来
config.AddConfiguration(_config);
//通过配置和特性加载额外的Config(或者不加载配置),通过继承IHostingStartup无侵入性加载。
ExecuteHostingStartups();
});
//将上面Startup中Config的配置放到Build阶段加载
_builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
if (_hostingStartupWebHostBuilder != null)
{
var webhostContext = GetWebHostBuilderContext(context);
_hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
}
});
//增加注入的服务
_builder.ConfigureServices((context, services) =>
{
var webhostContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
//注入一些其他服务
services.AddSingleton(webhostContext.HostingEnvironment);
services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.WebHostOptions = webHostOptions;
options.HostingStartupExceptions = _hostingStartupErrors;
});
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
//可以通过配置的方式查找程序集加载StartUp,但是默认只会加载最后一个StartUp
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
UseStartup(startupType, context, services);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
capture.Throw();
};
});
}
}
});
}
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
//提供.netCore 静态Web资产(ps:说实话这里不知道有什么用)
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
//使用 Kestrel 配置反向代理
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
})
.ConfigureServices((hostingContext, services) =>
{
//配置启动的Url
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
//用来获取客户端的IP地址
if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
}
//添加路由配置
services.AddRouting();
})
//默认使用IIS
.UseIIS()
//使用进程内的托管模式
.UseIISIntegration();
}
这部分内容可能会多点,源码总结:
- 添加Memory缓存,添加ASPNETCORE_开头的环境变量。
- 根据用户的配置,来加载额外的StartUp中Config配置,但是它的参数是IWebHostBuilder,这部分可以参考微软文档StartUp的部分。
- 如果有存在这些配置的话,则统一放到Build阶段加载。
- 加载web主机需要的注入的服务,以及判断是否需要通过程序集来加载StartUp,并且添加一个程序启动时调用的服务(这里主要是构建HttpContext执行管道)。
- 引用Kestrel,继承路由和IIS,并且默认使用进程内托管。
- 加载用户自定义的其他配置,例如默认的调用UseStartup方法。
根据指定配置开始初始化主机Build
public class HostBuilder : IHostBuilder
{
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
private bool _hostBuilt;
private IConfiguration _hostConfiguration;
private IConfiguration _appConfiguration;
private HostBuilderContext _hostBuilderContext;
private HostingEnvironment _hostingEnvironment;
private IServiceProvider _appServices;
private PhysicalFileProvider _defaultProvider;
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
_configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
{
_configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
?? throw new ArgumentNullException(nameof(configureDelegate))));
return this;
}
public IHost Build()
{
//只能执行一次这个方法
if (_hostBuilt)
{
throw new InvalidOperationException(SR.BuildCalled);
}
_hostBuilt = true;
using var diagnosticListener = new DiagnosticListener("Microsoft.Extensions.Hosting");
const string hostBuildingEventName = "HostBuilding";
const string hostBuiltEventName = "HostBuilt";
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName))
{
Write(diagnosticListener, hostBuildingEventName, this);
}
//执行Host配置(应用程序执行路径,加载_dotnet环境变量,获取命令行参数,加载预配置)
BuildHostConfiguration();
//设置主机环境变量
CreateHostingEnvironment();
//构建HostBuilderContext实例
CreateHostBuilderContext();
//构建程序配置(加载appsetting.json,环境变量,命令行参数等)
BuildAppConfiguration();
//构造容器,注入服务
CreateServiceProvider();
var host = _appServices.GetRequiredService<IHost>();
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName))
{
Write(diagnosticListener, hostBuiltEventName, host);
}
return host;
}
private static void Write<T>(
DiagnosticSource diagnosticSource,
string name,
T value)
{
diagnosticSource.Write(name, value);
}
private void BuildHostConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions)
{
buildAction(configBuilder);
}
//本质是执行ConfigureProvider中的Load方法,加载对应配置
_hostConfiguration = configBuilder.Build();
}
private void CreateHostingEnvironment()
{
//设置环境变量
_hostingEnvironment = new HostingEnvironment()
{
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
};
if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
{
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
}
//程序运行路径
_hostingEnvironment.ContentRootFileProvider = _defaultProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
}
private void CreateHostBuilderContext()
{
_hostBuilderContext = new HostBuilderContext(Properties)
{
HostingEnvironment = _hostingEnvironment,
Configuration = _hostConfiguration
};
}
private void BuildAppConfiguration()
{
//对于已经加载过的配置不再重新加载
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
//注意这里是AppConfig
foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
//将新的配置赋值给config
_hostBuilderContext.Configuration = _appConfiguration;
}
private void CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_ => _appConfiguration);
services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost>(_ =>
{
return new Internal.Host(_appServices,
_hostingEnvironment,
_defaultProvider,
_appServices.GetRequiredService<IHostApplicationLifetime>(),
_appServices.GetRequiredService<ILogger<Internal.Host>>(),
_appServices.GetRequiredService<IHostLifetime>(),
_appServices.GetRequiredService<IOptions<HostOptions>>());
});
services.AddOptions().Configure<HostOptions>(options => { options.Initialize(_hostConfiguration); });
services.AddLogging();
//主要加载额外注入的服务
foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
//这里返回object,主要是为了保留扩展,让用户自定义的依赖注入框架能够运行。
object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
{
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
if (_appServices == null)
{
throw new InvalidOperationException(SR.NullIServiceProvider);
}
//可能是想先把IConfiguration加载到内存中
_ = _appServices.GetService<IConfiguration>();
}
}
在上面的两个小单元可以看出,所有的构造是以委托的方式,最后都加载到HostBuilder内部的委托集合中,源码总结:
- 加载环境变量和命令行参数。
- 构建HostingEnvironment对象。
- 构建HostBuilderContext对象,里面包含配置和执行环境。
- 加载appsettings.json,环境变量和命令行参数等。
- 注入一些必须的服务,加载日志配置,WebHost里面注入的服务,加载StartUp里面ConfigService里面的服务,以及其他的一些注入的服务,构建容器(最后那里获取IConfiguration猜测可能是想先缓存到根容器吧)。
运行主机Run
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
//应用程序启动和关闭事件
await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
combinedCancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
//主要是执行一些后台任务,可以重写启动和关闭时要做的操作
foreach (IHostedService hostedService in _hostedServices)
{
//立即执行的任务,例如构建管道就是在这里
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
//执行一些后台任务
if (hostedService is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}
//通知应用程序启动成功
_applicationLifetime.NotifyStarted();
//程序启动
_logger.Started();
}
源码总结:
- 监听程序的启动关闭事件。
- 开始执行Hosted服务或者加载后台执行的任务。
- 通过TaskCompletionSource来持续监听Token,hold住进程。
总结
通过模板来构建的.Net泛型主机,其实已经可以满足大部分的要求,并且微软保留大量扩展让用户来自定义,当然你也可以构建其他不同的主机类型(如:Web主机或者控制台程序启动项配置),想了解的可以点击这里。
以上就是笔者通过阅读源码来分析的程序执行流程,因为篇幅问题没有把所有代码都放出来,实在是太多了,所以只放了部分代码,主要是想给阅读源码的同学在阅读的时候找到思路,可能会有一点错误,还请评论指正。
源码解析.Net中Host主机的构建过程的更多相关文章
- 源码解析Android中View的measure量算过程
Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文< Android中View ...
- [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer
[源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer 目录 [源码解析] PyTorch 分布式(11) ----- Dis ...
- 源码解析.Net中IConfiguration配置的实现
前言 关于IConfituration的使用,我觉得大部分人都已经比较熟悉了,如果不熟悉的可以看这里.因为本篇不准备讲IConfiguration都是怎么使用的,但是在源码部分的解读,网上资源相对少一 ...
- 源码解析.Net中DependencyInjection的实现
前言 笔者的这篇文章和上篇文章思路一样,不注重依赖注入的使用方法,更加注重源码的实现,我尽量的表达清楚内容,让读者能够真正的学到东西.如果有不太清楚依赖注入是什么或怎么在.Net项目中使用的话,请点击 ...
- 源码解析.Net中Middleware的实现
前言 本篇继续之前的思路,不注重用法,如果还不知道有哪些用法的小伙伴,可以点击这里,微软文档说的很详细,在阅读本篇文章前,还是希望你对中间件有大致的了解,这样你读起来可能更加能够意会到意思.废话不多说 ...
- Spark 源码解析 : DAGScheduler中的DAG划分与提交
一.Spark 运行架构 Spark 运行架构如下图: 各个RDD之间存在着依赖关系,这些依赖关系形成有向无环图DAG,DAGScheduler对这些依赖关系形成的DAG,进行Stage划分,划分的规 ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- 源码解析C#中PriorityQueue(优先级队列)的实现
前言 前段时间看到有大佬对.net 6.0新出的PriorityQueue(优先级队列)数据结构做了解析,但是没有源码分析,所以本着探究源码的心态,看了看并分享出来.它不像普通队列先进先出(FIFO) ...
- multiprocessing 源码解析 更新中......
一.参考链接 1.源码包下载·链接: https://pypi.org/search/?q=multiprocessing+ 2.源码包 链接:https://pan.baidu.com/s/1j ...
随机推荐
- EF中数据修改时动态更新其他数据
场景 利用.net core开发时,经常会遇到使用EF(Entity Framework),但是今天在开发过程中发现一个值莫名其妙的自己变了,我怀疑是EF的问题. 主要代码如下: 1 // 最近一条告 ...
- 【Maven实战技巧】「插件使用专题」Maven-Assembly插件实现自定义打包
前提概要 最近我们项目越来越多了,然后我就在想如何才能把基础服务的打包方式统一起来,并且可以实现按照我们的要求来生成,通过研究,我们通过使用maven的assembly插件完美的实现了该需求,爽爆了有 ...
- spring Data Rest 远程命令执行漏洞(CVE-2017-8046)
参考 文章https://blog.csdn.net/weixin_41438728/article/details/110425174 影响版本 Spring Framework 5.0 to 5. ...
- 移动APP我们需要关注什么
移动APP关注的点比web或者PC上的程序更多 1.测试用例的设计 移动互联网的快节奏,要放弃传统的测试用例编写方式,不需要写详细的测试用例,采用罗列测试点的方式如思维导图,这样既节省时间又能够直观清 ...
- 文件包含 & LFI-labs靶场
文件包含漏洞学习 冲冲冲,好好学习 2020.1.30 认真对待自己做出的每一个决定 知识与实践 Q:什么是文件包含? A:简单一句话,为了更好地使用代码的重用性,引入了文件包含函数,可以通过文件包含 ...
- 关于Asp.Net MVC html.renderaction传递参数
View视图 @{html.renderaction("控制器方法名","控制器名称",new{params1="参数值"})} Contr ...
- 手写Pascal解释器(一)
目录 一.编写解释器的动机 二.part1 三.part2 四.part3 一.编写解释器的动机 学习了Vue之后,我发现对字符串的处理对于编写一个程序框架来说是非常重要的,就拿Vue来说,我们使用该 ...
- synchronized优化手段:锁膨胀、锁消除、锁粗化和自适应自旋锁...
synchronized 在 JDK 1.5 时性能是比较低的,然而在后续的版本中经过各种优化迭代,它的性能也得到了前所未有的提升,上一篇中我们谈到了锁膨胀对 synchronized 性能的提升,然 ...
- FreeRTOS-05-队列
说明 本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正. FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式.合作式和时间片调度.适用于微处理 ...
- (转)Python爬虫--通用框架
转自https://blog.csdn.net/m0_37903789/article/details/74935906 前言: 相信不少写过Python爬虫的小伙伴,都应该有和笔者一样的经历吧只要确 ...