真正的拦截器对我的c#类做了什么?

ACP*_*ACP 12 c# castle-dynamicproxy class interceptor

我被要求在我的asp.net Web应用程序中实现城堡动态代理,我正在阅读几篇文章,我从Castle ProjectCode Project获得了关于asp.net web应用程序中的城堡动态代理....

这两篇文章都与创建拦截器有关,但是我无法理解为什么拦截器会与类一起使用....为什么我要拦截行为正常的类?

Aar*_*ght 53

假设你的班级需要为某项操作做3件事:

  1. 执行安全检查;
  2. 记录方法调用;
  3. 缓存结果.

让我们进一步假设您的类对您配置安全性,日志记录或缓存的具体方式一无所知.你需要依赖这些东西的抽象.

有几种方法可以解决它.一种方法是设置一堆接口并使用构造函数注入:

public class OrderService : IOrderService
{
    private readonly IAuthorizationService auth;
    private readonly ILogger logger;
    private readonly ICache cache;

    public OrderService(IAuthorizationService auth, ILogger logger,
        ICache cache)
    {
        if (auth == null)
            throw new ArgumentNullException("auth");
        if (logger == null)
            throw new ArgumentNullException("logger");
        if (cache == null)
            throw new ArgumentNullException("cache");
        this.auth = auth;
        this.logger = logger;
        this.cache = cache;
    }

    public Order GetOrder(int orderID)
    {
        auth.AssertPermission("GetOrder");
        logger.LogInfo("GetOrder:{0}", orderID);
        string cacheKey = string.Format("GetOrder-{0}", orderID);
        if (cache.Contains(cacheKey))
            return (Order)cache[cacheKey];
        Order order = LookupOrderInDatabase(orderID);
        cache[cacheKey] = order;
        return order;
    }
}
Run Code Online (Sandbox Code Playgroud)

这不是可怕的代码,但想想我们引入的问题:

  • OrderService没有所有三个依赖项,该类无法运行.如果我们想要这样做,我们需要开始在每个地方使用空检查来编写代码.

  • 我们正在编写大量额外代码来执行相对简单的操作(查找订单).

  • 所有这些样板代码都必须在每个方法中重复,这使得实现非常大,丑陋,容易出错.

这是一个更容易维护的类:

public class OrderService : IOrderService
{
    [Authorize]
    [Log]
    [Cache("GetOrder-{0}")]
    public virtual Order GetOrder(int orderID)
    {
        return LookupOrderInDatabase(orderID);
    }
}
Run Code Online (Sandbox Code Playgroud)

面向方面编程中,这些属性称为连接点,其完整集合称为点切割.

我们不是一遍又一遍地实际编写依赖代码,而是留下"提示",即应该为此方法执行一些额外的操作.

当然,这些属性必须得到变成代码的某个时候,但你可以推迟,所有的方式到你的主应用程序代码,通过创建一个代理OrderService(注意GetOrder方法已经取得了virtual,因为它需要重写的服务),并拦截GetOrder方法.

编写拦截器可能就像这样简单:

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        if (Attribute.IsDefined(invocation.Method, typeof(LogAttribute))
        {
            Console.Writeline("Method called: "+ invocation.Method.Name);
        }
        invocation.Proceed();
    }
}
Run Code Online (Sandbox Code Playgroud)

创建代理将是:

var generator = new ProxyGenerator();
var orderService = (IOrderService)generator.CreateClassProxy(typeof(OrderService),
    new LoggingInterceptor());
Run Code Online (Sandbox Code Playgroud)

这不仅是重复性较低的代码,而且它完全消除了实际的依赖性,因为看看我们做了什么 - 我们甚至没有授权或缓存系统,但系统仍在运行.我们稍后可以通过注册另一个拦截器并检查AuthorizeAttribute或来插入授权和缓存逻辑CacheAttribute.

希望这能解释"为什么".

边栏:正如KrzysztofKoźmic评论的那样,使用这样的动态拦截器并不是DP的"最佳实践".在生产代码中,您不希望拦截器运行不必要的方法,因此请使用IInterceptorSelector.

  • 你几乎明白了。从技术上讲,我将把“我们在正确的方法中”的检查移到拦截器选择器中(一个IInterceptorSelector实现)。 (2认同)
  • @KrzysztofKoźmic:这是你的博客让我开始使用DP,所以我很高兴你赞成!我倾向于同意'IInterceptorSelector'的偏好,我主要在这里排除它,以使这个非常基本的例子对那些不熟悉DP的人来说更加可口. (2认同)