17

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

两篇文章都与创建拦截器有关,但我不明白为什么拦截器与类一起使用......为什么我应该拦截行为正确的类?

4

2 回答 2

62

假设您的班级需要为某个操作做 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;
    }
}

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

  • 如果OrderService没有所有三个依赖项,该类将无法运行。如果我们想做到这一点,我们需要开始在代码中到处添加空检查。

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

  • 所有这些样板代码都必须在每个方法中重复,从而形成一个非常大、丑陋、容易出错的实现。

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

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

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

我们没有一遍又一遍地实际编写依赖代码,而是留下“提示”,应该为这个方法执行一些额外的操作。

当然,这些属性有时必须转化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();
    }
}

创建代理将是:

var generator = new ProxyGenerator();
var orderService = (IOrderService)generator.CreateClassProxy(typeof(OrderService),
    new LoggingInterceptor());

这不仅减少了重复代码,而且完全消除了实际的依赖关系,因为看看我们所做的——我们甚至没有授权或缓存系统,但系统仍然运行。我们可以稍后通过注册另一个拦截器并检查AuthorizeAttributeor来插入授权和缓存逻辑CacheAttribute

希望这能解释“为什么”。

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

于 2010-04-07T14:05:30.557 回答
4

您使用 Castle-DynamicProxy 的原因是所谓的面向方面编程。它使您可以将代码插入代码的标准操作流程,而无需依赖代码本身。

一个简单的例子是一如既往的记录。您将围绕一个您有错误的类创建一个 DynamicProxy,它会记录进入方法的数据并捕获任何异常,然后记录异常。

使用拦截器,您当前的代码不知道它存在(假设您的软件以解耦方式构建,并且接口正确)并且您可以使用控制容器的反转来更改类的注册以使用代理类,而无需在代码中的其他地方更改一行。然后,当您解决错误时,您可以关闭代理。

使用 NHibernate 可以看到更高级的代理使用,其中所有的延迟加载都是通过代理处理的。

于 2010-04-07T13:43:18.430 回答