65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Spring.NET 实现 AOP - 第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (12投票s)

2008年6月12日

CPOL

6分钟阅读

viewsIcon

51686

downloadIcon

659

AOP 和 Spring.NET 系列的第二部分。

引言

在我之前的文章中,我讨论了面向切面编程 (AOP) 以及我们如何使用 Spring.NET 在多层应用程序中实现 AOP 概念。

在本文中,我将增强之前文章中创建的 Advice,增加更多的灵活性,使其可以在实时应用程序中使用。

早期实现的局限性

为什么我们需要这个系列的第二部分?嗯,我之前的文章和实现存在一些局限性。我之前文章的要点是介绍 AOP 概念并解释如何进行一个简单的实现,以帮助那些刚接触 Spring.NET 和 AOP 的开发人员。我故意把它做得简单。

局限性包括:

  • Advice 总是写入日志文件,无法开启或关闭。
  • 文件名路径是硬编码的。

我们需要 Advice 具有更大的灵活性。我们应该能够向其传递参数,以便避免硬编码并实现灵活性。我们应该允许开发人员创建自定义 Advice 以满足其业务需求,同时向他们展示一种标准的实现方式。为了克服所有这些局限性,我撰写了这一部分,其中我解释了如何实现 Advice,并融入了最佳的面向对象概念和设计原则。

深入创建自定义 Advice

应用程序架构

应用程序具有三个逻辑层 - UI、业务逻辑和数据访问。Advice 应用于数据访问层。Advice 作为独立的 C# 类库项目创建。下图给出了应用程序的逻辑架构。

代码使用 Northwind 数据库,脚本可以从此处下载。

LogicalArchitecture.JPG

解决方案结构

下图展示了此应用程序的解决方案结构。为了节省空间,我只展开了 AOP 项目,其余项目的结构与我之前的 AOP 文章类似。

SolutionStructure.JPG

详细设计

本节是文章的核心。我将重点介绍如何创建更灵活的 Advice。在整篇文章中,我将专注于 Around Advice。其他类型的 Advice 将留给您自行研究。

IAroundInterface & AroundAdviceBase

我创建了自定义接口 "IAroundAdvice"。它将内部继承自 IMethodInterceptor (Spring.NET) 接口。我的自定义接口定义了 InitializeBeforeMethodCallAfterMethodCall 等方法。方法名称具有自解释性。该接口具有一个名为 Argument 的 getter 和 setter 属性。该接口还将继承 IDisposable。如果您需要创建 Around advice,则需要实现此接口。

我创建了一个名为 "AroundAdviceBase" 的抽象基类,它实现了 IAroundAdvice 接口。这个类为 Around advice 提供了默认功能。

AroundAdviceBase 类将 InitializeBeforeMethodCallAfterMethodCallIAroundAdvice 的方法)声明为抽象方法。Argument 属性(IAroundAdvice 的属性)声明为 抽象 属性。预期是派生类将提供实际实现,而此基类将提供结构。

AroundAdviceBase 类有一个重载构造函数,它在其中调用 Initialize() 方法。

AroundAdviceBase 类提供了 IMethodInterceptor.Invoke() 方法的实现。在该方法内部,它调用抽象方法 - BeforeMethodCall(), AfterMethodCall()。以下代码片段展示了 AroundAdviceBase 类的 Invoke 方法实现。invocation.Proceed() 语句调用实际的目标方法。

public virtual object Invoke(IMethodInvocation invocation)
{
    // Call Before method call activity
    BeforeMethodCall(invocation);

    // Call actual method
    object returnValue = invocation.Proceed();

    // Call After method call activity
    AfterMethodCall(invocation);

    return returnValue;
}

您可以从 AroundAdviceBase 类派生,只实现三个抽象方法和一个抽象属性,而不是实现 IAroundAdvice 和所有方法。

创建 Around Advice

一旦我们定义了接口和抽象类,现在是时候创建 Around advice 的具体实现了。我们希望创建一个 Advice,它将在方法调用之前和之后写入跟踪消息。在我们的例子中,我们需要从 AroundAdviceBase 类派生,并实现抽象方法和属性。"TraceAdviceBase" 类为我们完成了这项工作。

TraceAdviceBase 类为 InitializeBeforeMethodCallAfterMethodCall 方法以及 Argument 属性提供了实现。

Initialize() 方法接受一个 对象 作为参数,并将其值设置为 "TraceAdviceArgument" 类型的一个实例。我将很快讨论 "TraceAdviceArgument" 类型。但现在,您可以假设该对象是 "TraceAdviceArgument" 类型,并且该值已在 Initialize() 方法中设置。Initialize() 创建一个 TextWriteTraceListener (System.Diagnostics),创建一个文件流对象,并将其添加到跟踪监听器中。文件名通过 "TraceAdviceArgument" 传递。该方法最后初始化一个 布尔 变量,以指示 TraceAdviceBase 是否已初始化。以下代码片段显示了 Initialize() 方法的实现。

public override bool Initialize(object argument)
{

    if (argument != null)
    {
        _traceArgument = (TraceAdviceArgument)argument;
        _stream = File.Create(_traceArgument.TraceName);
        _listener = new TextWriterTraceListener(_stream);
        Trace.Listeners.Add(_listener);

        _isInitialized = true;
    }
    else
    {
        _isInitialized = false;
    }
    return true;
}

BeforeMethodCall() 方法检查初始化状态,并向在 Initialize() 方法中创建的跟踪监听器写入一条“开始”消息。

public override bool BeforeMethodCall(IMethodInvocation argument)
{
    if ( (_isInitialized == true ) && ( argument != null))
    {
        Trace.WriteLine("Begin method call " + argument.Method.Name);
        Trace.Flush();
        return true;
    }
    return false;
}

AfterMethodCall() 方法检查初始化状态,并向跟踪监听器写入一条“已完成”消息。

public override bool AfterMethodCall(IMethodInvocation argument)
{
    if ((_isInitialized == true) && (argument != null))
    {
        Trace.WriteLine("Completed method call " + argument.Method.Name);
        Trace.Flush();
        return true;
    }
    return false;
}

Argument 属性的 setter 会将值类型转换为 "TraceAdviceArgument" 类型并调用 Initialize()。实现如下所示:

public override object Argument
{
    get { return _traceArgument; }
    set
    {
        _traceArgument = (TraceAdviceArgument)value;
        Initialize(_traceArgument);
    }
}

传递参数

多次看到 "TraceAdviceArgument",现在是时候看看这个类了。顾名思义,这个类的目的是通过配置将参数传递给 Advice。该类定义了公共的 get/set 属性。如果您希望向 Advice 传递多个参数,可以将所有此类属性封装到一个自定义类中,提供公共的 get/set 属性,并在 Spring 对象图中进行配置。Spring 将创建此类的实例并将其传递给 Advice。参数类的实现如下所示。

public sealed class TraceAdviceArgument : IDisposable
{
    # region Private Members
    private string _traceName;
    # endregion

    # region Public Constructor
    public TraceAdviceArgument()
    {
        _traceName = string.Empty;
    }

    public TraceAdviceArgument(string traceName)
    {
        _traceName = traceName;
    }
    # endregion

    # region Public Property
    public string TraceName
    {
        get { return _traceName; }
        set { _traceName = value; }
    }
    # endregion

    #region IDisposable Members

    public void Dispose()
    {
        _traceName = null;
        return;
    }

    #endregion
}

在这种情况下,我只定义了一个参数,即文件名。您可以通过添加另一个名为 enabled/disabled 的属性来扩展此功能,该属性将决定是否写入跟踪以及是否从配置中传递布尔值。

AOP 类图

整个 AOP 项目的详细类图如下所示

AOPClassDiagram.JPG

Spring 配置

众所周知,所有东西都是在运行时挂钩的,Spring.NET 需要为此进行配置。下图给出了 Advice 和 Argument 的配置,以及它们如何应用于 Customer DAL 类。

Configuration.JPG

对象 "CustomerDALWithAdvice" 指向 DAL 程序集中的 CustomerDAL 类。它有一个拦截器,TraceAroundAdvice。这是我们在上一节中创建的 Advice。

"TraceAroundAdvice" 对象定义为 "TraceAdviceBase"(我们创建的 Advice 类)类型,名为 "Argument" 的属性指向 "TraceAdviceArgument" 类。TraceAdviceArgument 类接受一个名为 "TraceName" 的属性,我们已将其设置为 "C:\Temp\Trace.Log"。

因此,当您使用 "CustomerDALWithAdvice" 时,"TraceAroundAdvice" 将被应用,它将 "TraceAdviceArgument" 作为实例传递给 "Argument" 属性。参数类反过来定义了一个名为 "TraceName" 的属性,该属性代表物理文件名。

扩展 AOP 项目

到目前为止,我讨论了默认实现 - "TraceAdviceBase"。它从配置中获取一个参数,并将跟踪消息写入文件。

如果您需要创建一个 Around Advice 来执行其他任务,例如将跟踪消息写入数据库,监控性能统计信息(所花费的时间)等,您所需要做的就是从 AroundAdviceBase 派生并实现抽象方法和属性。

结论

本文详细介绍了如何创建 Advice 和传递参数。它向您介绍了如何提供一个健壮且灵活的组件,该组件允许开发人员使用默认功能,或者创建新功能并将其插入到系统中,而无需编写太多代码,无需破坏流程和设计,并遵循组件设定的设计标准。

阅读愉快!编码愉快!

© . All rights reserved.