Castle DynamicProxy实现LogAspect拦截失效求助
我在项目中用Castle DynamicProxy构建了拦截器结构,写了日志用的LogAspect,但它完全没触发。以下是相关代码,求排查原因或给出调整方案。
代码详情
基础拦截器基类
namespace ebordro.Interceptors { //所有拦截器的基类 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class MethodInterceptionBaseAttribute : Attribute, IInterceptor { public int Priority { get; set; } //控制执行顺序 public virtual void Intercept(IInvocation invocation) { } } }
方法拦截器实现
namespace ebordro.Interceptors { //方法拦截操作的基类 public class MethodInterception : MethodInterceptionBaseAttribute { protected virtual void OnBefore(IInvocation invocation) { } protected virtual void OnAfter(IInvocation invocation) { } protected virtual void OnException(IInvocation invocation, Exception e) { } protected virtual void OnSuccess(IInvocation invocation) { } public override void Intercept(IInvocation invocation) { var isSuccess = true; OnBefore(invocation); try { invocation.Proceed(); } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); } } }
拦截器选择器
namespace ebordro.Interceptors { //选择要应用的拦截器 public class AspectInterceptorSelector : IInterceptorSelector { public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) { var classAttributes = type.GetCustomAttributes<MethodInterceptionBaseAttribute>(true).ToList(); var methodAttributes = type.GetMethod(method.Name).GetCustomAttributes<MethodInterceptionBaseAttribute>(true); classAttributes.AddRange(methodAttributes); return classAttributes.OrderBy(x => x.Priority).ToArray(); } } }
日志拦截器实现
namespace ebordro.Interceptors.Aspects { public class LogAspect : MethodInterception { private readonly IActionLogService _actionLogService; private readonly IHttpContextAccessor _httpContextAccessor; public LogAspect() { var serviceProvider = ServiceToolForAspect.CreateScope().ServiceProvider; _actionLogService = serviceProvider.GetRequiredService<IActionLogService>(); _httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>(); } protected override void OnAfter(IInvocation invocation) { using (var scope = ServiceToolForAspect.serviceProvider.CreateScope()) { var actionLogService = scope.ServiceProvider.GetRequiredService<IActionLogService>(); using (var scope = ServiceToolForAspect.CreateScope()) { var actionLogService = scope.ServiceProvider.GetRequiredService<IActionLogService>(); var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>(); var httpContext = httpContextAccessor.HttpContext; string usermail = httpContext.User.Identity.Name; int userId = 0; int companyId = 0; if (httpContext.User.Identity.IsAuthenticated) { var userIdClaim = httpContext.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim != null) { userId = int.Parse(userIdClaim.Value); } var companyIdClaim = httpContext.User.FindFirst("CompanyId"); if (companyIdClaim != null) { companyId = int.Parse(companyIdClaim.Value); } } İşlem detayını oluştur var processDetail = CreateProcessDetail(invocation); Loglama işlemini gerçekleştir actionLogService.AddLogRecordAsync(userId, usermail, companyId, invocation.Method.Name.ToUpper(), processDetail) .ConfigureAwait(false) .GetAwaiter() .GetResult(); } var httpContext = _httpContextAccessor.HttpContext; var usermail = httpContext.User.Identity.Name; var userId = 0; var companyId = 0; var processDetail = CreateProcessDetail(invocation); if (httpContext.User.Identity.IsAuthenticated) { var userIdClaim = httpContext.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim != null) { userId = int.Parse(userIdClaim.Value); } var companyIdClaim = httpContext.User.FindFirst("CompanyId"); if (companyIdClaim != null) { companyId = int.Parse(companyIdClaim.Value); } } //_actionLogService.AddLogRecordAsync(userId,usermail,companyId,invocation.Method.Name.ToUpper(),processDetail).Wait(); _actionLogService.AddLogRecordAsync(userId, usermail, companyId, invocation.Method.Name.ToUpper(), processDetail).ConfigureAwait(false).GetAwaiter().GetResult(); } private string CreateProcessDetail(IInvocation invocation) { var methodName = invocation.Method.Name; var arguments = invocation.Arguments; string message = $"{methodName} metodu çağrıldı. Parametreler: "; for (int i = 0; i < arguments.Length; i++) { message += $"Parametre {i + 1}: {arguments[i]?.ToString() ?? \"null\"}; "; } return message; } } }
标记了拦截器的方法
[LogAspect] public async Task AddLogRecordAsync(int userId, string stringValue, int? intValue, string process, string processDetail) { ...codes... }
已尝试操作
- 基于Castle DynamicProxy搭建拦截器结构,定义日志用的LogAspect类;
- 将LogAspect指定为继承自MethodInterceptionBaseAttribute的特性;
- 通过AspectInterceptorSelector类收集类或方法上的特性,按优先级执行;
- 使用ServiceToolForAspect通过DI容器解决依赖。
预期效果
调用GetAllActionLogAsync方法时,LogAspect生效,执行OnBefore、OnAfter等拦截方法完成日志操作。
实际情况
LogAspect完全未运行,方法未经过拦截逻辑,日志代码无执行且无报错信息。
排查问题点及调整方案
1. 未正确注册代理生成器与拦截器选择器
Castle DynamicProxy需要显式配置代理生成器,并指定拦截器选择器。如果没有在DI容器中注册ProxyGenerator并绑定AspectInterceptorSelector,拦截器不会被应用。
调整方案:
在DI注册时添加以下配置:
services.AddSingleton<ProxyGenerator>(); services.AddSingleton<IInterceptorSelector, AspectInterceptorSelector>();
2. 未通过代理实例调用方法
如果直接实例化类(如new ActionLogService())而非通过代理获取实例,拦截器不会生效。必须通过ProxyGenerator创建代理对象,或者在DI中注册服务时使用代理包装。
调整方案:
在DI注册服务时,使用代理包装实现类:
services.AddScoped<IActionLogService>(sp => { var proxyGenerator = sp.GetRequiredService<ProxyGenerator>(); var target = sp.GetRequiredService<ActionLogService>(); var selector = sp.GetRequiredService<IInterceptorSelector>(); return proxyGenerator.CreateInterfaceProxyWithTarget<IActionLogService>(target, selector); });
3. LogAspect中的语法错误
查看LogAspect代码,存在两处问题:
- 注释未加英文注释符(
//),导致编译错误; CreateProcessDetail方法嵌套在OnAfter方法内部,属于语法错误,应移到方法外部。
调整方案:
修复LogAspect中的语法问题:
protected override void OnAfter(IInvocation invocation) { using (var scope = ServiceToolForAspect.serviceProvider.CreateScope()) { var actionLogService = scope.ServiceProvider.GetRequiredService<IActionLogService>(); using (var scope = ServiceToolForAspect.CreateScope()) { var actionLogService = scope.ServiceProvider.GetRequiredService<IActionLogService>(); var httpContextAccessor = scope.ServiceProvider.GetRequiredService<IHttpContextAccessor>(); var httpContext = httpContextAccessor.HttpContext; string usermail = httpContext.User.Identity.Name; int userId = 0; int companyId = 0; if (httpContext.User.Identity.IsAuthenticated) { var userIdClaim = httpContext.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim != null) userId = int.Parse(userIdClaim.Value); var companyIdClaim = httpContext.User.FindFirst("CompanyId"); if (companyIdClaim != null) companyId = int.Parse(companyIdClaim.Value); } // 创建操作详情 var processDetail = CreateProcessDetail(invocation); // 执行日志记录 actionLogService.AddLogRecordAsync(userId, usermail, companyId, invocation.Method.Name.ToUpper(), processDetail) .ConfigureAwait(false) .GetAwaiter() .GetResult(); } // ... 其余代码同理修复注释 } } // 将方法移到OnAfter外部 private string CreateProcessDetail(IInvocation invocation) { var methodName = invocation.Method.Name; var arguments = invocation.Arguments; string message = $"{methodName} metodu çağrıldı. Parametreler: "; for (int i = 0; i < arguments.Length; i++) { message += $"Parametre {i + 1}: {arguments[i]?.ToString() ?? \"null\"}; "; } return message; }
4. 异步方法拦截的兼容性问题
当前拦截器是同步实现,而目标方法是异步Task方法。Castle DynamicProxy对异步方法的拦截需要特殊处理,否则可能导致拦截逻辑不执行或异常。
调整方案:
修改MethodInterception的Intercept方法以支持异步方法:
public override void Intercept(IInvocation invocation) { var isSuccess = true; OnBefore(invocation); try { invocation.Proceed(); // 处理异步方法 if (invocation.Method.ReturnType.IsTaskOrTaskOfT()) { var task = (Task)invocation.ReturnValue; invocation.ReturnValue = task.ContinueWith(t => { if (t.IsFaulted) { isSuccess = false; OnException(invocation, t.Exception.InnerException); } else { OnSuccess(invocation); } OnAfter(invocation); return t.IsCompletedSuccessfully ? t.Result : null; }, TaskContinuationOptions.ExecuteSynchronously); return; } } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); } // 扩展方法判断是否为Task类型 public static class TypeExtensions { public static bool IsTaskOrTaskOfT(this Type type) { return type == typeof(Task) || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)); } }
5. 拦截器选择器的方法属性获取错误
AspectInterceptorSelector中使用type.GetMethod(method.Name)可能获取到错误的重载方法,应直接使用传入的method参数获取属性。
调整方案:
修改选择器代码:
public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) { var classAttributes = type.GetCustomAttributes<MethodInterceptionBaseAttribute>(true).ToList(); // 直接使用传入的method参数获取属性,避免重载问题 var methodAttributes = method.GetCustomAttributes<MethodInterceptionBaseAttribute>(true); classAttributes.AddRange(methodAttributes); return classAttributes.OrderBy(x => x.Priority).ToArray(); }
内容的提问来源于stack exchange,提问作者jemil

