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

AOP 容器 2

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2011 年 10 月 24 日

CPOL

5分钟阅读

viewsIcon

22424

downloadIcon

226

讨论了用于向组件注册切面的 Fluent 接口,以及用于将切面链接到 AOP Container 的对象的函数。

引言

文章 AOP Container 介绍了一种 AOP 模型,该模型通过配置使任何 IoC 容器都具有 AOP 功能。例如,该模型已应用于 MEFUnityWindsor 容器。

自发布以来,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。在我们继续之前,让我更详细地解释一下 AOPContainerRegisterAspect 函数。RegisterAspect 的声明如下。

public AOPContainer RegisterAspect<T, V>(string methods, 
                    Decoration preDeco, Decoration postDeco) where T : V
  • T - 目标类型(目标是将切面附加到的原始对象)
  • V - 目标类型实现的接口,在使用 AOPContainer 解析 T 类型对象时返回
  • methods - 目标函数(如果多个,则用逗号分隔)的名称,这些函数将附加由 preDecopostDeco 指定的切面
  • preDeco - 预处理 Decoration
  • postDeco - 后处理 Decoration

DecorationDecorationDelegateobject[] 的聚合。其构造函数如下

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 注册到 RepositoryDepartmentGetAll 函数,该函数实现了 IRepository<IDepartment>SharedLib.SharedConcerns.EnterLogSharedLib.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)
)

将一个空预处理切面和一个匿名函数作为后处理切面注册到 RepositoryEmployeeGetAll 函数,该函数实现了 IRepository<IEmployee>。请注意,匿名函数为 RepositoryEmployee 提供了排序逻辑。

代码

RegisterAspect<Department, IDepartment>("get_DepartmentID",
    new Decoration(SharedLib.SharedConcerns.EnterLog, null),
    null)

将预处理切面 SharedLib.SharedConcerns.EnterLog 和空后处理切面注册到 Departmentget_DepartmentID 函数,该函数实现了 IDepartment

类型注册和切面注册已完成。在使用 AOP Container 执行一些有用的操作之前,我们设置安全策略为 WindowsPrincipal,将部门和员工加载到数据集中,然后打印出员工。

现在,我们可以使用 AOP Container 来执行一些操作了。

首先,我们使用 aopcontainer 解析一个 RepositoryDepartment 对象。它将 IRepository<IDepartment> 的接口返回给变量 rpDepartment。当使用 rpDepartment 调用 GetAll 函数时,您将在目标函数执行之前看到进入日志,在之后看到退出日志。

接下来,rpDepartment.RepList[0].DepartmentID 将在返回部门 ID 之前写入进入日志。之所以这样工作,是因为 aopcontainerRepositoryDepartmentGetAll 中用于解析单个部门。

类似地,aopcontainer 用于解析一个 RepositoryEmployee 对象,该对象将 IRepository<IEmployee> 的接口返回给变量 rpEmployee。当使用 rpEmployee 调用 GetAll 函数时,它将对员工列表进行排序并将其保存在目标中。

使用 RegisterAspect<T, V> 函数将切面注册到组件意味着通过 Resolve<T, V> 解析的组件的所有对象都将附加切面。但是,在某些情况下,您可能不希望组件的所有对象都附加切面,而是希望在对象创建后向其添加切面。还有一些情况,您希望在对象创建后向其链接更多切面。AOPContainerChainAspect<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 结合使用。
© . All rights reserved.