使用 Spring.NET 进行 AOP - 第一部分






4.80/5 (27投票s)
面向方面编程 (Aspect Oriented Programming) 及其在 Spring.NET 中的支持。
引言
在我之前的文章中,我讨论了 Spring.NET 的依赖注入 (Dependency Injection) 技术以及如何在实际场景中使用它。本文将重点介绍另一个名为面向方面编程 (Aspect Oriented Programming) 的功能,以及 Spring.NET 如何支持它。这将是我计划撰写的一系列 AOP 文章的第一篇。
面向方面编程 (AOP)
AOP 是一种设计原则,有助于在开发多层系统时实现关注点分离。AOP 是基于面向对象编程 (Object Oriented Programming) 概念构建的。在现代框架/运行时(如 CLR 和 JRE)中,AOP 是通过反射 (Reflection) 来实现的。
既然提到了关注点分离,现在是时候来谈谈它了。在 AOP 的世界里,一个“关注点”可以被定义为满足一个业务需求。例如,从数据库获取客户列表可能就是一个关注点。关注点有两种类型——核心关注点和横切关注点。
- 核心关注点 (Core concern): 这定义了主要的业务需求。例如,获取客户列表、更新客户信息等。
- 横切关注点 (Cross-cutting concern): 这定义了在执行核心关注点时需要发生的其他活动。这些活动可能跨越多个层和类。例如,为了监控性能而记录检索客户列表所花费的时间,为了审计目的而记录客户更新活动等。
关注点分离
为什么我们需要关注点分离?
嗯,从宏观角度来看,我们需要一种清晰的处理事情的方式。为了满足一个业务需求,我们创建了软件(类/组件/方法),同时我们还创建了额外的软件,如日志记录、异常处理、性能监控等,这些并非实际的业务需求。额外的软件在某些区域(如开发、QA 环境)是必需的,而在另一些区域(生产环境)则是可选的。那么,我们如何实现一个在某些阶段是必需的,而在另一些阶段是可选的软件呢?答案就是——关注点分离。
我们通过 AOP 概念将实际逻辑(核心关注点)和可选逻辑(横切关注点)分离开来。
从微观角度来看,这归结为实际逻辑和基础设施逻辑的分离。我们创建执行实际逻辑的类/方法,然后创建一个执行基础设施逻辑的单独的类/方法,并在运行时根据配置将它们粘合在一起。在 QA 环境中,可以启用此配置,从而将它们添加进来;而在生产环境中,则可以禁用它们。实际逻辑将不包含任何基础设施代码,它可以专注于其应该做的事情。所有基础设施代码(横切关注点逻辑)都是单独开发的。
以下是一些好处:
- 核心逻辑不关心基础设施逻辑
- 基础设施逻辑可以在运行时插入,并根据环境启用/禁用
- 基础设施逻辑的任何更改、错误修复、新版本都不会影响核心逻辑
- 松耦合系统
AOP 术语
在我们深入探讨之前,需要了解一些 AOP 术语。
- 通知 (Advice): 我们编写的用于实现横切关注点的基础设施代码或附加代码被创建为“通知”并应用于核心关注点。
- 切点 (Point-cut): 这是将“通知”应用于代码的点。如果我们想在数据访问层方法调用执行之前编写跟踪消息,那么“切点”就定义为在执行数据访问层方法调用之前。
- 方面 (Aspect): “通知”和“切点”的组合称为“方面”。
使用 Spring.NET 进行 AOP
AOP 是 Spring.NET 提供的核心功能之一。本节将介绍实现此功能的步骤。
场景
场景与我之前的文章非常相似。我有三个层——表示层 (Presentation Layer)、业务逻辑层 (Business Logic Layer, BLL) 和数据访问层 (Data Access Layer, DAL)。UI 层实现了 MVP (Model-View-Presenter) 模式,我创建了视图(WinForms)、Presenter 类和 `DataTable` 作为模型。
UI 调用 Presenter,Presenter 与 BLL 交互,BLL 与 DAL 交互并从 Northwind 数据库检索数据。
逻辑架构
下图显示了应用程序的层次结构
数据访问层 (DAL) 应用了“环绕通知” (Around Advice) 来跟踪方法调用。基本上,我们希望为数据访问层编写“开始”和“结束”跟踪消息。没有 AOP,我原本会这样实现跟踪需求:
public DataSet GetAll()
{
TracingComponent.Write("Begin of GetAll()");
// Retrive the data from database. Perform the actual logic
DataSet dataSet = null;// get data from database;
TracingComponent.Write("End of GetAll()");
return dataSet;
}
在上面的代码片段中,我们在代码中直接使用了 `TracingComponent`(一个虚构的组件)。对 `TracingComponent` 的任何更改都会影响我们的数据访问层代码。使用 AOP,情况得到了改善,这正是我们现在将要关注的。
使用 Spring.NET 进行 AOP 的步骤
- 创建通知,并在通知中实现基础设施代码
- 配置通知和目标对象
- 使用 Spring 应用程序上下文获取对象实例
- 调用目标方法
在了解了高级步骤之后,现在是时候深入代码来理解如何将这些内容付诸实践了。
创建通知
步骤 1:创建通知。Spring.NET 提供了多种类型的通知。它们是:前置通知 (Before Advice)、后置通知 (After Advice)、环绕通知 (Around Advice) 和异常通知 (Throws Advice)。这些名称不言自明,我将留给您去学习。
在我们的例子中,我们将创建一个环绕通知。这将帮助我们在目标方法调用之前和之后进行拦截。我们需要实现“`AopAlliance.Intercept`”命名空间中提供的 `IMethodInterceptor` 接口。我们需要为 `IMethodInterceptor` 的 `Invoke()` 方法提供一个实现。其签名如下:
public interface IMethodInterceptor
{
object Invoke(IMethodInvocation invocation);
}
在这里,`IMethodInvocation` 将持有指向调用方法的实际对象的引用。我们将创建一个实现此接口的类。
using System.IO;
// Diagnostics namespace
using System.Diagnostics;
// Spring namespaces
using AopAlliance.Intercept;
using AOP.Argument;
public class TraceAroundAdvice : IMethodInterceptor
{
private FileStream _stream = null;
private TextWriterTraceListener _listener = null;
public TraceAroundAdvice()
{
_stream = File.Open(@"C:\temp\Logs.txt", FileMode.Append);
_listener = new TextWriterTraceListener(_stream);
Trace.Listeners.Add(_listener);
}
public object Invoke(IMethodInvocation invocation)
{
// Write "begin" trace message
string message = string.Format("[{0}] Begin method call {1}",
DateTime.Now.ToString("M/dd/yy hh:m:ss"),
invocation.Method.Name);
Trace.WriteLine(message);
Trace.Flush();
// Call the actual method
object returnValue = invocation.Proceed();
// Write "end" trace message
message = string.Format("[{0}] Completed method call {1}",
DateTime.Now.ToString("M/dd/yy hh:m:ss"),
invocation.Method.Name);
Trace.WriteLine(message);
Trace.Flush();
// return the result
return returnValue;
}
}
TraceAroundAdvice 类的构造函数初始化跟踪监听器。`Invoke()` 方法在 `invocation.Proceed()` 调用之前和之后写入消息。
Spring 配置
Spring 配置与我们在 DI 文章中学到的非常相似。我们需要包含一个 Spring 配置部分,并定义上下文和对象图。我们还需要在对象图中定义通知。
下图显示了通知的配置
在上图中,`CustomerDAL` 类被定义了两次(用于演示目的)——一次是没有通知的“`CustomerDAL`”(类似于 DI 文章中的),另一次是带通知的“`CustomerDALWithAdvice`”。对象配置“`CustomerDALWithAdvice`”添加了“`TraceAroundAdvice`”通知。此通知定义在“`InterceptorNames`”属性中。注意“`CustomerDALWithAdvice`”对象属性的类型——它显示了 Spring 框架的“`ProxyFactoryObject`”类。`Target` 属性指向实际对象,即“`DAL.Customer.CustomerDAL`”类。
`TraceAroundAdvice` 本身也有自己的对象配置,如下图底部所示。带通知的配置可以这样解析:当使用“`CustomerDALWithAdvice`”时,Spring 工厂(`Spring.Aop.Framework.ProxyFactoryObject`)类会创建一个在 `Target` 属性中定义的类的实例。在我们的例子中,它是“`DAL.Customer.CustomerDAL`”,位于“DAL”程序集中。在创建此实例时,它会将 `InterceptorNames` 属性中定义的通知添加到执行管道中。在我们的例子中,将添加 `TraceAroundAdvice`。
初始化 Spring 配置并调用目标方法
以下代码片段显示了初始化 Spring 配置并获取 `IApplicationContext` 实例所涉及的步骤
// Include namespaces
using Spring.Context;
using Spring.Context.Support;
// Get the context
IApplicationContext applicationContext = ContextRegistry.GetContext();
// Get the instance through Spring configuration
IDAL _customerDAL = applicationContext[“customerDALWithAdvice”];
// Call the target method
_customerBLL.GetAll();
示例应用程序
示例应用程序包含三个层——UI、BLL 和 DAL。每个层都在单独的程序集中开发。UI 层是一个 Windows Forms 应用程序。除了这些程序集之外,通知还维护在名为“AOP”的另一个程序集中。下图显示了解决方案结构
在 UI 层,窗体使用 DI 创建 presenter。presenter 使用 DI 创建 BLL。BLL 使用 DI 创建 DAL(使用“`CustomerDAL`”时)或 AOP(使用“`CustomerDALWithAdvice`”时)。应用程序的配置如下所示
当应用程序运行时,在使用 Customer DAL 方法时,将在“C:\Temp\Logs.txt”文件中写入方法调用之前的跟踪消息和之后的消息。
结论
本文介绍了 AOP 概念、关注点分离和术语。我们讨论了 Spring.NET 的 AOP 支持,以及创建通知并将其添加到目标对象所涉及的步骤。我们还介绍了一个使用 Spring 的 AOP 和 DI 技术的示例多层应用程序。
阅读愉快!编码愉快!