ABP中的模块初始化过程(一)
在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp.Net Core项目中,我们启动一个带Swagger UI的Web API项目为例,在介绍这个Web API项目之前我们先来看看整个Swagger 文档的样式。
我们定义的WebAPI最终都会以Swagger文档这种形式来展现出来,通过这种形式也是非常方便我们进行代码的调试的,在进行网站的前后端分离开发的过程中,前端去定义接口后端根据前端定义的接口进行开发,这个模式能够实现整个开发的分离,当然这篇文章主要不是介绍如何去进行前后端分离开发而是重点介绍如何ABP模块中代码的加载顺序,前面的截图是整个ABP项目的启动界面,通过这些能够让我们对整个项目有一个概念性的认识和理解。
在整个项目的运行过程中,首先也是从Program类中开始的,首先执行Program类中的静态Main方法,然后在Main方法中会创建一个IWebHost对象,然后执行Run方法,看起来像下面的形式:
- public class Program
- {
- private static IConfiguration Configuration { get; set; }
- public static void Main(string[] args)
- {
- BuildWebHost(args).Run();
- }
- public static IWebHost BuildWebHost(string[] args)
- {
- return WebHost.CreateDefaultBuilder(args)
- .UseStartup<Startup>()
- .Build();
- }
- }
在这里面会执行UseStartup<Startup>()这个方法,然后会将控制主逻辑转移到Startup这个类中,下面我们再来看一看Startup这个类中执行了些什么操作?
- public class Startup
- {
- private const string _defaultCorsPolicyName = "localhost";
- private readonly IConfigurationRoot _appConfiguration;
- public Startup(IHostingEnvironment env)
- {
- _appConfiguration = env.GetAppConfiguration();
- }
- public IServiceProvider ConfigureServices(IServiceCollection services)
- {
- // MVC
- services.AddMvc(
- options => options.Filters.Add(new CorsAuthorizationFilterFactory(_defaultCorsPolicyName))
- );
- IdentityRegistrar.Register(services);
- AuthConfigurer.Configure(services, _appConfiguration);
- #if FEATURE_SIGNALR_ASPNETCORE
- services.AddSignalR();
- #endif
- // Configure CORS for angular2 UI
- services.AddCors(
- options => options.AddPolicy(
- _defaultCorsPolicyName,
- builder => builder
- .WithOrigins(
- // App:CorsOrigins in appsettings.json can contain more than one address separated by comma.
- _appConfiguration["App:CorsOrigins"]
- .Split(",", StringSplitOptions.RemoveEmptyEntries)
- .Select(o => o.RemovePostFix("/"))
- .ToArray()
- )
- .AllowAnyHeader()
- .AllowAnyMethod()
- )
- );
- // Swagger - Enable this line and the related lines in Configure method to enable swagger UI
- services.AddSwaggerGen(options =>
- {
- options.SwaggerDoc("v1", new Info { Title = "Server API", Version = "v1" });
- options.DocInclusionPredicate((docName, description) => true);
- // Define the BearerAuth scheme that's in use
- options.AddSecurityDefinition("bearerAuth", new ApiKeyScheme()
- {
- Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
- Name = "Authorization",
- In = "header",
- Type = "apiKey"
- });
- // Assign scope requirements to operations based on AuthorizeAttribute
- options.OperationFilter<SecurityRequirementsOperationFilter>();
- });
- // Configure Abp and Dependency Injection
- return services.AddAbp<ServerWebHostModule>(
- // Configure Log4Net logging
- options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
- f => f.UseAbpLog4Net().WithConfig("log4net.config")
- )
- );
- }
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
- {
- app.UseAbp(options => { options.UseAbpRequestLocalization = false; }); // Initializes ABP framework.
- app.UseCors(_defaultCorsPolicyName); // Enable CORS!
- app.UseStaticFiles();
- app.UseAuthentication();
- app.UseAbpRequestLocalization();
- #if FEATURE_SIGNALR
- // Integrate with OWIN
- app.UseAppBuilder(ConfigureOwinServices);
- #elif FEATURE_SIGNALR_ASPNETCORE
- app.UseSignalR(routes =>
- {
- routes.MapHub<AbpCommonHub>("/signalr");
- });
- #endif
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "defaultWithArea",
- template: "{area}/{controller=Home}/{action=Index}/{id?}");
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- // Enable middleware to serve generated Swagger as a JSON endpoint
- app.UseSwagger();
- // Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
- app.UseSwaggerUI(options =>
- {
- options.SwaggerEndpoint("/swagger/v1/swagger.json", "Server API V1");
- options.IndexStream = () => Assembly.GetExecutingAssembly()
- .GetManifestResourceStream("SunLight.Server.Web.Host.wwwroot.swagger.ui.index.html");
- }); // URL: /swagger
- }
- #if FEATURE_SIGNALR
- private static void ConfigureOwinServices(IAppBuilder app)
- {
- app.Properties["host.AppName"] = "Server";
- app.UseAbp();
- app.Map("/signalr", map =>
- {
- map.UseCors(CorsOptions.AllowAll);
- var hubConfiguration = new HubConfiguration
- {
- EnableJSONP = true
- };
- map.RunSignalR(hubConfiguration);
- });
- }
- #endif
- }
上面的过程熟悉Asp.Net Core开发的都应该十分熟悉,在Startup类中定义了两个主要的方法:ConfigureServices和Configure方法,这两个方法是从ConfigureServices开始进行服务配置,包括MVC配置、CORS配置,Swagger的一些配置以及最关键的ABP的配置,这里仅仅列出最为关键的过程,然后对着这些代码来一步步进行分析。
- return services.AddAbp<ServerWebHostModule>(
- // Configure Log4Net logging
- options => options.IocManager.IocContainer.AddFacility<LoggingFacility>(
- f => f.UseAbpLog4Net().WithConfig("log4net.config")
- )
- );
这里面最为关键的就是执行AddAbp方法了,整个ABP框架的执行也将从这里拉开序幕,我们来看看ABP项目的源码
- public static class AbpServiceCollectionExtensions
- {
- /// <summary>
- /// Integrates ABP to AspNet Core.
- /// </summary>
- /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
- /// <param name="services">Services.</param>
- /// <param name="optionsAction">An action to get/modify options</param>
- public static IServiceProvider AddAbp<TStartupModule>(this IServiceCollection services, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
- where TStartupModule : AbpModule
- {
- var abpBootstrapper = AddAbpBootstrapper<TStartupModule>(services, optionsAction);
- ConfigureAspNetCore(services, abpBootstrapper.IocManager);
- return WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services);
- }
- private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
- {
- //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
- services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
- services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
- //Use DI to create controllers
- services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
- //Use DI to create view components
- services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
- //Change anti forgery filters (to work proper with non-browser clients)
- services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
- services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());
- //Add feature providers
- var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
- partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));
- //Configure JSON serializer
- services.Configure<MvcJsonOptions>(jsonOptions =>
- {
- jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
- {
- NamingStrategy = new CamelCaseNamingStrategy()
- };
- });
- //Configure MVC
- services.Configure<MvcOptions>(mvcOptions =>
- {
- mvcOptions.AddAbp(services);
- });
- //Configure Razor
- services.Insert(0,
- ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
- new ConfigureOptions<RazorViewEngineOptions>(
- (options) =>
- {
- options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
- }
- )
- )
- );
- }
- private static AbpBootstrapper AddAbpBootstrapper<TStartupModule>(IServiceCollection services, Action<AbpBootstrapperOptions> optionsAction)
- where TStartupModule : AbpModule
- {
- var abpBootstrapper = AbpBootstrapper.Create<TStartupModule>(optionsAction);
- services.AddSingleton(abpBootstrapper);
- return abpBootstrapper;
- }
- }
整个ABP项目在AbpServiceCollectionExtensions这个类里面定义了一个AddABP的方法,就像当前方法的注释写的那样,让ABP项目与Asp.Net Core结合,在这个方法中首先就是创建唯一的AbpBootstrapper的实例,在这里创建的方式采用的是静态方法Create方法,下面通过源代码来分析一下这个方法。
- /// <summary>
- /// Creates a new <see cref="AbpBootstrapper"/> instance.
- /// </summary>
- /// <typeparam name="TStartupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</typeparam>
- /// <param name="optionsAction">An action to set options</param>
- public static AbpBootstrapper Create<TStartupModule>([CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
- where TStartupModule : AbpModule
- {
- return new AbpBootstrapper(typeof(TStartupModule), optionsAction);
- }
这个方法是一个泛型方法,用于创建一个唯一的AbpBootstrapper的实例,这里的泛型参数是TStartupModule,这个是整个项目的启动的Module,一般是XXXWebHostModule,后面的参数是一个参数类型为AbpBootstrapperOptions的Action类型委托,这个类型是一个可为空类型。
接下来我们再看看在私有的AbpBootstrapper构造函数中做了哪些事情,然后来一步步分析,首先来看看源代码。
- /// <summary>
- /// Creates a new <see cref="AbpBootstrapper"/> instance.
- /// </summary>
- /// <param name="startupModule">Startup module of the application which depends on other used modules. Should be derived from <see cref="AbpModule"/>.</param>
- /// <param name="optionsAction">An action to set options</param>
- private AbpBootstrapper([NotNull] Type startupModule, [CanBeNull] Action<AbpBootstrapperOptions> optionsAction = null)
- {
- Check.NotNull(startupModule, nameof(startupModule));
- var options = new AbpBootstrapperOptions();
- optionsAction?.Invoke(options);
- if (!typeof(AbpModule).GetTypeInfo().IsAssignableFrom(startupModule))
- {
- throw new ArgumentException($"{nameof(startupModule)} should be derived from {nameof(AbpModule)}.");
- }
- StartupModule = startupModule;
- IocManager = options.IocManager;
- PlugInSources = options.PlugInSources;
- _logger = NullLogger.Instance;
- if (!options.DisableAllInterceptors)
- {
- AddInterceptorRegistrars();
- }
- }
首先Check.NotNull是一个静态方法,用于判断整个ABP项目中启动Module是否为Null,如果为Null则直接抛出异常,然后第一步就是创建AbpBootstrapperOptions,在这个options里面定义了整个ABP项目中唯一的依赖注入容器IocManager ,这个容器是通过 IocManager = Abp.Dependency.IocManager.Instance,来完成的,这里我们就来简单看一下我们用的唯一的一个依赖注入容器。
- /// <summary>
- /// Creates a new <see cref="IocManager"/> object.
- /// Normally, you don't directly instantiate an <see cref="IocManager"/>.
- /// This may be useful for test purposes.
- /// </summary>
- public IocManager()
- {
- IocContainer = new WindsorContainer();
- _conventionalRegistrars = new List<IConventionalDependencyRegistrar>();
- //Register self!
- IocContainer.Register(
- Component.For<IocManager, IIocManager, IIocRegistrar, IIocResolver>().UsingFactoryMethod(() => this)
- );
- }
在我们的项目中,我们使用的是WindsorContainer,这个老牌的依赖注入容器作为全局的唯一的一个依赖注入容器,关于Castel Windsor这个著名的开源的依赖注入容器我们可以去它的官网去了解其详细信息,请点击这里访问Castle Project项目。
在使用这个依赖注入容器之前首先要将this也就是自己作为第一个实例注入到WindsorContainer容器中去,关于这个容器还有很多的内容,这个需要我们查看源码查看具体的实现,这个IocContainer类中还有很多的关于注册外部的实例到容器的方法,这个在后续的内容中会逐步去分析。
另外在AbpBootstrapperOptions这个类的构造函数中除了创建整个ABP项目中唯一的依赖注入容器IocManager以外,还定义了一个PlugInSources的公共属性,这个主要是为构建插件化、模块化项目提供插件模块的一个程序集集合,关于这个部分这里来看一下有哪些内容?
- public class PlugInSourceList : List<IPlugInSource>
- {
- public List<Assembly> GetAllAssemblies()
- {
- return this
- .SelectMany(pluginSource => pluginSource.GetAssemblies())
- .Distinct()
- .ToList();
- }
- public List<Type> GetAllModules()
- {
- return this
- .SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies())
- .Distinct()
- .ToList();
- }
- }
这个主要是一个为了加载外部的一些继承自AbpModule的一些程序集,包括一些外部文件夹里面的一些可扩展的程序集,我们来看一下ABP中为我们实现了哪些类型的扩展。1 FolderPlugInSource、2 PlugInTypeListSource、3 AssemblyFileListPlugInSource。
在分析完AbpBootstrapper类中AbpBootstrapperOptions的构建后,我们接着来分析AbpBootstrapper构造函数中其它的逻辑,在后面首先判断传入的泛型参数TStartupModule是否是继承自ABP项目中的基类AbpModule,否则的话就会抛出参数的异常。
后面的一个重点内容就是就是如果没有默认关掉所有的ABP拦截器的话,就会初始化ABP中所有的拦截器,这个是一个很大的内容,在后面我会花一篇文章来专门介绍ABP中的各种拦截器。
- private void AddInterceptorRegistrars()
- {
- ValidationInterceptorRegistrar.Initialize(IocManager);
- AuditingInterceptorRegistrar.Initialize(IocManager);
- EntityHistoryInterceptorRegistrar.Initialize(IocManager);
- UnitOfWorkRegistrar.Initialize(IocManager);
- AuthorizationInterceptorRegistrar.Initialize(IocManager);
- }
这里暂时先不去分析这些内容,只是让对这个框架先有一个整体上的把握。在完成所有的AbpBootstrapper类的初始化,后面就是执行ConfigureAspNetCore这个方法了,这个方法主要是用于配置一些常用的服务,下面我们通过具体的代码来一步步去分析。
- private static void ConfigureAspNetCore(IServiceCollection services, IIocResolver iocResolver)
- {
- //See https://github.com/aspnet/Mvc/issues/3936 to know why we added these services.
- services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
- services.TryAddSingleton<IActionContextAccessor, ActionContextAccessor>();
- //Use DI to create controllers
- services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
- //Use DI to create view components
- services.Replace(ServiceDescriptor.Singleton<IViewComponentActivator, ServiceBasedViewComponentActivator>());
- //Change anti forgery filters (to work proper with non-browser clients)
- services.Replace(ServiceDescriptor.Transient<AutoValidateAntiforgeryTokenAuthorizationFilter, AbpAutoValidateAntiforgeryTokenAuthorizationFilter>());
- services.Replace(ServiceDescriptor.Transient<ValidateAntiforgeryTokenAuthorizationFilter, AbpValidateAntiforgeryTokenAuthorizationFilter>());
- //Add feature providers
- var partManager = services.GetSingletonServiceOrNull<ApplicationPartManager>();
- partManager?.FeatureProviders.Add(new AbpAppServiceControllerFeatureProvider(iocResolver));
- //Configure JSON serializer
- services.Configure<MvcJsonOptions>(jsonOptions =>
- {
- jsonOptions.SerializerSettings.ContractResolver = new AbpContractResolver
- {
- NamingStrategy = new CamelCaseNamingStrategy()
- };
- });
- //Configure MVC
- services.Configure<MvcOptions>(mvcOptions =>
- {
- mvcOptions.AddAbp(services);
- });
- //Configure Razor
- services.Insert(0,
- ServiceDescriptor.Singleton<IConfigureOptions<RazorViewEngineOptions>>(
- new ConfigureOptions<RazorViewEngineOptions>(
- (options) =>
- {
- options.FileProviders.Add(new EmbeddedResourceViewFileProvider(iocResolver));
- }
- )
- )
- );
- }
这里面主要是配置一些核心的Asp.Net Core服务,比如用ServiceBasedControllerActivator来替换默认的DefaultControllerActivator ,
使用ServiceBasedViewComponentActivator来替换默认的DefaultViewComponentActivator,这里面我们重点来关注一下services.Configure<MvcOptions>这个方法,我们来看一下最终在里面做了些什么。
- internal static class AbpMvcOptionsExtensions
- {
- public static void AddAbp(this MvcOptions options, IServiceCollection services)
- {
- AddConventions(options, services);
- AddFilters(options);
- AddModelBinders(options);
- }
- private static void AddConventions(MvcOptions options, IServiceCollection services)
- {
- options.Conventions.Add(new AbpAppServiceConvention(services));
- }
- private static void AddFilters(MvcOptions options)
- {
- options.Filters.AddService(typeof(AbpAuthorizationFilter));
- options.Filters.AddService(typeof(AbpAuditActionFilter));
- options.Filters.AddService(typeof(AbpValidationActionFilter));
- options.Filters.AddService(typeof(AbpUowActionFilter));
- options.Filters.AddService(typeof(AbpExceptionFilter));
- options.Filters.AddService(typeof(AbpResultFilter));
- }
- private static void AddModelBinders(MvcOptions options)
- {
- options.ModelBinderProviders.Insert(0, new AbpDateTimeModelBinderProvider());
- }
- }
在这个方法中,主要实现了三个部分:AddConventions、AddFilters、AddModelBinders这三个方法,第一个就是添加默认的协定、第二个就是我们的Asp.Net Core服务中添加各种过滤器,这些过滤器会添加到Asp.Net Core请求过程中,这些Filter的主要作用是在Action执行前和执行后进行一些加工处理,关于Asp.Net Core中的Filter请参考下面的这篇文章,第三个部分就是为默认的MvcOptions中添加默认的ModelBinder。
在AddAbp方法最后执行的是 WindsorRegistrationHelper.CreateServiceProvider(abpBootstrapper.IocManager.IocContainer, services)这个方这个最后的方法就是替换掉了 Asp.Net Core 默认的 Ioc 容器,最终使用的是 CastleWindsor 的IocContainer。通过这上面的这些过程完成了整个Asp.Net Core 的服务配置过程,在后面的一片文章中我们将重点分析在StarpUp类中Configure方法中调用UseAbp()方法了,这个方法将会去一步步分析整个Module是如何查找,如何加载如何运行的。
最后,点击这里返回整个ABP系列的主目录。
ABP中的模块初始化过程(一)的更多相关文章
- 通俗理解ABP中的模块Module
网上有不少文章说ABP的模块,有的直接翻译自官网介绍,有的分析Modlue的源代码,有的写一通代码,没什么注释,很少有能通俗说清的.那么,有两个问题:1.ABP中的模块到底是什么?2.搞这个东西是干嘛 ...
- ABP中模块初始化过程(二)
在上一篇介绍在StartUp类中的ConfigureService()中的AddAbp方法后我们再来重点说一说在Configure()方法中的UserAbp()方法,还是和前面的一样我们来通过代码来进 ...
- java中对象产生初始化过程
以前面试的时候,很多公司的笔试题中有关new一个对象有关一系列初始化的过程的选择题目.请看下面的题目. class Parent { static { System.out.println(" ...
- java中对象的初始化过程
class Parent{ int num = 8;// ->3 Parent(){ //super(); // ->2 //显示初始化 // ->3 //构造代码段 // -> ...
- Framebuffer 驱动学习总结(二)---- Framebuffer模块初始化
---恢复内容开始--- Framebuffer模块初始化过程:--driver\video\fbmem.c 1. 初始化Framebuffer: FrameBuffer驱动是以模块的形式注册到系统 ...
- linux PCI设备初始化过程
linux PCI设备初始化过程 start_kernel->rest_init 这个函数会启动一个核心线程0, 核心线程然后调用init -> do_basic_setup. 然后我们开 ...
- ES6中的模块
前面的话 JS用"共享一切"的方法加载代码,这是该语言中最容出错且容易令人感到困惑的地方.其他语言使用诸如包这样的概念来定义代码作用域,但在ES6以前,在应用程序的每一个JS中定义 ...
- ABP架构学习系列二:ABP中配置的注册和初始化
一.手工搭建平台 1.创建项目 创建MVC5项目,手动引入Abp.Abp.Web.Abp.Web.Mvc.Abp.Web.Api 使用nuget添加Newtonsoft.Json.Castle.Cor ...
- Java中的成员初始化顺序和内存分配过程
Java中的成员初始化顺序和内存分配过程 原帖是这样描述的: http://java.dzone.com/articles/java-object-initialization?utm_source= ...
随机推荐
- Fragment 生命周期的详情
Fragment每个生命周期方法的意义.作用(注意红色的不是生命周期方法):setUserVisibleHint():设置Fragment可见或者不可见时会调用此方法.在该方法里面可以通过调用getU ...
- Xshell工具使用--连接VMware虚拟机
假设有这样的场景,开发者用的是Windows系统,且系统的存储资源和内存有限,在运行VMware虚拟机中做一些测试时,通常会碍于电脑的VMWare客户端图形界面的响应速度太慢.而在Xshell中对虚拟 ...
- nodejs 使用 js 模块
nodejs 使用 js 模块 Intro 最近需要用 nodejs 做一个爬虫,Google 有一个 Puppeteer 的项目,可以用它来做爬虫,有关 Puppeteer 的介绍网上也有很多,在这 ...
- (二)版本控制管理器之CVS(上)
在前一篇<(一)版本控制管理器之发展史>的介绍中,有提到古典时期的CVS,那什么是CVS?CVS特点是什么?怎么个用法?等一系列的问题,虽然这个版本控制管理器早已过时,但大家了解下也不妨, ...
- UE3客户端加入DS过程
拉起DS进程 客户端将比赛地图及相关参数发送给ZoneSvr请求开赛,收到消息后,ZoneSvr会分配一个ip和端口号,并与客户端发过来的地图及其他参数,来构建一个命令行来拉起一个DS进程, DS启动 ...
- Linux 中磁盘容量配额
linux的设计之处就是为了多用户同时执行不同的任务,但是硬件资源是有限的,不能让一个用户无限制的上传文件,如果不加以限制,那么磁盘最终将会被充满,对此我们应该使用uquota来加以限制. 1.quo ...
- sql取指定时间段内的所有月份
declare @begin datetime,@end datetime set @begin='2017-01-01' set @end='2019-03-04' declare @months ...
- 关于clone(java.lang.Object)重写
1. 需要实现接口java.lang.Cloneable 2. 重写java.lang.Object的clone 3. clone访问权限扩大为public 4. 不实现(java.lang.Clon ...
- Django REST framework基础:视图和路由
DRF中的Request 在Django REST Framework中内置的Request类扩展了Django中的Request类,实现了很多方便的功能--如请求数据解析和认证等. 比如,区别于Dj ...
- 读写锁ReentrantReadWriteLock的使用
package com.thread.test.Lock; import java.util.Random; import java.util.concurrent.locks.Lock; impor ...