From 63b46caeea0e32238ee09e2e7a7e86c8bd58922a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BD=AD=E4=BC=9F?= Date: Mon, 11 Oct 2021 18:33:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=81=E8=AE=B8=E5=9C=A8Scoped=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E9=87=8C=E6=89=A7=E8=A1=8CJob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ASPNetCoreExecutor.csproj | 4 +- samples/ASPNetCoreExecutor/DemoJobHandler.cs | 8 +- samples/ASPNetCoreExecutor/Startup.cs | 8 +- .../DefaultJobHandlerFactory.cs | 51 ++++++------- .../Extensions/ServiceCollectionExtensions.cs | 76 ++++++++++++++----- src/DotXxlJob.Core/IJobHandlerFactory.cs | 5 +- src/DotXxlJob.Core/JobHandlerCache.cs | 62 +++++++++++++++ .../TaskExecutors/BeanTaskExecutor.cs | 34 ++++++--- .../BeanTaskExecutorTest.cs | 59 ++++++++++++++ .../DefaultJobHandlerFactory.cs | 42 ++++++++++ .../DotXxlJob.Core.Tests.csproj | 27 ++++--- .../JobHandlerCacheTest.cs | 31 ++++++++ tests/DotXxlJob.Core.Tests/UnitTest1.cs | 13 ---- 13 files changed, 326 insertions(+), 94 deletions(-) create mode 100644 src/DotXxlJob.Core/JobHandlerCache.cs create mode 100644 tests/DotXxlJob.Core.Tests/BeanTaskExecutorTest.cs create mode 100644 tests/DotXxlJob.Core.Tests/DefaultJobHandlerFactory.cs create mode 100644 tests/DotXxlJob.Core.Tests/JobHandlerCacheTest.cs delete mode 100644 tests/DotXxlJob.Core.Tests/UnitTest1.cs diff --git a/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj b/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj index 6f62e05..7fc76f4 100644 --- a/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj +++ b/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj @@ -1,13 +1,13 @@  - netcoreapp3.1 + netcoreapp3.1 2.2.0 - + diff --git a/samples/ASPNetCoreExecutor/DemoJobHandler.cs b/samples/ASPNetCoreExecutor/DemoJobHandler.cs index 8534842..149b97a 100644 --- a/samples/ASPNetCoreExecutor/DemoJobHandler.cs +++ b/samples/ASPNetCoreExecutor/DemoJobHandler.cs @@ -8,12 +8,12 @@ namespace ASPNetCoreExecutor /// 示例Job,只是写个日志 /// [JobHandler("demoJobHandler")] - public class DemoJobHandler:AbstractJobHandler + public class DemoJobHandler : AbstractJobHandler { public override async Task Execute(JobExecuteContext context) - { - context.JobLogger.Log("receive demo job handler,parameter:{0}",context.JobParameter); - context.JobLogger.Log("开始休眠10秒"); + { + context.JobLogger.Log("receive demo job handler,parameter:{0}", context.JobParameter); + context.JobLogger.Log("开始休眠10秒"); await Task.Delay(10 * 1000); context.JobLogger.Log("休眠10秒结束"); return ReturnT.SUCCESS; diff --git a/samples/ASPNetCoreExecutor/Startup.cs b/samples/ASPNetCoreExecutor/Startup.cs index 66735ee..5b5b4ec 100644 --- a/samples/ASPNetCoreExecutor/Startup.cs +++ b/samples/ASPNetCoreExecutor/Startup.cs @@ -16,17 +16,17 @@ namespace ASPNetCoreExecutor } private IConfiguration Configuration { get; } - + // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - + services.AddXxlJobExecutor(Configuration); services.AddDefaultXxlJobHandlers();// add httpHandler; - services.AddSingleton(); // 添加自定义的jobHandler - + services.AddJobHandler(); // 添加自定义的jobHandler + services.AddAutoRegistry(); // 自动注册 diff --git a/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs b/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs index 23b4363..c8ee3d7 100644 --- a/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs +++ b/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs @@ -1,49 +1,46 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; namespace DotXxlJob.Core { - public class DefaultJobHandlerFactory:IJobHandlerFactory + public class DefaultJobHandlerFactory : IJobHandlerFactory { - private readonly IServiceProvider _provider; - private readonly Dictionary handlersCache = new Dictionary(); - public DefaultJobHandlerFactory(IServiceProvider provider) + private readonly JobHandlerCache _handlerCache; + + public DefaultJobHandlerFactory(IServiceProvider provider, JobHandlerCache handlerCache = null) { - this._provider = provider; - Initialize(); + _handlerCache = handlerCache ?? new JobHandlerCache(); + + Initialize(provider); } - private void Initialize() + private void Initialize(IServiceProvider provider) { - var list = this._provider.GetServices(); - if (list == null || !list.Any()) + foreach (var handler in provider.GetServices()) { - throw new TypeLoadException("IJobHandlers are not found in IServiceCollection"); + _handlerCache.AddJobHandler(handler); } - foreach (var handler in list) + if (_handlerCache.HandlersCache.Count < 1) { - var jobHandlerAttr = handler.GetType().GetCustomAttribute(); - var handlerName = jobHandlerAttr == null ? handler.GetType().Name : jobHandlerAttr.Name; - if (handlersCache.ContainsKey(handlerName)) - { - throw new Exception($"same IJobHandler' name: [{handlerName}]"); - } - handlersCache.Add(handlerName,handler); + throw new TypeLoadException("IJobHandlers are not found in IServiceCollection"); } - } - public IJobHandler GetJobHandler(string handlerName) + public IJobHandler GetJobHandler(IServiceScopeFactory scopeFactory, string handlerName, out IServiceScope serviceScope) { - if (handlersCache.ContainsKey(handlerName)) - { - return handlersCache[handlerName]; - } - return null; + serviceScope = null; + + var jobHandler = _handlerCache.Get(handlerName); + + if (jobHandler == null) return null; + + if (jobHandler.JobHandler != null) return jobHandler.JobHandler; + + serviceScope = scopeFactory.CreateScope(); + + return (IJobHandler)ActivatorUtilities.CreateInstance(serviceScope.ServiceProvider, jobHandler.JobHandlerType, jobHandler.JobHandlerConstructorParameters); } } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs b/src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs index cff4966..2f90816 100644 --- a/src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/DotXxlJob.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using DotXxlJob.Core.Config; using DotXxlJob.Core.DefaultHandlers; using DotXxlJob.Core.Queue; @@ -11,43 +12,37 @@ namespace DotXxlJob.Core { public static class ServiceCollectionExtensions { - public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,IConfiguration configuration) - { - services.AddLogging(); - services.AddOptions(); - services.Configure(configuration.GetSection("xxlJob")) - .AddXxlJobExecutorServiceDependency(); - - return services; - } - public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services,Action configAction) + public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services, IConfiguration configuration) => + services.AddXxlJobExecutor(configuration.GetSection("xxlJob").Bind); + + public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services, Action configAction) { services.AddLogging(); services.AddOptions(); services.Configure(configAction).AddXxlJobExecutorServiceDependency(); return services; } - + public static IServiceCollection AddDefaultXxlJobHandlers(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); return services; } + public static IServiceCollection AddAutoRegistry(this IServiceCollection services) { - services.AddSingleton() - .AddSingleton(); + services.AddSingleton() + .AddSingleton(); return services; } - + private static IServiceCollection AddXxlJobExecutorServiceDependency(this IServiceCollection services) - { - + { //可在外部提前注册对应实现,并替换默认实现 services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); - + services.AddHttpClient("DotXxlJobClient"); services.AddSingleton(); services.AddSingleton(); @@ -55,9 +50,48 @@ namespace DotXxlJob.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + services.AddSingleton(new JobHandlerCache()); + + return services; + } + + /// 允许创建Scoped实例 + /// + /// + /// 用于创建实例的额外参数,比如字符串 + /// + public static IServiceCollection AddJobHandler(this IServiceCollection services, + params object[] constructorParameters) where TJob : IJobHandler + { + services.GetJobHandlerCache().AddJobHandler(constructorParameters); + + return services; + } + + /// 允许创建Scoped实例 + /// + /// + /// + /// 用于创建实例的额外参数,比如字符串 + /// + public static IServiceCollection AddJobHandler(this IServiceCollection services, + string handlerName, params object[] constructorParameters) where TJob : IJobHandler + { + services.GetJobHandlerCache().AddJobHandler(handlerName, constructorParameters); + return services; } - + + private static JobHandlerCache GetJobHandlerCache(this IServiceCollection services) + { + var sd = services.FirstOrDefault(x => x.ImplementationInstance is JobHandlerCache); + if (sd != null) return (JobHandlerCache)sd.ImplementationInstance; + + var cache = new JobHandlerCache(); + + services.AddSingleton(cache); + + return cache; + } } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/IJobHandlerFactory.cs b/src/DotXxlJob.Core/IJobHandlerFactory.cs index d09be4c..dfd947e 100644 --- a/src/DotXxlJob.Core/IJobHandlerFactory.cs +++ b/src/DotXxlJob.Core/IJobHandlerFactory.cs @@ -1,7 +1,10 @@ +using System; +using Microsoft.Extensions.DependencyInjection; + namespace DotXxlJob.Core { public interface IJobHandlerFactory { - IJobHandler GetJobHandler(string handlerName); + IJobHandler GetJobHandler(IServiceScopeFactory scopeFactory, string handlerName, out IServiceScope serviceScope); } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/JobHandlerCache.cs b/src/DotXxlJob.Core/JobHandlerCache.cs new file mode 100644 index 0000000..3af60d1 --- /dev/null +++ b/src/DotXxlJob.Core/JobHandlerCache.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using DotXxlJob.Core.DefaultHandlers; + +namespace DotXxlJob.Core +{ + public class JobHandlerCache + { + internal Dictionary HandlersCache { get; } = new Dictionary(); + + public void AddJobHandler(params object[] constructorParameters) + where TJob : IJobHandler => + AddJobHandler(typeof(TJob).GetCustomAttribute()?.Name ?? + typeof(TJob).Name, constructorParameters); + + public void AddJobHandler(string handlerName, params object[] constructorParameters) + where TJob : IJobHandler => + AddJobHandler(handlerName, new JobHandlerItem { + JobHandlerType = typeof(TJob), + JobHandlerConstructorParameters = constructorParameters, + }); + + public void AddJobHandler(IJobHandler jobHandler) + { + var jobHandlerType = jobHandler.GetType(); + + var handlerName = jobHandlerType.GetCustomAttribute()?.Name ?? jobHandlerType.Name; + + AddJobHandler(handlerName, jobHandler); + } + + public void AddJobHandler(string handlerName, IJobHandler jobHandler) + { + + AddJobHandler(handlerName, new JobHandlerItem { JobHandler = jobHandler }); + } + + private void AddJobHandler(string handlerName, JobHandlerItem jobHandler) + { + if (HandlersCache.ContainsKey(handlerName)) + { + throw new ArgumentException($"Same IJobHandler' name: [{handlerName}]", nameof(handlerName)); + } + + HandlersCache.Add(handlerName, jobHandler); + } + + public JobHandlerItem Get(string handlerName) => + HandlersCache.TryGetValue(handlerName, out var item) ? item : null; + + public class JobHandlerItem + { + public IJobHandler JobHandler { get; set; } + + public Type JobHandlerType { get; set; } + + public object[] JobHandlerConstructorParameters { get; set; } + } + } +} diff --git a/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs b/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs index 3c85c06..e9c71b1 100644 --- a/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs +++ b/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs @@ -1,31 +1,45 @@ using System.Threading.Tasks; using DotXxlJob.Core.Model; +using Microsoft.Extensions.DependencyInjection; namespace DotXxlJob.Core.TaskExecutors { - /// - /// 实现 IJobHandler的执行器 - /// - public class BeanTaskExecutor:ITaskExecutor + /// + /// 实现 IJobHandler的执行器 + /// + public class BeanTaskExecutor : ITaskExecutor { private readonly IJobHandlerFactory _handlerFactory; private readonly IJobLogger _jobLogger; + private readonly IServiceScopeFactory _scopeFactory; - public BeanTaskExecutor(IJobHandlerFactory handlerFactory,IJobLogger jobLogger) + public BeanTaskExecutor(IJobHandlerFactory handlerFactory, IJobLogger jobLogger, IServiceScopeFactory scopeFactory) { this._handlerFactory = handlerFactory; this._jobLogger = jobLogger; + this._scopeFactory = scopeFactory; } - + public string GlueType { get; } = Constants.GlueType.BEAN; - - public Task Execute(TriggerParam triggerParam) + + public async Task Execute(TriggerParam triggerParam) { - var handler = _handlerFactory.GetJobHandler(triggerParam.ExecutorHandler); + var handler = _handlerFactory.GetJobHandler(_scopeFactory, triggerParam.ExecutorHandler, out var scope); + + if (scope == null) return await Execute(handler, triggerParam); + + using (scope) + using (handler) + { + return await Execute(handler, triggerParam); + } + } + private Task Execute(IJobHandler handler, TriggerParam triggerParam) + { if (handler == null) { - return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); + return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); } var context = new JobExecuteContext(this._jobLogger, triggerParam.ExecutorParams); return handler.Execute(context); diff --git a/tests/DotXxlJob.Core.Tests/BeanTaskExecutorTest.cs b/tests/DotXxlJob.Core.Tests/BeanTaskExecutorTest.cs new file mode 100644 index 0000000..85502ab --- /dev/null +++ b/tests/DotXxlJob.Core.Tests/BeanTaskExecutorTest.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotXxlJob.Core.DefaultHandlers; +using DotXxlJob.Core.Model; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace DotXxlJob.Core.Tests +{ + public class BeanTaskExecutorTest + { + [Fact] + public async Task Repeated_Job_Handler() + { + var services = new ServiceCollection(); + + services.AddScoped(); + + services.AddXxlJobExecutor(options => options.AdminAddresses = "http://localhost"); + + var list = new List(); + + services.AddJobHandler("test", list); + + using (var provider = services.BuildServiceProvider(true)) + { + await provider.GetRequiredService() + .Execute(new TriggerParam { + ExecutorHandler = "test" + }); + } + + Assert.Single(list); + } + + private class TestJobHandler : IJobHandler + { + private readonly List _list; + + public TestJobHandler(List list, ScopedService _) => _list = list; + + public void Dispose() + { + } + + public Task Execute(JobExecuteContext context) + { + _list.Add(new object()); + + return Task.FromResult(ReturnT.SUCCESS); + } + } + + private class ScopedService + { + } + } +} diff --git a/tests/DotXxlJob.Core.Tests/DefaultJobHandlerFactory.cs b/tests/DotXxlJob.Core.Tests/DefaultJobHandlerFactory.cs new file mode 100644 index 0000000..c342e0f --- /dev/null +++ b/tests/DotXxlJob.Core.Tests/DefaultJobHandlerFactory.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using DotXxlJob.Core.Model; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace DotXxlJob.Core.Tests +{ + public class DefaultJobHandlerFactory + { + [Fact] + public async Task Repeated_Job_Handler() + { + var services = new ServiceCollection(); + + services.AddXxlJobExecutor(options => options.AdminAddresses = "http://localhost"); + + services.AddDefaultXxlJobHandlers(); + + services.AddJobHandler(); + + using (var provider = services.BuildServiceProvider()) + { + Assert.Throws(() => provider.GetRequiredService()); + } + } + + [JobHandler("simpleHttpJobHandler")] + private class TestJobHandler : IJobHandler + { + public void Dispose() + { + } + + public Task Execute(JobExecuteContext context) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj b/tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj index a8d75a1..57f5c70 100644 --- a/tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj +++ b/tests/DotXxlJob.Core.Tests/DotXxlJob.Core.Tests.csproj @@ -1,19 +1,22 @@  - - netcoreapp2.2 + + netcoreapp3.1 - false - + false + - - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + diff --git a/tests/DotXxlJob.Core.Tests/JobHandlerCacheTest.cs b/tests/DotXxlJob.Core.Tests/JobHandlerCacheTest.cs new file mode 100644 index 0000000..c1f5b18 --- /dev/null +++ b/tests/DotXxlJob.Core.Tests/JobHandlerCacheTest.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using DotXxlJob.Core.DefaultHandlers; +using DotXxlJob.Core.Model; +using Xunit; + +namespace DotXxlJob.Core.Tests +{ + public class JobHandlerCacheTest + { + [Fact] + public void Repeated_Job_Handler() + { + var cache = new JobHandlerCache(); + + cache.AddJobHandler(); + + Assert.Throws(() => cache.AddJobHandler("simpleHttpJobHandler", new TestJobHandler())); + } + + private class TestJobHandler : IJobHandler + { + public void Dispose() { } + + public Task Execute(JobExecuteContext context) + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/tests/DotXxlJob.Core.Tests/UnitTest1.cs b/tests/DotXxlJob.Core.Tests/UnitTest1.cs deleted file mode 100644 index dd1c350..0000000 --- a/tests/DotXxlJob.Core.Tests/UnitTest1.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Xunit; - -namespace DotXxlJob.Core.Tests -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - } - } -} \ No newline at end of file