异常处理:AOP与传统处理方式的区别?

3
我正在升级一个 插件加载引擎 (.NET),它执行以下操作:
  1. 加载插件
  2. 将它们连接到适当的数据源
  3. 启动插件
  4. 显示结果
所有插件都实现相同的接口:IPlugin,并且每个插件都在单独的BackgroundWorker中启动。所有BackgroundWorkers都由名为Host的模块管理。
我的问题是错误/异常处理。该引擎已经部署,我想找到一种优雅的方式来处理可能在插件运行时抛出的错误/异常。有些异常在插件中被捕获,但不是全部。
我考虑了一个可以捕获所有插件错误并对其进行处理的单独层。
我想象了一种附加到每个PluginContext,其中包含其进度级别(BackgroundWorker.ReportProgress)、状态、抛出的异常(使用RunWorkerCompletedEvent),但只有在BackgroundWorker停止后才会抛出错误。当抛出异常时,我希望能够中断它。
我也在考虑面向切面编程可能是一个很好的方式。我查看了一些框架,如 Spring.NET。但不确定它是否适用于我的情况。 [更新] 以下是更多设计细节:
  • IPlugin接口:称为AbstractEnvChecker

AbstractEnvChecker

该应用程序是一个富客户端应用程序。编译插件后,生成的DLL被加载,并在简单的Windows窗体中向用户显示列表。然后,用户选择要启动的插件,调用Plugin.DoWork()方法。
以下是Host如何启动所选插件:
void LaunchPlugin(AbstractEnvChecker p_Plugin)
{
    if (p_Plugin != null)
    {
        BackgroundWorker l_BackgroundWorker = new BackgroundWorker();
        l_BackgroundWorker.WorkerReportsProgress = true;
        l_BackgroundWorker.WorkerSupportsCancellation = true;

        l_BackgroundWorker.DoWork +=
            new DoWorkEventHandler(bw_DoWork);
        l_BackgroundWorker.ProgressChanged +=
            new ProgressChangedEventHandler(bw_ProgressChanged);
        l_BackgroundWorker.RunWorkerCompleted +=
            new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);

        m_PluginByThreadMap.Add(l_BackgroundWorker, p_Plugin);
        l_BackgroundWorker.DoWork += p_Plugin.DoWork;
        l_BackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(l_BackgroundWorker_RunWorkerCompleted);
        l_BackgroundWorker.RunWorkerAsync(p_Plugin);
    }
}

AOP是否是添加错误处理层的适当解决方案?


得走了,但我想提出PostSharp作为AOP的一个选择。不能评论你在那里所拥有的影响/问题,但我绝对对讨论和答案感兴趣。 :) - Chris Sinclair
1
你能把你的backgroundWorker示例发布到你的插件上下文中吗? - Turbot
1
你的 IPlugin 接口是什么样子的?这是什么类型的应用程序:富客户端、控制台应用程序/计划任务...? - Marijn
2个回答

3
最简单的方法是将 IPlugin.DoWork() 方法放在一个 try/catch 代码块中,就像这样:l_BackgroundWorker.DoWork += (o, e) => ExecutePlugin(o, e, p_plugin);
private void ExecutePlugin(object sender, DoWorkEventArgs e, IPlugin plugin)
{   
   try 
   { 
      plugin.DoWork(o, e);
   }
   catch (Exception e)
   {
      //do something with the error. disable the plugin maybe?
   }
}

如果只是为了错误处理,我认为仅使用Spring有点过度设计。您可以采取一些附加措施,例如抛出自定义异常(例如PluginException),并在应用程序中全局处理它们。这可以通过连接到Application.ThreadException和AppDomain.CurrentDomain.UnhandledException事件来实现。

我同意,但问题是aop在这里可以如何使用,以及它会是什么样子。 - Marijn
@Marijn 他的观点之一是 AOP是否是添加错误处理层的好解决方案?还是我应该保留我提到的解决方案并进行开发(欢迎提出建议)?。所以我认为仅仅因为错误处理而使用AOP和Spring.net不是一个好的解决方案,而是杀鸡焉用牛刀。 - Sebastian Piu
我完全同意;当我第一次阅读时,错过了你回答中的过度部分。 - Marijn

1

Spring.net使用动态织入,这基本上意味着在运行时,Spring.net aop可以在方法调用周围包装异常处理程序。但是,Spring.net aop需要一个缝隙来定位其拦截器。

如果您的插件应该加载到UI中,则用户(可能)可以调用根本不通过主机或IPlugin接口的方法,这使得spring.net aop很难(如果不是不可能)拦截和包装异常处理程序。

如果您的主机是调用myPlugin.DoWork()的控制台应用程序或服务,则绝对可以使用Spring.net aop拦截插件抛出的任何异常。如果您可以提供更多详细信息(请参见您问题的评论),那么我可以向您展示如何做到这一点。

下面是一个示例,它使用Spring.net AOP代理插件实例并将其包装在拦截器中,以捕获抛出的异常并将其委托回主机。请注意,您也可以在没有AOP的情况下执行此操作...这取决于您。

using System;
using AopAlliance.Intercept;
using NUnit.Framework;
using Spring.Aop.Framework;

namespace Aop
{

    [TestFixture]
    public class SimpleProxyFactoryTests
    {
        [Test]
        public void Main()
        {
            var host = new Host();

            var mp = new SimplePlugin(); 
            var pf = new ProxyFactory(mp);
            pf.AddAdvice(new DelegateToHostExceptionHandlingAdvice(host));

            var proxy = (IPlugin)pf.GetProxy();

            proxy.DoWork();
        }
    }

    public interface IPlugin
    {
        void DoWork();
    }

    public class Host
    {
        public void HandleExceptionFromPlugin(Exception ex)
        {
            Console.WriteLine("Handling exception: {0}", ex.Message);
        }
    }

    public class SimplePlugin : IPlugin
    {
        public void DoWork()
        {
            Console.WriteLine("Doing it and throwing an exception ... ");

            throw new ApplicationException("Oops!");
        }
    }

    public class DelegateToHostExceptionHandlingAdvice : IMethodInterceptor 
    {
        private readonly Host _host;

        public DelegateToHostExceptionHandlingAdvice(Host host)
        {
            _host = host;
        }

        public object Invoke(IMethodInvocation invocation)
        {
            try
            {
                return invocation.Proceed();
            }
            catch (Exception ex)
            {
                 _host.HandleExceptionFromPlugin(ex);
                return null; 
            }
        }
    }
}

讨论

我希望我已经向您展示了如何利用aop框架来处理异常。正如Sebastian在他的回答中提到的那样,仅仅使用Spring aop来进行异常包装可能被认为是过度设计 - 我同意;将他的代码示例的简单性与我的复杂性进行比较。想象一下向团队中的新开发人员解释其中任何一个。

当您将Spring aop与Spring IOC容器结合使用时,Spring aop开始“闪耀”。


是的,主机通过调用 myPlugin.DoWork() 方法来启动插件,我添加了更多细节... - Mehdi
那更清楚了...我今天稍后会向您展示一种可能的解决方案。 - Marijn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接