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

使用配置文件通过面向切面的编程 (AOP) 和 Unity 实现 NHibernate 的日志记录和事务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (9投票s)

2009年1月5日

CPOL

4分钟阅读

viewsIcon

63864

downloadIcon

354

在 NHibernate 上使用 Unity 应用 AOP

引言

日志记录和事务等横切关注点是每个企业应用程序的一部分。尽管它们是必需的,但有时它们会使代码变得模糊,占用的行数比实际业务逻辑还要多。此外,代码评审员/设计人员一直在努力确保开发人员在他们编写的代码中不会遗漏这些重要方面。使用 Unity 进行 AOP 提供了一种可配置的解决方案来解决这个问题,从而提高了代码的整体可读性和可维护性,同时提高了团队的生产力。

背景

我必须承认,这是一个相当大的标题。请允许我将其分解。那些熟悉标题中术语的人可以跳过本节。

面向切面编程(AOP)是一种技术,可帮助您专注于业务逻辑,将横切基础设施代码放在外部(有关更多信息,您可以参考 Wiki)。例如,日志记录和事务是两个常见的横切关注点。典型的企业应用程序代码如下所示

private static ILog log = LogManager.GetLogger(typeof(DepartmentFacade));
public IObservableList<Department> GetDepartments()
{
     ISession session = null;
     ITransaction trans = null;
     try
     {
           log.Debug("Entering GetDepartments...");
           session = NHibernateHelper.OpenSession();
           trans = session.BeginTransaction();
           DepartmentRepository departmentRepository = new DepartmentRepository(session);
           var departments = departmentRepository.GetDepartments();
           trans.Commit();
           session.Close();
           return departments;
     }
     catch (Exception ex)
     {
           log.Error("" + ex);
           trans.Rollback();
           session.Close();
           throw;
     }
     finally
     {
           log.Debug("Exiting GetDepartments...");
     }
} 

在上面的代码中,只有两行(粗体 + 斜体)完成了与业务相关的实际工作,其余行主要处理横切关注点。(注意:此处显示的日志记录使用 log4Net 完成,您可以通过我的博客 条目 了解如何开始使用它)。

接下来是 Unity,它是 Microsoft Patterns & Practices 团队提供的基于 .NET 的容器,可满足开发人员对依赖注入 (DI) 和 AOP 的需求。AOP 是本文的主要焦点。

NHibernate 是一个广泛使用的开源 O/R(对象-关系)映射器。它解决了对象世界和关系世界之间的阻抗不匹配问题。虽然我将展示如何将 AOP 应用于 NHibernate,但此处讨论的技术可以应用于任何 .NET 代码。

构建代码

要开始,您需要下载 Unity 框架(在我撰写本文时,1.2 是最新版本)并编译 Unity VS.NET 解决方案。创建一个新的控制台应用程序(为了简单起见),然后添加对所有 六个生成程序集的引用,如下所示

AOPWithUnity

下一步是创建 Unity CallHandlers(或拦截器),它们将封装代码的某个方面。您可以继承 Microsoft.Practices.Unity.InterceptionExtension 来创建 CallHandler。ICallHandler 接口。以下代码显示了一个 LogHandler

public class LogHandler : ICallHandler
{
        public int Order { get; set; }
        public IMethodReturn Invoke
	(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            ILog _log = LogManager.GetLogger(input.MethodBase.ReflectedType);
            _log.Debug("Entering " + input.MethodBase.Name + "...");
            //Call the next handler in the pipeline
            var returnMessage = getNext()(input, getNext);
            _log.Debug("Exiting " + input.MethodBase.Name + "...");
            return returnMessage;
        }
}

ICallHandler 接口包含一个名为 Order 的属性,该属性有助于对多个处理程序进行优先级排序。Invoke 方法是定义 Handler 功能的实际入口点。Invoke 方法的参数类型,IMethodInvocation 包含被调用方法的详细信息,而 GetNextHandlerDelegate 引用链中的下一个处理程序。对于 LogHandler 类,Invoke 方法仅在调用下一个 CallHandler 之前/之后记录方法的进入和退出。让我们再创建一个处理程序使其稍微复杂一些。

如下面的代码所示,TranscationHandler 使用 TransactionScope System.Transactions - ADO.NET 2.0)提供一个外部事务。它确保在业务逻辑成功时提交,并在业务逻辑失败时回滚。失败通常通过业务逻辑抛出的异常来指示。需要注意事务的提升,特别是当您的业务逻辑打开多个 NHibernate Session 时,需要注意将其提升为分布式事务。

public class TransactionHandler : ICallHandler
{
        public int Order { get; set; }
        public IMethodReturn Invoke
	(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            IMethodReturn returnMessage;
            //Use logging in production
            Console.WriteLine("Starting Transaction...");
            using (TransactionScope scope = new TransactionScope())
            {
                returnMessage = getNext()(input, getNext);
                //If Exception is thrown rollback
                if (returnMessage.Exception == null)
                {
                    scope.Complete();
                    Console.WriteLine("Completeing Transaction...");
                }
                else
                {
                    Console.WriteLine
		    ("Aborting Transaction due to ..." + returnMessage.Exception);
                }
            }
            return returnMessage;
        }
}

现在,我们将定义接口和实现,我们将在其上应用这些方面。AOP 和 DI 要求您遵循面向接口而非实现进行编程的设计原则。因此,在这里,我们将定义接口 IDoWork ,并对其进行编程(稍后在文章中查找主函数定义)。接口的实现驻留在 DoWork 类中(稍后将由 Unity 注入),需要注意的重要一点是,其中没有显式的日志记录或事务。

public interface IDoWork
{
        void Work(string departmentName); //Save a new Department to DB
}
public class DoWork : IDoWork
{
        //Look ma, no cross cutting concerns...   
        public void Work(string departmentName)
        {
              //N.B. NHibernateHelper is a helper class for Session   
              //management; download the solution for its Source.
              //As an exercise you can try providing a 
              //Session via Dependency Injection. 
              var session = NHibernateHelper.OpenSession();
              var repository = new DepartmentRepository(session);
              repository.SaveDepartment(new Department() { Name = departmentName });
              session.Close();
        }
}

那么现在的问题来了,如何将我们的 CallHandlers 应用于 Work 方法,从而使日志记录和事务隐式化?

使用 Unity 的解决方案

Unity 的流程如下

  1. 您将请求 Unity 容器为您提供特定接口类型的实现。
  2. Unity 容器在配置文件中查找映射到您的接口类型的实现类型。
  3. 在将实现类型实例返回之前,Unity 还会查看配置文件中定义的策略。
  4. 每个策略都有一个匹配规则,而匹配规则又映射到一个类型。
  5. 如果 Unity 发现某个策略的匹配规则映射到它即将返回的实现类型(步骤 2),则它会在返回实现实例之前执行策略概述的操作。
  6. 这是您可以包装实现实例的 CallHandlers(在我们的例子中是为了使日志记录和事务隐式化)。

以上步骤的实现显示在下面的代码和配置文件中

class Program
{
        static void Main()
        {            
            //Step 1
            var worker = Unity.UnityContainer.Resolve<IDoWork>();
            worker.Work("Physics");            
        }
}
//App.Config  
<unity>
    <containers>
      <container>
        <types>
     <!-- Step 2 -->
          <type type="UnityGettingStarted.IDoWork, UnityGettingStarted"
                mapTo="UnityGettingStarted.DoWork, UnityGettingStarted"  />
        </types>
        <extensions>
          <add type="Microsoft.Practices.Unity.InterceptionExtension.Interception, 
  Microsoft.Practices.Unity.Interception" />
        </extensions>
        <extensionConfig>
          <add name="interception" 
  type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.
	InterceptionConfigurationElement, 
  Microsoft.Practices.Unity.Interception.Configuration">
            <policies>
              <policy name="InterceptingDoWorkPolicy"><!--Step 3-->
                <matchingRules>
                  <matchingRule name="MatchDoWork" 
   type="Microsoft.Practices.Unity.InterceptionExtension.TypeMatchingRule, 
   Microsoft.Practices.Unity.Interception">
                    <injection>
                      <constructor>
                        <param name="typeName" parameterType="System.String">
                          <value value="UnityGettingStarted.DoWork" 
				type="System.String"/><!--Step 4 -->
                        </param>
                        <param name="ignoreCase" parameterType="System.Boolean">
                          <value value="true" type="System.Boolean" />
                        </param>
                      </constructor>
                    </injection>
                  </matchingRule>
                </matchingRules>
                <callHandlers><!--Step 5, 6 -->
                  <callHandler name="LogHandler" 
   type="UnityGettingStarted.LogHandler, UnityGettingStarted" />
                  <callHandler name="TransactionHandler" 
   type="UnityGettingStarted.TransactionHandler, UnityGettingStarted" />
                </callHandlers>
              </policy>
            </policies>
            <interceptors>
              <interceptor 
  type="Microsoft.Practices.Unity.InterceptionExtension.InterfaceInterceptor, 
   Microsoft.Practices.Unity.Interception">
                <default type="UnityGettingStarted.IDoWork, UnityGettingStarted" />
              </interceptor>
            </interceptors>
          </add>
        </extensionConfig>
      </container>
    </containers>
  </unity>

结论

本文展示了如何使用 Unity 的 AOP 来实现事务和日志记录等常见的横切关注点。Unity 的 AOP 是一项强大的技术,一旦掌握,就能为整个团队带来巨大的价值。

历史

  • 2009年1月5日:初稿
© . All rights reserved.