【问题标题】:Autofac Interception with custom attributes具有自定义属性的 Autofac 拦截
【发布时间】:2015-04-26 16:06:08
【问题描述】:

我一直在寻找 AOP 日志记录的特定解决方案。我需要一个可以做这样的事情的拦截:

[MyCustomLogging("someParameter")]

问题是,我在其他 DI 框架中看到了使这成为可能的示例。但是我的项目已经在使用 Autofac 进行 DI,我不知道与 Unity 混合是否是个好主意(例如)。在 Autofac.extras.dynamiclibrary2 中,InterceptAttribute 类是密封的。

有人对这个问题有想法吗?

Ps.:我会满意的:

[Intercept(typeof(MyLoggingClass), "anotherParameter"]

【问题讨论】:

    标签: c# aop autofac custom-attributes


    【解决方案1】:

    尽管使用属性来丰富类型的元数据以feed 使用数据的横切关注点还不错,但使用属性来标记类或方法以运行 一些横切关注点通常是。

    用你展示的属性标记代码有一些严重的缺点:

    1. 它使您的代码依赖于使用的拦截库,使代码更难更改,更难替换外部库。应用程序核心与外部库的依赖关系应保持在绝对最低限度。如果你的代码充满了依赖注入库的依赖,那就太讽刺了。用于最小化外部依赖和增加松散耦合的工具。
    2. 要将横切关注点应用到范围广泛的类(这是您通常想要做的),您必须通过完整的代码库在方法中添加或删除属性。这是耗时且容易出错的。但更糟糕的是,确保方面以特定顺序运行对于属性来说是很困难的。一些框架允许您指定属性的顺序(使用某种Order 属性),但更改顺序意味着通过代码进行彻底的更改以更改属性的Order。忘记一个会导致错误。这违反了Open/closed principle
    3. 由于该属性引用了方面类(在您的示例中为typeof(MyLoggingClass)),这使得您的代码仍然静态地依赖于横切关注点。用另一个类替换该类将再次导致您对代码库进行彻底更改,并且保持硬依赖使重用代码或在运行时或部署时决定是否应应用方面变得更加困难。在许多情况下,您的代码不能对切面有这种依赖性,因为代码存在于基础库中,而切面特定于应用程序框架。例如,您可能拥有在 Web 应用程序和 Windows 服务中运行的相同业务逻辑。在 Web 应用程序中运行时,您希望以不同的方式登录。换句话说,您违反了Dependency inversion principle

    因此,我认为以这种方式应用属性是不好的做法。 与其使用这样的属性,不如使用拦截或装饰器透明地应用横切关注点。装饰器是我的首选方法,因为它们的使用更清洁、更简单,因此更易于维护。无需依赖任何外部库即可编写装饰器,因此可以将它们放置在应用程序中任何合适的位置。然而,装饰器的缺点是编写和应用它们非常麻烦,以防您的设计不是SOLIDDRY 并且您没有遵循Reused abstraction principle

    但是,如果您使用right application design SOLID 和message based patterns,您会发现应用横切关注点(例如日志记录)只是编写一个非常简单的装饰器的问题,例如:

    public class LoggingCommandHandlerDecorator<T> : ICommandHandler<T>
    {
        private readonly ILogger logger;
        private readonly ICommandHandler<T> decoratee;
        public LoggingCommandHandlerDecorator(ILogger logger, ICommandHandler<T> decoratee) {
            this.logger = logger;
            this.decoratee = decoratee;
        }
    
        public void Handle(T command) {
            this.logger.Log("Handling {0}. Data: {1}", typeof(T).Name,
                JsonConvert.SerializeObject(command));
            this.decoratee.Handle(command);
        }
    }
    

    如果没有适当的设计,您仍然可以使用拦截(没有属性),因为拦截允许您“装饰”任何在代码中似乎没有关系的类型(不共享公共接口)。定义要拦截的类型和不拦截的类型可能很麻烦,但您通常仍然可以在应用程序的一个位置进行定义,因此无需在整个代码库中进行彻底的更改。

    侧节点。正如我所说,使用属性来描述pure metadata 很好,preferable。例如,获取一些只允许为具有特定权限的用户运行的代码。您可以将该代码标记如下:

    [Permission(Permissions.Crm.ManageCompanies)]
    public class BlockCompany : ICommand {
        public Guid CompanyId;
    }
    

    此属性不描述运行的方面,也不引用来自外部库的任何类型(PermissionAttribute 是您可以(并且应该)自己定义的东西)或任何 AOP 特定的类型。它仅使用元数据丰富代码。

    最后,您显然想要应用一些横切关注点来检查当前用户是否具有正确的权限,但该属性不会强迫您进入特定方向。有了上面的属性,我可以想象装饰器看起来如下:

    public class PermissionCommandHandlerDecorator<T> : ICommandHandler<T>
    {
        private static readonly Guid requiredPermissionId =
            typeof(T).GetCustomAttribute<PermissionAttribute>().PermissionId;
    
        private readonly IUserPermissionChecker checker;
        private readonly ICommandHandler<T> decoratee;
    
        public PermissionCommandHandlerDecorator(IUserPermissionChecker checker,
            ICommandHandler<T> decoratee) {
            this.checker = checker;
            this.decoratee = decoratee;
        }
    
        public void Handle(T command) {
            this.checker.CheckPermission(requiredPermissionId);
            this.decoratee.Handle(command);
        }
    }
    

    【讨论】:

    • 感谢 Steven 的精彩回答。这确实让我思考。
    • 我很高兴能帮上忙,尽管我没有回答你的问题 :-)
    • @Steven 你的回答真的很棒,你为我提供了一些很棒的见解。但是,当涉及到一般意义上的应用程序工具时,我认为拦截机制(不是通过使用属性)也许是最好的解决方案。必须装饰应用程序的每个接口以实现类似的检测逻辑将违反 DRY 原则。实际上,Mark Seemann 有一篇关于这个主题的精彩博文:blog.ploeh.dk/2010/09/20/…
    • @LunaticSoul:我对您有另一个见解:如果您发现自己将一个方面应用于许多不相关的接口,那么您就违反了重用抽象原则。 Mark Seemann 有多个关于此的有趣帖子,例如 thisthisthis。有了适当的 SOLID、DRY、RAP 设计,我根本没有看到使用拦截的理由。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-09
    相关资源
    最近更新 更多