AOP 容器 2





5.00/5 (1投票)
讨论了用于向组件注册切面的 Fluent 接口,以及用于将切面链接到 AOP Container 的对象的函数。
引言
文章 AOP Container 介绍了一种 AOP 模型,该模型通过配置使任何 IoC 容器都具有 AOP 功能。例如,该模型已应用于 MEF、Unity 和 Windsor 容器。
自发布以来,AOP Container 已得到增强,增加了新功能,例如通过代码注册切面以及在对象创建后向对象添加切面。本文将讨论这些功能。
背景
我创建 AOP Container 的原因是方便为 IoC Containers 添加 AOP 功能。因此,AOP Container 提供方法以代码方式注册切面,就像大多数 IoC Containers 提供方法以代码方式注册类型一样,这是有道理的。
在某些情况下,您可能不希望组件的所有对象都具有切面,而是希望在创建后根据需要向特定对象添加或链接切面。AOP Container 通过提供一个函数供您在对象创建后使用,从而提供了这种灵活性。
在接下来的几节中,我将讨论如何通过代码向组件注册切面以及如何在创建后将切面链接到对象。Windsor Container 和 Unity Container 将作为 IoC Containers 的示例。
Using the Code
以下代码演示了 AOP Container 与 Windsor Container 和 Unity Container 的结合使用。基本上,AOP Container 为切面注册和对象解析提供了一个通用的应用程序编程接口。
static void Main(string[] args)
{
AOPContainer aopcontainer = null;
switch (args[0])
{
case "0":
//Register types for Windsor Container and create AOPWindsorContainer
{
IWindsorContainer windsorContainer = new WindsorContainer();
windsorContainer.Register(AllTypes
.FromAssembly(Assembly.LoadFrom("Employee.dll"))
.Where(t => t.Name.Equals("Employee"))
.Configure(c => c.LifeStyle.Transient)
).Register(AllTypes
.FromAssembly(Assembly.LoadFrom("Department.dll"))
.Where(t => t.Name.Equals("Department"))
.Configure(c => c.LifeStyle.Transient)
).Register(AllTypes
.FromAssembly(Assembly.GetExecutingAssembly())
.Where(t => (t.Name.Equals("RepositoryEmployee") ||
t.Name.Equals("RepositoryDepartment")))
.Configure(c => c.LifeStyle.Transient)
).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IEmployee>))
).Register(Castle.MicroKernel.Registration.Component.For(typeof(List<IDepartment>))
);
aopcontainer = new AOPWindsorContainer(windsorContainer);
}
break;
case "1": //Register types for Unity Container and create AOPUnityContainer
{
IUnityContainer unityContainer = new UnityContainer();
unityContainer.RegisterType<IEmployee, Employee>(new InjectionConstructor()
).RegisterType<IDepartment, Department>(new InjectionConstructor()
).RegisterType<IRepository<IDepartment>, RepositoryDepartment>(new InjectionConstructor()
).RegisterType<IRepository<IEmployee>, RepositoryEmployee>(new InjectionConstructor()
).RegisterType<IList<IEmployee>, List<IEmployee>>(new InjectionConstructor()
).RegisterType<IList<IDepartment>, List<IDepartment>>(new InjectionConstructor()
);
aopcontainer = new AOPUnityContainer(unityContainer);
}
break;
default:
{
Console.WriteLine("Invalid number for an AOP container.");
Console.ReadLine();
}
return;
}
//Register Aspects
aopcontainer.RegisterAspect<RepositoryDepartment,
IRepository<IDepartment>>("GetAll",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null)
).RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
null,
new Decoration((ctx, parameters) =>
{
object target = ctx.Target;
if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null)
).RegisterAspect<Department, IDepartment>("get_DepartmentID",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
null);
//Set security policy.
//Commenting out this line, the security check aspect will throw out an exception
Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
//Load employees and departments into a data set from an xml file.
DataSet dsDB = new DataSet();
dsDB.ReadXml("employees.xml");
//Print original employees
Console.WriteLine("Employees");
string str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (DataRow r in dsDB.Tables["Employee"].Rows)
{
str = r["EmployeeID"].ToString() + "\t" +
r["FirstName"].ToString() + "\t" +
r["LastName"].ToString() + "\t" +
r["DateOfBirth"].ToString() + "\t" +
r["DepartmentID"].ToString();
Console.WriteLine(str);
}
Console.WriteLine();
IRepository<IDepartment> rpDepartment =
aopcontainer.Resolve<RepositoryDepartment,
IRepository<IDepartment>>("GetAll");
rpDepartment.AopContainer = aopcontainer;
rpDepartment.DataSource = dsDB;
//The following line populates a department list and writes enter/exit logs.
rpDepartment.GetAll();
//The following line gets a department id and writes enter log.
int? iDep = rpDepartment.RepList[0].DepartmentID;
IRepository<IEmployee> rpEmployee =
aopcontainer.Resolve<RepositoryEmployee,
IRepository<IEmployee>>("GetAll");
rpEmployee.AopContainer = aopcontainer;
rpEmployee.DataSource = dsDB;
//The following line populates an employee list and sorts the list.
rpEmployee.GetAll();
Console.WriteLine();
//Print employees sorted by employee's last name
Console.WriteLine("Employees sorted by employee's last name");
str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (IEmployee em in rpEmployee.RepList)
{
str = em.EmployeeID.ToString() + "\t" + em.FirstName +
"\t" + em.LastName + "\t" +
em.DateOfBirth.ToShortDateString() + "\t" +
em.DepartmentID.ToString();
Console.WriteLine(str);
}
Console.WriteLine();
try
{
IEmployee oEm = rpEmployee.RepList[0];
//Add a security check aspect to an employee before changing its property
oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
"set_EmployeeID,set_FirstName,set_LastName,set_DateOfBirth,set_DepartmentID",
new Decoration(AppConcerns.SecurityCheck,
new object[] { System.Threading.Thread.CurrentPrincipal }),
null);
//The following line check security before setting the property
oEm.DepartmentID = 2;
//Chain enetr/exit log aspects to the employee's DetailsByLevel method
oEm = AOPContainer.ChainAspect<IEmployee, IEmployee>(oEm,
"DetailsByLevel",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null));
//The following line write entering log before and exiting log
//after execution of the target's DetailsByLevel(3)
oEm.DetailsByLevel(3);
Console.WriteLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine();
}
//Print employees after sort and update
Console.WriteLine("Employees after sort and update");
str = "ID\tFName\tLName\tDateOfBirth\tDepartmentID";
Console.WriteLine(str);
Console.WriteLine("====================================================");
foreach (IEmployee em in rpEmployee.RepList)
{
str = em.EmployeeID.ToString() + "\t" + em.FirstName + "\t" +
em.LastName + "\t" + em.DateOfBirth.ToShortDateString() +
"\t" + em.DepartmentID.ToString();
Console.WriteLine(str);
}
Console.WriteLine();
aopcontainer.IocContainer.Dispose();
Console.ReadLine();
}
代码的第一部分(switch ... case
子句之间的代码)特定于 IoC Containers。也就是说,根据使用的 IoC Container,您将组件注册到 IoC Container 并创建相应的 AOP Container。case "0" 的代码特定于 Windsor Container,而 case "1" 的代码特定于 Unity Container。该代码基本上为 IoC Containers 注册类型,以便稍后可以使用 IoC Containers 解析已注册类型的对象。根据使用的 IoC Container,您还可以创建相应的 AOP Container。对于 Windsor Container,通过传递 Windsor Container 的实例来创建 AOPWindsorContainer
。对于 Unity Container,通过传递 Unity Container 的实例来创建 AOPUnityContainer
。
其余代码独立于 IoC Containers。在我们继续之前,让我更详细地解释一下 AOPContainer
的 RegisterAspect
函数。RegisterAspect
的声明如下。
public AOPContainer RegisterAspect<T, V>(string methods,
Decoration preDeco, Decoration postDeco) where T : V
T
- 目标类型(目标是将切面附加到的原始对象)V
- 目标类型实现的接口,在使用AOPContainer
解析T
类型对象时返回methods
- 目标函数(如果多个,则用逗号分隔)的名称,这些函数将附加由preDeco
和postDeco
指定的切面preDeco
- 预处理Decoration
postDeco
- 后处理Decoration
Decoration
是 DecorationDelegate
和 object[]
的聚合。其构造函数如下
public Decoration(DecorationDelegate aspectHandler, object[] parameters)
aspectHandler
- 一个委托,其第一个参数为AspectContext
,第二个参数为object[]
,返回值为void
parameters
- 调用时传递给aspectHandler
的第二个参数的对象数组
DecorationDelegate
定义如下
public delegate void DecorationDelegate(AspectContext ctx, object[] parameters)
本质上,AOP Container 的切面是一个具有 DecorationDelegate
签名的函数。
希望这能为您理解以下切面注册代码提供足够的背景信息。
代码
aopcontainer.RegisterAspect<RepositoryDepartment, IRepository<IDepartment>>("GetAll",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
new Decoration(SharedLib.SharedConcerns.ExitLog, null)
)
将预处理切面 SharedLib.SharedConcerns.EnterLog
和后处理切面 SharedLib.SharedConcerns.ExitLog
注册到 RepositoryDepartment
的 GetAll
函数,该函数实现了 IRepository<IDepartment>
。SharedLib.SharedConcerns.EnterLog
和 SharedLib.SharedConcerns.ExitLog
分别是用于写入进入/退出日志的切面函数。
代码
RegisterAspect<RepositoryEmployee, IRepository<IEmployee>>("GetAll",
null,
new Decoration((ctx, parameters) =>
{
object target = ctx.Target;
if (target.GetType().ToString() == "ConsoleUtil.RepositoryEmployee")
{
List<IEmployee> emps = ((IRepository<IEmployee>)target).RepList;
IEnumerable<IEmployee> query = emps.OrderByDescending(emp => emp,
new EmployeeLastnameComparer()).ToList<IEmployee>();
((IRepository<IEmployee>)target).RepList = (List<IEmployee>)query;
}
}, null)
)
将一个空预处理切面和一个匿名函数作为后处理切面注册到 RepositoryEmployee
的 GetAll
函数,该函数实现了 IRepository<IEmployee>
。请注意,匿名函数为 RepositoryEmployee
提供了排序逻辑。
代码
RegisterAspect<Department, IDepartment>("get_DepartmentID",
new Decoration(SharedLib.SharedConcerns.EnterLog, null),
null)
将预处理切面 SharedLib.SharedConcerns.EnterLog
和空后处理切面注册到 Department
的 get_DepartmentID
函数,该函数实现了 IDepartment
。
类型注册和切面注册已完成。在使用 AOP Container 执行一些有用的操作之前,我们设置安全策略为 WindowsPrincipal,将部门和员工加载到数据集中,然后打印出员工。
现在,我们可以使用 AOP Container 来执行一些操作了。
首先,我们使用 aopcontainer
解析一个 RepositoryDepartment
对象。它将 IRepository<IDepartment>
的接口返回给变量 rpDepartment
。当使用 rpDepartment
调用 GetAll
函数时,您将在目标函数执行之前看到进入日志,在之后看到退出日志。
接下来,rpDepartment.RepList[0].DepartmentID
将在返回部门 ID 之前写入进入日志。之所以这样工作,是因为 aopcontainer
在 RepositoryDepartment
的 GetAll
中用于解析单个部门。
类似地,aopcontainer
用于解析一个 RepositoryEmployee
对象,该对象将 IRepository<IEmployee>
的接口返回给变量 rpEmployee
。当使用 rpEmployee
调用 GetAll
函数时,它将对员工列表进行排序并将其保存在目标中。
使用 RegisterAspect<T, V>
函数将切面注册到组件意味着通过 Resolve<T, V>
解析的组件的所有对象都将附加切面。但是,在某些情况下,您可能不希望组件的所有对象都附加切面,而是希望在对象创建后向其添加切面。还有一些情况,您希望在对象创建后向其链接更多切面。AOPContainer
的 ChainAspect<T, V>
函数为这些情况提供了解决方案,可用于根据需要向对象添加或链接切面。
在上述代码的 try
块中,ChainAspect<IEmployee, IEmployee>
将安全检查切面添加到第一个员工,以便只有内置的管理员可以修改员工的属性。如果应用程序由非管理员执行,则会引发异常,并且属性不会被修改。
运行应用程序,输出如下所示
关注点
- AOP Container 提供了一种机制和一个通用的应用程序编程接口,用于向 IoC Containers 添加切面。
- AOP Container 提供了一个 Fluent 接口来向组件注册切面。由 AOP Container 解析的对象将附加切面。
- AOP Container 提供了一个函数,用于在对象创建后根据需要添加/链接切面。
- 尽管本文讨论了 AOP Container 与 Windsor 和 Unity IoC Containers 的结合使用,但您应该能够轻松地将 AOP Container 与其他 IoC Containers 结合使用。