You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Castle DynamicProxy实现LogAspect拦截失效求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 18:37:01