Skip to content

Commit

Permalink
feat: 优化AssemblyHelper实现.不再显式指定排除的程序集.同时添加多线程加载. (#571)
Browse files Browse the repository at this point in the history
  • Loading branch information
joesdu authored Oct 8, 2024
2 parents a6b6b24 + 2dda75f commit df41ecf
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 23 deletions.
2 changes: 0 additions & 2 deletions sample/WebApi.Test.Unit/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using EasilyNET.Core.Misc;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.OpenTelemetry;
Expand All @@ -10,7 +9,6 @@
#endif

Console.Title = $"❤️ {Constant.InstanceName}";
AssemblyHelper.AddExcludeLibs("Npgsql.");
var builder = WebApplication.CreateBuilder(args);

// 配置Kestrel支持HTTP1,2,3
Expand Down
98 changes: 77 additions & 21 deletions src/EasilyNET.Core/Misc/AssemblyHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
Expand All @@ -10,52 +13,43 @@ namespace EasilyNET.Core.Misc;
/// <summary>
/// 程序集帮助类
/// </summary>
// ReSharper disable once UnusedType.Global
public static class AssemblyHelper
{
private static readonly HashSet<string> Filters = ["dotnet-", "Microsoft.", "mscorlib", "netstandard", "System", "Windows", "PresentationUI"];

/// <summary>
/// 需要排除的项目
/// </summary>
private static readonly HashSet<string> FilterLibs = [];
private static readonly ConcurrentDictionary<string, Assembly?> AssemblyCache = [];

/// <summary>
/// 构造函数
/// </summary>
static AssemblyHelper()
{
AllAssemblies = DependencyContext.Default?.GetDefaultAssemblyNames().Where(c => c.Name is not null && !Filters.Any(c.Name.StartsWith) && !FilterLibs.Any(c.Name.StartsWith)).Select(Assembly.Load);
AllTypes = AllAssemblies.AsNotNull().SelectMany(c => c.GetTypes());
AllAssemblies = LoadAssemblies();
AllTypes = AllAssemblies.SelectMany(c => c.GetTypes());
}

/// <summary>
/// 获取所有扫描到符合条件的程序集
/// </summary>
public static IEnumerable<Assembly>? AllAssemblies { get; }
public static IEnumerable<Assembly> AllAssemblies { get; }

/// <summary>
/// 获取所有扫描到符合条件的程序集中的类型
/// </summary>
public static IEnumerable<Type>? AllTypes { get; }

/// <summary>
/// 添加排除项目,该排除项目可能会影响AutoDependenceInjection自动注入,请使用的时候自行测试.
/// </summary>
/// <param name="names"></param>
public static void AddExcludeLibs(params string[] names) => FilterLibs.AddRangeIfNotContains(names);
public static IEnumerable<Type> AllTypes { get; }

/// <summary>
/// 根据程序集名字得到程序集
/// </summary>
/// <param name="assemblyNames"></param>
/// <returns></returns>
public static IEnumerable<Assembly> GetAssembliesByName(params string[] assemblyNames) => assemblyNames.Select(o => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, $"{o}.dll")));
public static IEnumerable<Assembly> GetAssembliesByName(params string[] assemblyNames)
{
return assemblyNames.Select(name => AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(AppContext.BaseDirectory, $"{name}.dll")));
}

/// <summary>
/// 查找指定条件的类型
/// </summary>
public static IEnumerable<Type> FindTypes(Func<Type, bool> predicate) => AllTypes.AsNotNull().Where(predicate);
public static IEnumerable<Type> FindTypes(Func<Type, bool> predicate) => AllTypes.Where(predicate);

/// <summary>
/// 查找所有指定特性标记的类型
Expand All @@ -69,10 +63,72 @@ static AssemblyHelper()
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> FindTypesByAttribute(Type type) => AllTypes.AsNotNull().Where(a => a.IsDefined(type, true)).Distinct();
public static IEnumerable<Type> FindTypesByAttribute(Type type) => AllTypes.Where(a => a.IsDefined(type, true)).Distinct();

/// <summary>
/// 查找指定条件的类型
/// </summary>
public static IEnumerable<Assembly> FindAllItems(Func<Assembly, bool> predicate) => AllAssemblies.AsNotNull().Where(predicate);
public static IEnumerable<Assembly> FindAllItems(Func<Assembly, bool> predicate) => AllAssemblies.Where(predicate);

private static IEnumerable<Assembly> LoadAssemblies()
{
var assemblyNames = DependencyContext.Default?.GetDefaultAssemblyNames().Where(c => c.Name is not null).ToFrozenSet();
if (assemblyNames is null) yield break;
var batchSize = (int)Math.Ceiling((double)assemblyNames.Count / Environment.ProcessorCount); // 计算每组的大小
var loadedAssemblies = new ConcurrentBag<Assembly>();
if (assemblyNames.Count >= Environment.ProcessorCount)
{
var batches = assemblyNames.Select((name, index) => new { name, index })
.GroupBy(x => x.index / batchSize)
.Select(g => g.Select(x => x.name));
Parallel.ForEach(batches, batch =>
{
foreach (var assemblyName in batch)
{
LoadAssembly(assemblyName, loadedAssemblies);
}
});
}
else
{
foreach (var assemblyName in assemblyNames)
{
LoadAssembly(assemblyName, loadedAssemblies);
}
}
foreach (var assembly in loadedAssemblies)
{
yield return assembly;
}
}

private static void LoadAssembly(AssemblyName assemblyName, ConcurrentBag<Assembly> loadedAssemblies)
{
if (AssemblyCache.TryGetValue(assemblyName.FullName, out var cachedAssembly))
{
if (cachedAssembly is not null)
{
loadedAssemblies.Add(cachedAssembly);
}
return;
}
try
{
var assembly = Assembly.Load(assemblyName);
AssemblyCache[assemblyName.FullName] = assembly;
// 预检查程序集中的类型,确保可以加载
_ = assembly.GetTypes();
loadedAssemblies.Add(assembly);
}
catch (ReflectionTypeLoadException)
{
AssemblyCache[assemblyName.FullName] = null;
Debug.WriteLine($"无法加载程序集中的某些类型: {assemblyName.Name}, 将跳过.");
}
catch (Exception ex)
{
AssemblyCache[assemblyName.FullName] = null;
Debug.WriteLine($"加载程序集失败: {assemblyName.Name}, 错误: {ex.Message}");
}
}
}

0 comments on commit df41ecf

Please sign in to comment.