在上一章中,我们介绍了 ASP.NET Core 的启动过程,主要是对 WebHost 源码的探索。而本文则是对上文的一个补充,更加偏向于实战,详细的介绍一下我们在实际开发中需要对 Hosting 做一些配置时经常用到的几种方式。

目录

本系列文章从源码分析的角度来探索 ASP.NET Core 的运行原理,分为以下几个章节:

ASP.NET Core 运行原理解剖[1]:Hosting

ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍(Current)

  1. WebHostBuild
  2. ISartup
  3. IHostingStartup
  4. IStartupFilter
  5. IHostedService
  6. IApplicationLifetime

ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

ASP.NET Core 运行原理解剖[5]:Authentication

WebHostBuild

WebHostBuild 用来构建 WebHost ,也是我们最先接触的一个类,它提供了如下方法:

ConfigureAppConfiguration

Configuration 在 ASP.NET Core 进行了全新的设计,使其更加灵活简洁,可以支持多种数据源。在 ASP.NET Core 1.x 中,我们是在Startup的构造函数中配置各种数据源的,而在 ASP.NET Core 2.0 中则移动了到Program中,这样能与控制台应用程序保持一致:

public static class WebHostBuilderExtensions
{
public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
{
return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));
}
} public class WebHostBuilder : IWebHostBuilder
{
private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureAppConfigurationBuilderDelegates;
public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
if (configureDelegate == null)
{
throw new ArgumentNullException(nameof(configureDelegate));
} _configureAppConfigurationBuilderDelegates.Add(configureDelegate);
return this;
}
}

_configureAppConfigurationBuilderDelegates委托会在 WebHostBuilder 的BuildCommonServices方法中执行,最后生成 IConfiguration 对象并以单例的形式注册到 DI 系统中, 我们可以在Startup以及应用程序的任何地方,通过 DI 系统来获取到:

foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
{
configureAppConfiguration(_context, builder);
}
var configuration = builder.Build();
services.AddSingleton<IConfiguration>(configuration);

而在 上一章 中也介绍过,在CreateDefaultBuilder中会通过该方法来添加appsettinggs.json等基本配置的配置源。

UseSetting

UseSetting 是一个非常重要的方法,它用来配置 WebHost 中的 IConfiguration 对象。需要注意与上面ConfigureAppConfiguration的区别, WebHost 中的 Configuration 只限于在 WebHost 使用,并且我们不能配置它的数据源,它只会读取ASPNETCORE_开头的环境变量:

private IConfiguration _config;

public WebHostBuilder()
{
_config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
}

而我们比较熟悉的当前执行环境,也是通过该_config来读取的,虽然我们不能配置它的数据源,但是它为我们提供了一个UseSetting方法,为我们提供了一个设置_config的机会:

public string GetSetting(string key)
{
return _config[key];
}

而我们通过UseSetting设置的变量最终也会以MemoryConfigurationProvider的形式添加到上面介绍的ConfigureAppConfiguration所配置的IConfiguration对象中。

UseStartup

UseStartup 这个我们都比较熟悉,它用来显式注册我们的Startup类,可以使用泛性,Type , 和程序集名称三种方式来注册:


// 常用的方法
public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
{
return hostBuilder.UseStartup(typeof(TStartup));
} // 通过指定的程序集来注册 Startup 类
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName)
{
if (startupAssemblyName == null)
{
throw new ArgumentNullException(nameof(startupAssemblyName));
} return hostBuilder
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.UseSetting(WebHostDefaults.StartupAssemblyKey, startupAssemblyName);
} // 最终的 Startup 类注册方法,上面两种只是一种简写形式
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
....
}

具体的注册方式,在 上一章 也介绍过,就是通过反射创建实例,然后注入到 DI 系统中。

ConfigureLogging

ConfigureLogging 用来配置日志系统,在 ASP.NET Core 1.x 中是在Startup类的Configure方法中,通过ILoggerFactory扩展来注册的,在 ASP.NET Core 中也变得更加简洁,并且统一通过 WebHostBuild 来配置:

public static class WebHostBuilderExtensions
{
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices(collection => collection.AddLogging(configureLogging));
} public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
}
}

AddLogging 是Microsoft.Extensions.Logging提供的扩展方法,更具体的可以看我之前介绍的 ASP.NET Core 源码学习之 Logging 系列。

ConfigureServices

在上面的几个方法中,多次用到 ConfigureServices,而 ConfigureServices 与 Starup 中的 ConfigureServices 类似,都是用来注册服务的:

private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;

public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
if (configureServices == null)
{
throw new ArgumentNullException(nameof(configureServices));
} _configureServicesDelegates.Add(configureServices);
return this;
}

但不同的是_configureServicesDelegates的执行时机较早,是在WebHostBuilder的Build方法中执行的,所以会参与 WebHost 中hostingServiceProvider的构建。

其它

WebHostBuild 中还有很多配置的方法,就不再一一细说,在这里简单介绍一下:

  • UseContentRoot 使用UseSetting方法配置IConfiguration["contentRoot"],表示应用程序所在的默认文件夹地址,如 MVC 中视图的查询根目录。

  • UseWebRoot 使用UseSetting方法配置IConfiguration["webroot"],用来指定可让外部可访问的静态资源路径,默认为wwwroot,并且是以contentRoot为根目录。

  • CaptureStartupErrors 使用UseSetting方法配置IConfiguration["captureStartupErrors"],表示是否捕捉启动时的异常,如果为ture,则在启动时发生异常也会启动 Http Server,并显示错误页面,否则,不会启动 Http Server。

  • UseEnvironment 使用UseSetting方法配置IConfiguration["environment"],用来指定执行环境。

  • UseServer 用来配置 Http Server 服务,UseKestrel便是此方法的简写形式。

  • UseUrls 使用UseSetting方法配置IConfiguration["urls"],用来配置 Http 服务器地址,多个使用;分割。

  • UseShutdownTimeout 使用UseSetting方法配置IConfiguration["shutdownTimeoutSeconds"],用来设置 ASP.NET Core 停止时等待的时间。

  • DetailedErrors 表示是否显示详细的错误信息,可为true/false1/0,默认为 false,但它没有提供直接配置的方法,可以通过UseSetting来指定IConfiguration["detailedErrors"]

ISartup

ISartup 是我们比较熟悉的,因为在我们创建一个默认的 ASP.NET Core 项目时,都会有一个Startup.cs文件,包含三个约定的方法,按执行顺序排列如下:

1. ConfigureServices

ASP.NET Core 框架本身提供了一个 DI(依赖注入)系统,并且可以非常灵活的去扩展,很容易的切换成其它的 DI 框架(如 Autofac,Ninject 等)。在 ASP.NET Core 中,所有的实例都是通过这个 DI 系统来获取的,并要求我们的应用程序也使用 DI 系统,以便我们能够开发出更具弹性,更易维护,测试的应用程序。总之在 ASP.NET Core 中,一切皆注入。关于 “依赖注入” 这里就不再多说。

在 DI 系统中,想要获取服务,首先要进行注册,而ConfigureServices方法便是用来注册服务的。

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IUserService, UserService>();
}

如上,我们为IUserService接口注册了一个UserService类型的实例。

2. ConfigureContainer(不常用)

ConfigureContainer 是用来替换 DI 框架的,如下,我们将 ASP.NET Core 内置的 DI 框架替换为 Autofac

public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new AutofacModule());
}

虽然 ASP.NET Core 自带的 DI 系统只提供了构造函数注入,以及不支持命名实例等,但我喜欢它的简洁,并且不太喜欢依赖太多第三库,一直也只使用了内置的DI框架,因此对这个方法也不太了解,就不再多说。

3. Configure

Configure 接收一个IApplicationBuilder类型参数,而IApplicationBuilder上一章 中介绍过,它是用来构建请求管道的,因此,也可以说 Configure 方法是用来配置请求管道的,通常会在这里会注册一些中间件。

public void Configure(IApplicationBuilder app)
{
app.Use(next =>
{
return async (context) =>
{
await context.Response.WriteAsync("Hello ASP.NET Core!");
};
});
}

所谓中间件,也就是对 HttpContext 进行处理的一种便捷方式,下文会详细来介绍。而如上代码,我们注册了一个最简单的中间件,通过浏览器访问,便可以看到 “Hello ASP.NET Core!” 。

通常,我们的 Startup 类并没有去实现IStartup接口,这是因为我们在Configure方法中,大多时候可能需要获取一些其它的服务,如我刚才注册的IUserService,我们可以直接添加到 Configure 方法的参数列表当中:

public void Configure(IApplicationBuilder app, IUserService userService) { }

ASP.NET Core 会通过 DI 系统来解析到 userService 实例,但是 ASP.NET Core 中的 DI 系统是不支持普通方法的参数注入的,而是手动通过反射的方式来实现的:

services.AddSingleton(typeof(IStartup), sp =>
{
var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
return new ConventionBasedStartup(methods);
});

而通过反射也可以为我们带来更大的灵活性,上面的LoadMethods方法会根据当前的执行环境名称来查找适当的方法名:

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
var configureMethod = FindConfigureDelegate(startupType, environmentName);
} private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod);
}

更具体的可以查看 StartupLoader,ASP.NET Core 会根据当前环境的不同,而执行不同的方法:

public void ConfigureServices(IServiceCollection services) { }

public void ConfigureDevelopmentServices(IServiceCollection services) { }

public void ConfigureContainer(ContainerBuilder builder) {}

public void ConfigureDevelopmentContainer(ContainerBuilder builder) { }

public void Configure(IApplicationBuilder app) { }

public void ConfigureDevelopment(IApplicationBuilder app) { }

如上,当在Development环境上执行时,会选择带Development的方法来执行。

而在默认模版中是通过UseStartup<Startup>的方式来注册 Startup 类的,我们也可以使用上面介绍的指定程序集名称的方式来注册:

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup("EmptyWebDemo")
.Build();

如上,我们指定在 EmptyWebDemo 中查找Startup类,这样还有一个额外的好处,WebHost 同样会根据当前的执行环境来选择不同的Startup类(如StartupDevelopment),与上面介绍的Startup中方法的查询方式一样。

IHostingStartup

上面,我们介绍了Sartup,而一个项目中只能一个Sartup,因为如果配置多个,则最后一个会覆盖之前的。而在一个多层项目中,Sartup类一般是放在展现层中,我们在其它层也需要注册一些服务或者配置请求管道时,通常会写一个扩展方法:

public static class EfRepositoryExtensions
{
public static void AddEF(this IServiceCollection services,string connectionStringName)
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionStringName), opt => opt.EnableRetryOnFailure())
); services.TryAddScoped<IDbContext, AppDbContext>();
services.TryAddScoped(typeof(IRepository<,>), typeof(EfRepository<,>)); ...
} public static void UseEF(IApplicationBuilder app)
{
app.UseIdentity();
}
}

然后在 Startup 中调用这些扩展方法:

public void ConfigureDevelopmentServices(IServiceCollection services)
{
services.AddEF(Configuration.GetConnectionString("DefaultConnection");
} public void ConfigureDevelopment(IApplicationBuilder app)
{
services.UseEF();
}

感觉这种方式非常丑陋,而在上一章中,我们知道 WebHost 会在 Starup 这前调用 IHostingStartup,于是我们便以如下方式来实现:

[assembly: HostingStartup(typeof(Zero.EntityFramework.EFRepositoryStartup))]
namespace Zero.EntityFramework
{
public class EFRepositoryStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionStringName), opt => opt.EnableRetryOnFailure())
); services.TryAddScoped<IDbContext, AppDbContext>();
services.TryAddScoped(typeof(IRepository<,>), typeof(EfRepository<,>)); ...
}); builder.Configure(app => {
app.UseIdentity();
});
}
}
}

如上,只需实现 IHostingStartup 接口,要清爽简单的多,怎一个爽字了得!不过,还需要进行注册才会被WebHost执行,首先要指定HostingStartupAttribute程序集特性,其次需要配置 WebHost 中的 IConfiguration[hostingStartupAssemblies],以便 WebHost 能找到我们的程序集,可以使用如下方式配置:

WebHost.CreateDefaultBuilder(args)
// 如需指定多个程序集时,使用 ; 分割
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "Zero.Application;Zero.EntityFramework")

这样便完成了 IHostingStartup 注册,不过还需要将包含IHostingStartup的程序集放到 Bin 目录下,否则根本无法加载。不过 ASP.NET Core 也提供了类似插件的方式来指定IHostingStartup程序集的查找位置,可通过设置DOTNET_ADDITIONAL_DEPSASPNETCORE_HOSTINGSTARTUPASSEMBLIES来实现,而这里就不再多说。

IHostingStartup 是由 WebHostBuilder 来调用的,执行时机较早,在创建 WebHost 之前执行,因此可以替换一些在 WebHost 中需要使用的服务。

IStartupFilter

IStartupFilter 是除StartupHostingStartup之处另一种配置IApplicationBuilder的方式:

public interface IStartupFilter
{
Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}

它只有一个Configure方法,是对 Starup 类中Configure方法的拦截器,给我们一个在Configure方法执行之前进行一些配置的机会。

让我们实践一把,先定义2个 StartupFilter:

public class A : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
Console.WriteLine("This is A1!");
return app =>
{
Console.WriteLine("This is A2!");
next(app);
};
}
} public class B : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
Console.WriteLine("This is B1!");
return app =>
{
Console.WriteLine("This is B2!");
next(app);
};
}
}

然后让他们注册到DI系统中,WebHost 在执行 Starup 类中Configure方法之前,会从 DI 系统中获取所有的IStartupFilter来执行:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IStartupFilter, A>();
services.AddSingleton<IStartupFilter, B>();
} public void Configure(IApplicationBuilder app)
{
Console.WriteLine("This is Configure!");
app.Use(next =>
{
return async (context) =>
{
await context.Response.WriteAsync("Hello ASP.NET Core!");
};
});
}

最终,它他的执行顺序为:B1 -> A1 -> A2 -> B2 -> Configure 。

IHostedService

当我们希望随着 ASP.NET Core 的启动,来执行一些后台任务(如:定期的刷新缓存等)时,并在 ASP.NET Core 停止时,可以优雅的关闭,则可以使用IHostedService,它有如下定义:

public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken); Task StopAsync(CancellationToken cancellationToken);
}

很简单,只有开始和停止两个方法,它的用法大概是这个样子的:

public class CacheHostService : IHostedService
{
private readonly ICacheService _cacheService;
private CancellationTokenSource _cts;
private Task _executingTask; public CacheHostService(ICacheService cacheService)
{
_cacheService = cacheService;
} public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = Task.Run(async () =>
{
while (!_cts.IsCancellationRequested)
{
Console.WriteLine("cancellationToken:" + _cts.IsCancellationRequested);
await _cacheService.Refresh();
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
});
return Task.CompletedTask;
} public async Task StopAsync(CancellationToken cancellationToken)
{
// 发送停止信号,以通知我们的后台服务结束执行。
_cts.Cancel(); // 等待后台服务的停止,而 ASP.NET Core 大约会等待5秒钟(可在上面介绍的UseShutdownTimeout方法中配置),如果还没有执行完会发送取消信号,以防止无限的等待下去。
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)); cancellationToken.ThrowIfCancellationRequested();
}
}

如上,我们定义了一个在台后每5秒刷新一次缓存的服务,并在 ASP.NET Core 程序停止时,优雅的关闭。最后,将它注册到 DI 系统中即可:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICacheService, CacheService>();
services.AddSingleton<IHostedService, CacheHostService>();
}

WebHost 在启动 HTTP Server 之后,会从 DI 系统中获取所有的IHostedService,来启动我们注册的 HostedService,参见上一章

IApplicationLifetime

IApplicationLifetime用来实现 ASP.NET Core 的生命周期钩子,我们可以在 ASP.NET Core 停止时做一些优雅的操作,如资源的清理等。它有如下定义:

public interface IApplicationLifetime
{
CancellationToken ApplicationStarted { get; } CancellationToken ApplicationStopping { get; } CancellationToken ApplicationStopped { get; } void StopApplication();
}

IApplicationLifetime已被 ASP.NET Core 注册到 DI 系统中,我们使用的时候,只需要注入即可。它有三个CancellationToken类型的属性,是异步方法终止执行的信号,表示 ASP.NET Core 生命周期的三个阶段:启动,开始停止,已停止。

public void Configure(IApplicationBuilder app, IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStarted.Register(() => Console.WriteLine("Started"));
appLifetime.ApplicationStopping.Register(() => Console.WriteLine("Stopping"));
appLifetime.ApplicationStopped.Register(() =>
{
Console.WriteLine("Stopped");
Console.ReadKey();
}); app.Use(next =>
{
return async (context) =>
{
await context.Response.WriteAsync("Hello ASP.NET Core!");
appLifetime.StopApplication();
};
});
}

执行结果如下:

在上一章中我们提到过, IApplicationLifetime 的启动信号是在 WebHostStartAsync方法中触发的,而没有提到停止信号的触发,在这里补充一下:

internal class WebHost : IWebHost
{
public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken))
{
.... // 设置 Task 的超时时间,上文在 IHostedService 中提到过
var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token;
if (!cancellationToken.CanBeCanceled)
{
cancellationToken = timeoutToken;
}
else
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token;
} // 触发 Stopping 信号
_applicationLifetime?.StopApplication(); // 停止 Http Server
if (Server != null)
{
await Server.StopAsync(cancellationToken).ConfigureAwait(false);
} // 停止 我们注册的 IHostService
if (_hostedServiceExecutor != null)
{
await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false);
} // 发送 Stopped 通知
_applicationLifetime?.NotifyStopped();
}
}

总结

本文详细介绍了对 WebHost 的配置,结合 上一章,对 ASP.NET Core 的启动流程也基本清楚了,下一章就来介绍一下请求管道的创建,敬请期待!

参考资料:

ASP.NET Core 运行原理解剖[2]:Hosting补充之配置介绍的更多相关文章

  1. ASP.NET Core 运行原理解剖[1]:Hosting

    ASP.NET Core 是新一代的 ASP.NET,第一次出现时代号为 ASP.NET vNext,后来命名为ASP.NET 5,随着它的完善与成熟,最终命名为 ASP.NET Core,表明它不是 ...

  2. ASP.NET Core 运行原理解剖[3]:Middleware-请求管道的构成

    在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 HttpModule 来实现对请求管道事件监 ...

  3. ASP.NET Core 运行原理解剖[4]:进入HttpContext的世界

    HttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到Http ...

  4. ASP.NET Core 运行原理解剖[5]:Authentication

    在现代应用程序中,认证已不再是简单的将用户凭证保存在浏览器中,而要适应多种场景,如App,WebAPI,第三方登录等等.在 ASP.NET 4.x 时代的Windows认证和Forms认证已无法满足现 ...

  5. ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行

    ASP.NET Core 运行原理剖析1:初始化WebApp模版并运行 核心框架 ASP.NET Core APP 创建与运行 总结 之前两篇文章简析.NET Core 以及与 .NET Framew ...

  6. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  7. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  8. ASP.NET Core 运行原理剖析 (转载)

    1.1. 概述 在ASP.NET Core之前,ASP.NET Framework应用程序由IIS加载.Web应用程序的入口点由InetMgr.exe创建并调用托管.以初始化过程中触发HttpAppl ...

  9. ASP.NET的运行原理与运行机制 如何:为 IIS 7.0 配置 <system.webServer> 节

    https://technet.microsoft.com/zh-cn/sysinternals/bb763179.aspx 当一个HTTP请求到服务器并被IIS接收到之后,IIS首先通过客户端请求的 ...

随机推荐

  1. asp.net修行入门讨论

    突然想起来大学的一个专业课老师,教C,C#,他的教学特点就是只教简单知识,现在想想真有道理,假如上来就教我们枯燥难以理解的高深知识,我们会恐惧编程的,极大地打击学习的兴趣,所以他C语言的指针从来没教过 ...

  2. mooc上学习acllib后写的包含背景音乐的小涂鸦板(初入江湖,大佬勿喷)

    #include "acllib.h"ACL_Sound sound1;ACL_Image img;//开始图ACL_Image img1;//涂鸦图ACL_Color c=RED ...

  3. Spring NamedParameterJdbcTemplate命名参数查询条件封装, NamedParameterJdbcTemplate查询封装

    Spring NamedParameterJdbcTemplate命名参数查询条件封装, NamedParameterJdbcTemplate查询封装 >>>>>> ...

  4. 【javascript】继承

    1. js 其实是一个非面向对象的语言,通过对象的深浅复制完成继承 2. 继承方法 继承的方法有两种 1)prototype 原型模式 举个例子 var Animal = function () { ...

  5. 【Django】学习资料

    一.基础 http://www.ibm.com/developerworks/cn/linux/l-django/ 安装.数据库连接.url路由转发 http://djangobook.py3k.cn ...

  6. tar命令(转)

    把常用的tar解压命令总结下,当作备忘: tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个, ...

  7. phpunit实践笔记

    phpunit成为单元测试的代名词已成为共识, 但很多在实际编写测试过程中遇到的很多问题通过手册.网上搜索都很难找到相关资料, 大部分都得通过查看源代码和实践的代码经验解决.欢迎大家拍砖.(在此之前请 ...

  8. 基于Windows环境下Myeclipse10.0下载安装破解及jdk的下载安装及环境变量的配置

    jdk的安装及环境变量的配置 1.安装JDK开发环境 附上jdk安装包的百度云链接 链接:http://pan.baidu.com/s/1mh6QTs8 密码:jkb6(当然自行去官网下载最好哒,可以 ...

  9. 删除物品[JLOI2013]

    题目描述 箱子再分配问题需要解决如下问题:  (1)一共有N个物品,堆成M堆.  (2)所有物品都是一样的,但是它们有不同的优先级.  (3)你只能够移动某堆中位于顶端的物品.  (4)你可以把任意一 ...

  10. java中的各种数据类型在内存中存储的方式

    原文地址:http://blog.csdn.net/aaa1117a8w5s6d/article/details/8251456 1.Java是如何管理内存的 java的内存管理就是对象的分配和释放问 ...