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






4.78/5 (12投票s)
AOP 和 Spring.NET 系列的第二部分。
引言
在我之前的文章中,我讨论了面向切面编程 (AOP) 以及我们如何使用 Spring.NET 在多层应用程序中实现 AOP 概念。
在本文中,我将增强之前文章中创建的 Advice,增加更多的灵活性,使其可以在实时应用程序中使用。
早期实现的局限性
为什么我们需要这个系列的第二部分?嗯,我之前的文章和实现存在一些局限性。我之前文章的要点是介绍 AOP 概念并解释如何进行一个简单的实现,以帮助那些刚接触 Spring.NET 和 AOP 的开发人员。我故意把它做得简单。
局限性包括:
- Advice 总是写入日志文件,无法开启或关闭。
- 文件名路径是硬编码的。
我们需要 Advice 具有更大的灵活性。我们应该能够向其传递参数,以便避免硬编码并实现灵活性。我们应该允许开发人员创建自定义 Advice 以满足其业务需求,同时向他们展示一种标准的实现方式。为了克服所有这些局限性,我撰写了这一部分,其中我解释了如何实现 Advice,并融入了最佳的面向对象概念和设计原则。
深入创建自定义 Advice
应用程序架构
应用程序具有三个逻辑层 - UI、业务逻辑和数据访问。Advice 应用于数据访问层。Advice 作为独立的 C# 类库项目创建。下图给出了应用程序的逻辑架构。
代码使用 Northwind 数据库,脚本可以从此处下载。
解决方案结构
下图展示了此应用程序的解决方案结构。为了节省空间,我只展开了 AOP 项目,其余项目的结构与我之前的 AOP 文章类似。
详细设计
本节是文章的核心。我将重点介绍如何创建更灵活的 Advice。在整篇文章中,我将专注于 Around Advice。其他类型的 Advice 将留给您自行研究。
IAroundInterface & AroundAdviceBase
我创建了自定义接口 "IAroundAdvice
"。它将内部继承自 IMethodInterceptor
(Spring.NET) 接口。我的自定义接口定义了 Initialize
、BeforeMethodCall
和 AfterMethodCall
等方法。方法名称具有自解释性。该接口具有一个名为 Argument
的 getter 和 setter 属性。该接口还将继承 IDisposable
。如果您需要创建 Around advice,则需要实现此接口。
我创建了一个名为 "AroundAdviceBase
" 的抽象基类,它实现了 IAroundAdvice
接口。这个类为 Around advice 提供了默认功能。
AroundAdviceBase
类将 Initialize
、BeforeMethodCall
和 AfterMethodCall
(IAroundAdvice
的方法)声明为抽象方法。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
类为 Initialize
、BeforeMethodCall
和 AfterMethodCall
方法以及 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 项目的详细类图如下所示
Spring 配置
众所周知,所有东西都是在运行时挂钩的,Spring.NET 需要为此进行配置。下图给出了 Advice 和 Argument 的配置,以及它们如何应用于 Customer
DAL 类。
对象 "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 和传递参数。它向您介绍了如何提供一个健壮且灵活的组件,该组件允许开发人员使用默认功能,或者创建新功能并将其插入到系统中,而无需编写太多代码,无需破坏流程和设计,并遵循组件设定的设计标准。
阅读愉快!编码愉快!