深入研究EF Core AddDbContext 引起的内存泄露的原因
var services = new ServiceCollection();
//方式一 AddDbContext注册方式,会引起内存泄露
//services.AddDbContext<EFCoreDbContext>(options => options.UseSqlServer("connectionString")); //方式二 使用AddScoped模拟AddDbContext注册方式,new EFCoreDbContext()时参数由DI提供,会引起内存泄露
services.AddMemoryCache(); // 手动高亮点1 Action<DbContextOptionsBuilder> optionsAction = o => o.UseSqlServer("connectionString");
Action<IServiceProvider, DbContextOptionsBuilder> optionsAction2 = (sp, o) => optionsAction.Invoke(o); services.TryAdd(new ServiceDescriptor(typeof(DbContextOptions<EFCoreDbContext>),
p => DbContextOptionsFactory<EFCoreDbContext>(p, optionsAction2),
ServiceLifetime.Scoped));
services.Add(new ServiceDescriptor(typeof(DbContextOptions),
p => p.GetRequiredService<DbContextOptions<EFCoreDbContext>>(),
ServiceLifetime.Scoped)); services.AddScoped(s => new EFCoreDbContext(s.GetRequiredService<DbContextOptions<EFCoreDbContext>>())); //方式三 直接使用AddScoped,new EFCoreDbContext()时参数自己提供。不会引起内存泄露
//var options = new DbContextOptionsBuilder<EFCoreDbContext>()
// .UseSqlServer("connectionString")
// .Options;
//services.AddScoped(s => new EFCoreDbContext(options)); //为了排除干扰,去掉静态ServiceLocator
//ServiceLocator.Init(services);
//for (int i = 0; i < 1000; i++)
//{
// var test = new TestUserCase();
// test.InvokeMethod();
//} //去掉静态ServiceLocator后的代码
var rootServiceProvider = services.BuildServiceProvider(); // 这一句放在循环外就可避免内存泄露,挪到循环内就会内存泄露
for (int i = ; i < ; i++)
{
using (var test = new TestUserCase(rootServiceProvider))
{
test.InvokeMethod();
}
}
二、上一步中引用的DbContextOptionsFactory<T>方法,放到Main方法后面即可
private static DbContextOptions<TContext> DbContextOptionsFactory<TContext>(IServiceProvider applicationServiceProvider,
Action<IServiceProvider, DbContextOptionsBuilder> optionsAction)
where TContext : DbContext
{
var builder = new DbContextOptionsBuilder<TContext>(
new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>())); builder.UseApplicationServiceProvider(applicationServiceProvider); // 手动高亮点2 optionsAction?.Invoke(applicationServiceProvider, builder); return builder.Options;
}
三、EFCoreDbContext也做一些更改,不需要重写OnConfiguring方法,构造方法参数类型改为DbContextOptions<EFCoreDbContext>
public class EFCoreDbContext : DbContext
{
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)
{
} public DbSet<TestA> TestA { get; set; }
}
services.AddMemoryCache()
和
builder.UseApplicationServiceProvider(applicationServiceProvider);
他的原话是“我测试过,Asp.net core并没有这个问题,EF6.x和EF core1.0也没这个问题,只有.net core console + EF core2.0会出现内存泄露。
经过测试是Microsoft.Extensions.DependencyInjection1.0升级到Microsoft.Extensions.DependencyInjection2.0造成的,只在console出现。”
这句话中的Asp.net core没有这个问题是有误导的,经测试,这个问题在ASP.NET Core中照样是有的,只不过平时大家在使用ASP.NET Core使用DI时一般都是直接获取IServiceProvider的实例,而不会直接用到ServiceCollection,更不会循环多次调用BuildServiceProvider。
就算在ASP.NET Core内部使用了ServiceCollection,一般也是用户自己新创建的,和Startup.ConfigureServices(IServiceCollection services)内的services没有关系。
而EF Core的注册到一般也是注册到services的,所以用户自己创建的ServiceCollection也就和EF Core扯不上关系,更不会引起EF Core内存泄露了。
关于,ASP.NET Core中复现内存泄露,我后面会给出测试代码。
因为微软在官方给出的使用依赖注入的建议其中有两项就是:
避免静态访问服务
应用程序代码中避免服务位置(ServiceLocator)
文档地址:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1
- 循环内多次调用BuildServiceProvider();
- services.AddMemoryCache()
- builder.UseApplicationServiceProvider(applicationServiceProvider);
//EF Core内部生成缓存key的代码
//代码位置:Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache
//所在方法:IServiceProvider GetOrAdd(IDbContextOptions options, bool providerRequired)
var key = options.Extensions
.OrderBy(e => e.GetType().Name)
.Aggregate(0L, (t, e) => (t * ) ^ ((long)e.GetType().GetHashCode() * ) ^ e.GetServiceProviderHashCode());
可以看到方法签名的其中一个参数是IDbContextOptions类型,而且key也是用它计算的。
var key = options.Extensions
.OrderBy(e => e.GetType().Name)
.Aggregate(0L, (t, e) => (t * ) ^ ((long)e.GetType().GetHashCode() * ) ^ e.GetServiceProviderHashCode()); Console.WriteLine($"EF Core当前DbContextOptions实例生成的缓存key为:{key}");
果然,得到的结果是:内存泄露时每次打印到的key值都不一样,没有内存泄露时打印出来的都一样(测试代码快速切换内存泄露/没有内存泄露方法,将前面提到的 var rootServiceProvider = services.BuildServiceProvider() 这句移动到循环内/外即可)。
详细信息如下图:
- 内存泄露时(BuildServiceProvider语句位于循环内)







- 没有内存泄露时(BuildServiceProvider语句位于循环外)






public class EFCoreDbContext : DbContext
{
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)
{
//模拟生成EF Core 缓存key
var key = options.Extensions
.OrderBy(e => e.GetType().Name)
.Aggregate(0L, (t, e) => (t * ) ^ ((long)e.GetType().GetHashCode() * ) ^ e.GetServiceProviderHashCode());
Console.WriteLine($"EF Core当前DbContextOptions实例生成的缓存key为:{key}"); //打印一下影响生成缓存key值的对象名、HashCore、自定义的ServiceProviderHashCode
var oExtensions = options.Extensions.OrderBy(e => e.GetType().Name);
Console.WriteLine($"打印引起key变化的IDbContextOptionsExtension实例列表");
foreach (var item in oExtensions)
{
Console.WriteLine($"item name:{item.GetType().Name} HashCode:{item.GetType().GetHashCode()} ServiceProviderHashCore:{item.GetServiceProviderHashCode()}");
} //从上一步打印结果来看,oExtensions内包含两个对象,SqlServerOptionsExtension和CoreOptionsExtension
//SqlServerOptionsExtension的HashCode和ServiceProviderHashCode每次都一样,不是变化因素,不再跟踪
//CoreOptionsExtension 用来表示由EF Core 管理的选项,而不是由数据库提供商或扩展管理的选项。
//前面提到过的 builder.UseApplicationServiceProvider(applicationServiceProvider);
//就是把当前使用的 ServiceProvider 赋值到 CoreOptionsExtension .ApplicationServiceProvider
var coreOptionsExtension = options.FindExtension<CoreOptionsExtension>();
if (coreOptionsExtension != null)
{
var x = coreOptionsExtension; Console.WriteLine($"\n打印CoreOptionsExtension的一些HashCode\n" +
$"GetServiceProviderHashCode:{x.GetServiceProviderHashCode()} \n" +
$"HashCode:{x.GetHashCode()} \n" +
$"ApplicationServiceProvider HashCode:{x.ApplicationServiceProvider?.GetHashCode()} \n" +
$"InternalServiceProvider HashCode:{x.InternalServiceProvider?.GetHashCode()}"); //模拟GetServiceProviderHashCode的生成过程
var memoryCache = x.MemoryCache ?? x.ApplicationServiceProvider?.GetService<IMemoryCache>();
var loggerFactory = x.LoggerFactory ?? x.ApplicationServiceProvider?.GetService<ILoggerFactory>();
var isSensitiveDataLoggingEnabled = x.IsSensitiveDataLoggingEnabled;
var warningsConfiguration = x.WarningsConfiguration; var hashCode = loggerFactory?.GetHashCode() ?? 0L;
hashCode = (hashCode * ) ^ (memoryCache?.GetHashCode() ?? 0L);
hashCode = (hashCode * ) ^ isSensitiveDataLoggingEnabled.GetHashCode();
hashCode = (hashCode * ) ^ warningsConfiguration.GetServiceProviderHashCode(); if (x.ReplacedServices != null)
{
hashCode = x.ReplacedServices.Aggregate(hashCode, (t, e) => (t * ) ^ e.Value.GetHashCode());
} Console.WriteLine($"\n模拟生成GetServiceProviderHashCode:{hashCode}");
if (x.GetServiceProviderHashCode() == hashCode)
{
Console.WriteLine($"模拟生成的GetServiceProviderHashCode和GetServiceProviderHashCode()获取的一致");
} //打印GetServiceProviderHashCode的生成步骤,对比差异
Console.WriteLine($"\n影响GetServiceProviderHashCode值的因素");
Console.WriteLine($"loggerFactory:{loggerFactory?.GetHashCode() ?? 0L}");
Console.WriteLine($"memoryCache:{memoryCache?.GetHashCode() ?? 0L}");
Console.WriteLine($"isSensitiveDataLoggingEnabled:{isSensitiveDataLoggingEnabled.GetHashCode()}");
Console.WriteLine($"warningsConfiguration:{warningsConfiguration.GetServiceProviderHashCode()}");
}
} public DbSet<TestA> TestA { get; set; }
}
再次运行项目,截图如下:
- 内存泄露时(BuildServiceProvider语句位于循环内)
第二次

第四次
- 没有内存泄露时(BuildServiceProvider语句位于循环外)

可以看到,虽然也有一些变化的地方,但变动的值没有参与计算key,只有上图我圈的部分才参与了key生成,所以缓存可以得到重用。
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddLogging(); //test 1
var options = new DbContextOptionsBuilder<EFCoreDbContext>()
.UseSqlServer(Config.connectionString)
.Options; ////test 2 模拟 AddDbContext
//services.AddMemoryCache(/*c=> { c.ExpirationScanFrequency = new TimeSpan(0,0,5);c.CompactionPercentage = 1;c.SizeLimit = 20000; }*/); //Action<DbContextOptionsBuilder> optionsAction = o => o.UseSqlServer(Config.connectionString);
//Action<IServiceProvider, DbContextOptionsBuilder> optionsAction2 = (sp, o) => optionsAction.Invoke(o); //services.TryAdd(new ServiceDescriptor(typeof(DbContextOptions<EFCoreDbContext>), p =>
//{
// Console.WriteLine($"正在从ServiceProvider[{p.GetHashCode().ToString()}]中获取/创建DbContextOptions<EFCoreDbContext>实例");
// //Console.ReadKey();
// return DbContextOptionsFactory<EFCoreDbContext>(p, optionsAction2);
//}, ServiceLifetime.Scoped));
//services.Add(new ServiceDescriptor(typeof(DbContextOptions), p => p.GetRequiredService<DbContextOptions<EFCoreDbContext>>(), ServiceLifetime.Scoped)); //这两个注册方式二选一, 使用第一行表示启用test 1, 使用第二行表示启用test 2
//services.AddScoped(s => new EFCoreDbContext(options));
//services.AddScoped(s => new EFCoreDbContext(s.GetRequiredService<DbContextOptions<EFCoreDbContext>>())); services.AddScoped<IMemoryCacheTest, MemoryCacheTest>(); services.AddDbContext<EFCoreDbContext>((p, o) =>
{
Console.WriteLine($"UseInternalServiceProvider[{p.GetHashCode().ToString()}]");
o.UseSqlServer(Config.connectionString);
});
//services.AddEntityFrameworkSqlServer().AddDbContext<EFCoreDbContext>((p,o)=> {
// Console.WriteLine($"UseInternalServiceProvider[{p.GetHashCode().ToString()}]");
// o.UseSqlServer(Config.connectionString).UseInternalServiceProvider(p); }); ILogger log; var rootServiceProvider = services.BuildServiceProvider();
for (int i = ; i < ; i++)
{ Console.WriteLine($"rootServiceProvider[{rootServiceProvider.GetHashCode().ToString()}]");
//log = rootServiceProvider.GetService<ILoggerFactory>().AddConsole().CreateLogger<Program>();
//log.LogInformation("日志输出正常");
using (var test = new TestUserCase(rootServiceProvider))
{
test.InvokeMethod();
} } //rootServiceProvider.Dispose();
//rootServiceProvider = null; Console.WriteLine("执行完毕,请按任意键继续...");
Console.ReadKey();
} private static DbContextOptions<TContext> DbContextOptionsFactory<TContext>(IServiceProvider applicationServiceProvider,
Action<IServiceProvider, DbContextOptionsBuilder> optionsAction)
where TContext : DbContext
{
var builder = new DbContextOptionsBuilder<TContext>(
new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>())); Console.WriteLine($"将ServiceProvider[{applicationServiceProvider.GetHashCode().ToString()}]设置为ApplicationServiceProvider");
//Console.ReadKey(); builder.UseApplicationServiceProvider(applicationServiceProvider); optionsAction?.Invoke(applicationServiceProvider, builder); return builder.Options;
}
} //调试时使用查看一下当前系统内的缓存状态
public interface IMemoryCacheTest
{
void Test();
} public class MemoryCacheTest : IMemoryCacheTest
{
private IMemoryCache _cache; public MemoryCacheTest(IMemoryCache memoryCache)
{
_cache = memoryCache;
} public void Test()
{
var x = _cache.GetType();
}
} public class TestUserCase : IDisposable
{
//private IServiceCollection services;
private IServiceScope serviceScope;
private IServiceProvider _serviceProvider;
private EFCoreDbContext _context;
public TestUserCase(/*IServiceCollection services,*/IServiceProvider serviceProvider)
{
//this.services = services;
_serviceProvider = serviceProvider;
} public void InvokeMethod()
{ //_serviceProvider = services.BuildServiceProvider();
using (serviceScope = _serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var internalServiceProvider = serviceScope.ServiceProvider;
Console.WriteLine($"获取一个新的ServiceProvider[{internalServiceProvider.GetHashCode().ToString()}]"); Console.WriteLine($"获取一个新的ServiceProviderType[{internalServiceProvider.GetType().GetHashCode().ToString()}]"); var memoryCache = internalServiceProvider?.GetService<IMemoryCache>();
var loggerFactory = internalServiceProvider?.GetService<ILoggerFactory>(); Console.WriteLine($"当前ServiceProvider.GetService<IMemoryCache>():{memoryCache.GetHashCode().ToString()}");
Console.WriteLine($"当前ServiceProvider.GetService<ILoggerFactory>():{loggerFactory.GetHashCode().ToString()}"); //using (_context = internalServiceProvider.GetRequiredService<EFCoreDbContext>())
//{
_context = internalServiceProvider.GetRequiredService<EFCoreDbContext>();
Printf(_serviceProvider, internalServiceProvider, _context, serviceScope);
//} var cacheTest = _serviceProvider.GetRequiredService<IMemoryCacheTest>();
cacheTest.Test(); //(internalServiceProvider as IDisposable)?.Dispose();
//internalServiceProvider = null;
}
} public void Printf(IServiceProvider sp, IServiceProvider _serviceProvider, EFCoreDbContext _context, IServiceScope _serviceScope)
{
for (var i = ; i < ; i++)
{
var testA = _context.TestA.AsNoTracking().FirstOrDefault();
//_context.TestA.Add(new TestA() { Name = "test"});
//_context.SaveChanges(); Console.WriteLine($"RootSP:{sp.GetHashCode()} CurrentSP:{_serviceProvider.GetHashCode()} DbContext:{_context?.GetHashCode()} Index:{i}");
}
} private bool disposed = false; public void Dispose()
{
Console.WriteLine($"{this.GetType().Name}.Dispose()...");
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
// Note disposing has been done.
disposed = true; //serviceScope?.Dispose();
//serviceScope = null;
////
////(_serviceProvider as IDisposable)?.Dispose();
////_serviceProvider = null; //_context?.Dispose();
//_context = null;
}
}
} public class EFCoreDbContext : DbContext
{
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)
{
//模拟生成EF Core 缓存key
var key = options.Extensions
.OrderBy(e => e.GetType().Name)
.Aggregate(0L, (t, e) => (t * ) ^ ((long)e.GetType().GetHashCode() * ) ^ e.GetServiceProviderHashCode());
Console.WriteLine($"EF Core当前DbContextOptions实例生成的缓存key为:{key}"); //打印影响生成缓存key值的对象名、HashCore、自定义的ServiceProviderHashCode
var oExtensions = options.Extensions.OrderBy(e => e.GetType().Name);
Console.WriteLine($"打印引起key变化的IDbContextOptionsExtension实例列表");
foreach (var item in oExtensions)
{
Console.WriteLine($"item name:{item.GetType().Name} HashCode:{item.GetType().GetHashCode()} ServiceProviderHashCore:{item.GetServiceProviderHashCode()}");
} //从上一步打印结果来看,oExtensions内包含两个对象,SqlServerOptionsExtension和CoreOptionsExtension
//SqlServerOptionsExtension的HashCode和ServiceProviderHashCode每次都一样,不是变化因素,不再跟踪
//CoreOptionsExtension 用来表示由EF Core 管理的选项,而不是由数据库提供商或扩展管理的选项。
//上面的代码中 builder.UseApplicationServiceProvider(applicationServiceProvider); 这句就是把当前 ServiceProvider 设置到该类型实例的 ApplicationServiceProvider 属性
var coreOptionsExtension = options.FindExtension<CoreOptionsExtension>();
if (coreOptionsExtension != null)
{
var x = coreOptionsExtension; Console.WriteLine($"\n打印CoreOptionsExtension的一些HashCode\n" +
$"GetServiceProviderHashCode:{x.GetServiceProviderHashCode()} \n" +
$"HashCode:{x.GetHashCode()} \n" +
$"ApplicationServiceProvider HashCode:{x.ApplicationServiceProvider?.GetHashCode()} \n" +
$"InternalServiceProvider HashCode:{x.InternalServiceProvider?.GetHashCode()}"); //模拟GetServiceProviderHashCode的生成过程
var memoryCache = x.MemoryCache ?? x.ApplicationServiceProvider?.GetService<IMemoryCache>();
var loggerFactory = x.LoggerFactory ?? x.ApplicationServiceProvider?.GetService<ILoggerFactory>();
var isSensitiveDataLoggingEnabled = x.IsSensitiveDataLoggingEnabled;
var warningsConfiguration = x.WarningsConfiguration; var hashCode = loggerFactory?.GetHashCode() ?? 0L;
hashCode = (hashCode * ) ^ (memoryCache?.GetHashCode() ?? 0L);
hashCode = (hashCode * ) ^ isSensitiveDataLoggingEnabled.GetHashCode();
hashCode = (hashCode * ) ^ warningsConfiguration.GetServiceProviderHashCode(); if (x.ReplacedServices != null)
{
hashCode = x.ReplacedServices.Aggregate(hashCode, (t, e) => (t * ) ^ e.Value.GetHashCode());
} Console.WriteLine($"\n模拟生成GetServiceProviderHashCode:{hashCode}");
if (x.GetServiceProviderHashCode() == hashCode)
{
Console.WriteLine($"模拟生成的GetServiceProviderHashCode和GetServiceProviderHashCode()获取的一致");
} //打印GetServiceProviderHashCode的生成步骤,对比差异
Console.WriteLine($"\n影响GetServiceProviderHashCode值的因素");
Console.WriteLine($"loggerFactory:{loggerFactory?.GetHashCode() ?? 0L}");
Console.WriteLine($"memoryCache:{memoryCache?.GetHashCode() ?? 0L}");
Console.WriteLine($"isSensitiveDataLoggingEnabled:{isSensitiveDataLoggingEnabled.GetHashCode()}");
Console.WriteLine($"warningsConfiguration:{warningsConfiguration.GetServiceProviderHashCode()}");
} } public DbSet<TestA> TestA { get; set; }
} public class TestA
{
public long Id { get; set; }
public string Name { get; set; }
}
一个小问题:生成缓存key中的 397 是什么?
补充:

循环20次以上就可看到这样的提示。
为英文不好的同学献上google翻译(google真TM机智,把microsoft.com翻译成google.com)
深入研究EF Core AddDbContext 引起的内存泄露的原因的更多相关文章
- 牛客网Java刷题知识点之内存溢出和内存泄漏的概念、区别、内存泄露产生原因、内存溢出产生原因、内存泄露解决方案、内存溢出解决方案
不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号: 大数据躺过的坑 Java从入门到架构师 人工智能躺过的坑 ...
- Java内存泄露的原因
Java内存泄露的原因 1.静态集合类像HashMap.Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector ...
- JS内存泄露常见原因
详细内容请点击 分享的笔记本-前端 开发中,我们常遇见的一些关于js内存泄露的问题,有时候我们常常会找半天找不出原因,这里给大家介绍简单便捷的方法 1.闭包上下文绑定后没有释放: 2.观察者模式在 ...
- Activity内部Handler引起内存泄露的原因分析
有时在Activity中使用Handler时会提示一个内存泄漏的警告,代码通常如下: public class MainActivity extends Activity { private Text ...
- Android内存泄露的原因
(一)释放对象的引用,误将一个本来生命周期短的对象存放到一个生命周期相对较长的对象中,也称“对象游离“.隐蔽的内部类(Anonymous Inner Class): mHandler = new Ha ...
- js内存泄露的原因
1.意外的全局变量 function fun(){ a=19//全局变量 console.log(a) } 2.未及时清理计时器或者回调函数 //记得及时清理定时器 var intervalId=se ...
- 一步步学习EF Core(3.EF Core2.0路线图)
前言 这几天一直在研究EF Core的官方文档,暂时没有发现什么比较新的和EF6.x差距比较大的东西. 不过我倒是发现了EF Core的路线图更新了,下面我们就来看看 今天我们来看看最新的EF Cor ...
- Java垃圾回收机制以及内存泄露
1.Java的内存泄露介绍 首先明白一下内存泄露的概念:内存泄露是指程序执行过程动态分配了内存,可是在程序结束的时候这块内存没有被释放,从而导致这块内存不可用,这就是内存 泄露,重新启动计算机能够解决 ...
- 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
随机推荐
- [十四]基础类型之StringBuffer 与 StringBuilder对比
StringBuilder 和 StringBuffer是高度类似的两个类 StringBuilder是StringBuffer的版本改写,下面从几个方面简单的对比下他们的区别 类继承关系 上文中,我 ...
- 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(13)- 从Serial(1-bit SPI) EEPROM/NOR恢复启动
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Serial EEPROM/NOR恢复启动. 在前几篇里痞子衡介绍的Boot Device都属于主动启 ...
- cocos creator主程入门教程(二)—— 弹窗管理
五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以TypeScript为介绍语言. 我们已经知道怎样制作.加载.显示界面.但cocos没有提供一个弹窗管理模块,对于一个多人合作的项目,没有 ...
- selenium加载配置参数,让chrome浏览器不出现‘Chrome正在受到自动软件的控制’的提示语,以及后台静默模式启动自动化测试,不占用桌面的方法
一:自动化测试的时候,启动浏览器出现‘Chrome正在受到自动软件的控制’,怎么样隐藏,今天学习分享: 在浏览器配置里加个参数,忽略掉这个警告提示语,disable_infobars option = ...
- 在Windows 下如何使用 AspNetCore Api 和 consul
一.概念:什么是consul: Consul 是有多个组件组成的一个整体,作用和Eureka,Zookeeper相当,都是用来做服务的发现与治理. Consul的特性: 1. 服务的发现:consul ...
- Java开发笔记(五十三)关键字final的用法
前面介绍了多态的相关用法,可以看到一个子类从父类继承之后,便能假借父类的名义到处晃悠.这种机制在正常情况之下没啥问题,但有时为了预防意外发生,往往只接受当事人来处理,不希望它的儿子乃至孙子来瞎掺和.可 ...
- MongoDB初了解——用户权限
本文所述MongoDB版本为4.0.5,笔者对MongoDB刚接触,对各个版本的MongoDB不甚了解,本文不对该版本的MongoDB做特性介绍,所涉及命令也许对其余版本不适用. 因为目前有一个试验性 ...
- 跨域iframe如何实现高度自适应?
经常有项目会要求实现iframe高度自适应,如果是同域的还好说,如果是跨域的,父页面没有办法操作子页面,想要正确获取子页面高度的话,可以采用以下办法: 方法一:使用HTML5 postMessage ...
- 使用addviewController()实现无业务逻辑跳转
需要实现WebMvcConfigurer类,重写addViewControllers方法. 添加@Configuration,等价于xml配置. package dbzx.config; import ...
- 我想要革命想要解脱——bootstrap常见问题及解决方式
最近一个月,恍若隔世,天天加班,昨晚终于发版了,今天才喘一口气.有时候,即便你工作效率再怎么高,撸码再怎么快也无可避免的会加班.不信的话,可以先给你定一个交付时间,然后不断的给你加需求,就让你一个人做 ...