一、背景与动机

微软Enterprise Library ELAB(Exception Handling Application Block)提供了一种基于策略(Policy)的异常处理方式,在不同的环境中,比如多层架构中不同的层次中,我们可以定义不同的异常处理策略。对于ELAB来说,Exception Handling Policy = Exception Type + Exception Handler(s) ,也就是说异常处理策略规定了对于某种类型的类型需要通过那些Exception Handler去处理。

从这种意义上讲,ELAB的异常处理机制是基于Exception Type的,异常的类型是我们处理异常的最小粒度。对于一个确定的异常处理策略,在不同场合抛出的同种类型的异常,都会使用相同的Exception Handler去处理。举个例子,对于一个基于SQL Server的Data Access操作,对于数据库连接失败和违反数据一致性约束,都会抛出SqlException。这两种情况对于ELAB都是等效的,因为它只能根据异常的类型进行异常处理。

在很多情况下,这种基于异常类型级别的异常处理并不能解决我们实际异常处理的问题。我们往往需要粒度更细的异常处理机制——对于抛出的同一种类型的异常,根据异常对象具体的属性值进行相应的异常处理。

二、从基于类型的异常处理到基于属性值异常处理

我们需要实现的目标很清楚,就是对于抛出的异常,我们可以根据某个属性的具体的值,为其指定对应的Exception Handler进行处理。由于ELAB基于异常类型的Exception Handler的分发机制,我们不能改变,我们只能采用一些变通的机制实现“曲线救国”,达到我们基于属性的分发Exception Handler的目的。

具体的实现方案就是创建一个特殊的Exception Handler,该Exception Handler根据异常对象某个属性的值,指定相应的Exception Handler。对于这个特殊的Exception Handler来说,他实现了基于属性值的筛选功能,我们把它命名为FilterableExceptionHandler。

一般情况下,异常的类型和对应的Exception Handler通过下图的形式直接进行匹配。当FooException抛出,两个Exception Handler,ExceptionHandlerA和ExceptionHandlerB先后被执行。

当引入了FilterableExceptionHandler以后,整个结构变成下面一种形式:FilterableExceptionHandler被指定到FooException,当FooException被抛出的时候,FilterableExceptionHandler被执行。而FilterableExceptionHandler本身并不执行异常处理相关的逻辑,它的工作是根据exception的某个属性值,创建相对应的ExceptionHandler(s),并使用他们来处理该异常。如下图所示,当exception.Property=Value1是,创建ExceptionHandlerA和ExceptionHandlerB处理异常;当exception.Property=Value2时,真正创建出来进行异常处理的是ExceptionHandlerC和ExceptionHandlerD。

三、FilterableExceptionHandler的配置

接下来,我们就来创建这样一个特殊的FilterableExceptionHandler。和一般的自定义Exception Handler一样,除了定义FilterableExceptionHandler本身之外,还需要定义两个辅助的类:ExceptionHandlerData和ExceptionHandlerAssembler,前者定义ExceptionHandler相关的配置信息;后者通过配置创建相应的ExceptionHandler。

我们先来定于FilterableExceptionHandler的ExceptionHandlerData:FilterableExceptionHandlerData。在这之前,我们来看看一个FilterableExceptionHandler配置的实例:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <configuration>
   3:  <configSections>
   4:     <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   5:  </configSections>
   6:  <exceptionHandling>
   7:         <exceptionPolicies>
   8:             <add name="Exception Policy">
   9:                 <exceptionTypes>
  10:                     <add type="Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo"
  11:                         postHandlingAction="ThrowNewException" name="Exception">
  12:                         <exceptionHandlers>
  13:                             <add type="Artech.ExceptionHandlers.FilterableExceptionHandler,Artech.ExceptionHandlers" name="Custom Handler">
  14:                                 <filters>
  15:                                     <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  16:                                         <exceptionHandlers>
  17:                                             <add exceptionMessage="Bar" exceptionMessageResourceType="" 
  18:                                 replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
  19:                                 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  20:                                 name="Replace Handler" />
  21:                                         </exceptionHandlers>
  22:                                     </add>
  23:                                     <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  24:                                         <exceptionHandlers>
  25:                                             <add exceptionMessage="Baz" exceptionMessageResourceType=""
  26:                                 replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
  27:                                 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  28:                                 name="Replace Handler" />
  29:                                         </exceptionHandlers>
  30:                                     </add>
  31:                                 </filters>
  32:                             </add>
  33:                         </exceptionHandlers>
  34:                     </add>
  35:                 </exceptionTypes>
  36:             </add>
  37:         </exceptionPolicies>
  38:     </exceptionHandling>
  39: </configuration>
  40:  

其中和FilterableExceptionHandler相关的配置集中在如下一段。整个配置的结果是这样的:<filters>中一个filter列表,定义了对异常对象属性名/属性值的筛选和符合该条件的Exception Handler列表。下面一段配置表达的场景是:对于抛出的异常(Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo),我们需要调用ReplaceHandler用一个另一个异常对其进行替换。具体的替换规则是:如何Message属性为“xxx”,则将其替换成BarException;如何Message属性为“yyy”,则替换成BazException。最终的Message分别为“Bar”和“Baz”。

   1: <filters>
   2:     <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
   3:         <exceptionHandlers>
   4:             <add exceptionMessage="Bar" exceptionMessageResourceType=""
   5: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
   6: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   7: name="Replace Handler" />
   8:         </exceptionHandlers>
   9:     </add>
  10:     <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  11:         <exceptionHandlers>
  12:             <add exceptionMessage="Baz" exceptionMessageResourceType=""
  13: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
  14: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  15: name="Replace Handler" />
  16:         </exceptionHandlers>
  17:     </add>
  18: </filters>

四、如何创建如何创建FilterableExceptionHandler

对配置有一个初步了解后,我们来定义FilterableExceptionHandlerData:

   1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   3: using System.Configuration;
   4: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder; 
   5: namespace Artech.ExceptionHandlers
   6: {
   7:     [Assembler(typeof(FilterableExceptionHandlerAssembler))]
   8:     public class FilterableExceptionHandlerData:ExceptionHandlerData
   9:     {
  10:         [ConfigurationProperty("filters", IsRequired = true)]
  11:         public NamedElementCollection<FilterEntry> Filters
  12:         {
  13:             get
  14:             {
  15:                 return base["filters"] as NamedElementCollection<FilterEntry>; 
  16:             }
  17:             set
  18:             {
  19:                 this["filters"] = value;
  20:             }
  21:         }
  22:     }
  23: }

FilterableExceptionHandlerData仅仅是FilterEntry的集合。我们接着来看看FilterEntry的定义:

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   3: using System.Configuration;
   4: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   5: using System.ComponentModel;
   6: namespace Artech.ExceptionHandlers
   7: {
   8:     public class FilterEntry : NamedConfigurationElement
   9:     {
  10:         [ConfigurationProperty("property", IsRequired = true)]
  11:         public string PropertyName
  12:         {
  13:             get
  14:             {
  15:                 return this["property"] as string;
  16:             }
  17:            set
  18:             {
  19:                 this["property"] = value;
  20:             }
  21:         }
  22:  
  23:         [ConfigurationProperty("value", IsRequired = true)]
  24:         public string PropertyValue
  25:         {
  26:            get
  27:             {
  28:                return this["value"] as string;
  29:             }
  30:             set
  31:             {
  32:                 this["value"] = value;
  33:             }
  34:         }
  35:         [ConfigurationProperty("typeConverter", IsRequired = false, DefaultValue = "")]
  36:         public string TypeConverterData
  37:         {
  38:             get
  39:             {
  40:                 return this["typeConverter"] as string;
  41:             }
  42:            set
  43:             {
  44:                 this["typeConverter"] = value;
  45:             }
  46:         }
  47:         public TypeConverter TypeConverter
  48:         {
  49:             get
  50:             {
  51:                 if (string.IsNullOrEmpty(this.TypeConverterData))
  52:                 {
  53:                     return new TypeConverter();
  54:                 }
  55:  
  56:                 Type typeConverterType = null;
  57:                 try
  58:                 {
  59:                     typeConverterType = Type.GetType(this.TypeConverterData);
  60:                 }
  61:                 catch (Exception ex)
  62:                 {
  63:                     throw new ConfigurationErrorsException(ex.Message);
  64:                 }
  65:                 TypeConverter typeConverter = Activator.CreateInstance(typeConverterType) as TypeConverter;
  66:                 if (typeConverter == null)
  67:                 {
  68:                     throw new ConfigurationErrorsException(string.Format("The {0} is not a valid TypeConverter.", this.TypeConverterData));
  69:                 }
  70:  
  71:                 return typeConverter;
  72:             }
  73:         }
  74:  
  75:         [ConfigurationProperty("exceptionHandlers")]
  76:         public NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData> ExceptionHandlers
  77:         {
  78:             get
  79:             {
  80:                 return (NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData>)this["exceptionHandlers"];
  81:             }
  82:         }
  83:     }
  84: }

由于我们需要根据exception的某个属性来动态指定具体的ExceptionHandler,我们定了3个必要的属性:PropertyName、PropertyValue和ExceptionHandlers。他们分别表示用于筛选的属性名称和属性,以及满足筛选条件所采用的Exception Handler的配置。此外还具有一个额外的属性:TypeConverter,用于类型的转化。在进行筛选比较的时候,我们通过反射得到exception某个属性(PropertyName)的值,然后和指定的值(PropertyValue)进行比较。简单起见,我们在这里进行字符串的比较,所以我们需要通过TypeConverter将通过反射得到的属性值转换成字符串。默认的TypeConverter为System.ComponentModel.TypeConverter。

接下来我们看看真正的FilterableExceptionHandler的定义:

using System;

using System.Collections.Generic;

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

using System.Reflection;

using System.Configuration;

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;

namespace Artech.ExceptionHandlers

{

[ConfigurationElementType(typeof(FilterableExceptionHandlerData))]

public class FilterableExceptionHandler : IExceptionHandler

{

private FilterableExceptionHandlerData _filterableExceptionHandlerData;

public FilterableExceptionHandler(FilterableExceptionHandlerData handlerData)

{

this._filterableExceptionHandlerData = handlerData;

}

private IList<IExceptionHandler> GetFilteredHandler(Exception exception, FilterableExceptionHandlerData handlerData)

{

IList<IExceptionHandler> handlers = new List<IExceptionHandler>();

foreach (FilterEntry filterEntry in handlerData.Filters)

{

PropertyInfo propertyInfo = exception.GetType().GetProperty(filterEntry.PropertyName);

if (propertyInfo == null)

{

throw new ConfigurationErrorsException(

string.Format("The {0} does not have the {1} property.", exception.GetType().Name, filterEntry.PropertyName));

}

object propertyValue = propertyInfo.GetValue(exception, null);

if (string.Compare(filterEntry.TypeConverter.ConvertToString(propertyValue), filterEntry.PropertyValue, true) == 0)

{

foreach(ExceptionHandlerData exceptionHandlerData in filterEntry.ExceptionHandlers)

{

handlers.Add( ExceptionHandlerCustomFactory.Instance.Create(null,exceptionHandlerData,null,null));

}

}

}

return handlers;

}

#region IExceptionHandler Members

public Exception HandleException(Exception exception, Guid handlingInstanceId)

{

foreach (IExceptionHandler handler in this.GetFilteredHandler(exception, this._filterableExceptionHandlerData))

{

exception = handler.HandleException(exception,handlingInstanceId);

}

return exception;

}

#endregion

}

}

FilterableExceptionHandler的构造函数接受一个FilterableExceptionHandlerData 参数。在GetFilteredHandler方法中,我们通过具体的Exception对象和FilterableExceptionHandlerData筛选出真正的ExceptionHandler。逻辑并不复杂:便利FilterableExceptionHandlerData 中的所有FilterEntry,通过反射得到FilterEntry指定的属性名称(PropertyName)对应的属性值;通过TypeConverter转化成字符串后和FilterEntry指定的属性值(PropertyValue)进行比较。如果两者相互匹配,得到FilterEntry所有ExceptionHandler的ExceptionHandlerData,通过ExceptionHandlerCustomFactory创建对应的Exception Handler对象。最后将创建的Exception Handler对象加入目标列表。

在HandleException方法中,只需要逐个执行通过GetFilteredHandler方法筛选出来的Exception Handler就可以了。

最后简单看看FilterableExceptionHandlerAssembler 的定义。

   1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
   4: using Microsoft.Practices.ObjectBuilder2;
   5: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   6:  
   7: namespace Artech.ExceptionHandlers
   8: {
   9:     public class FilterableExceptionHandlerAssembler : IAssembler<IExceptionHandler, ExceptionHandlerData>
  10:     {
  11:         #region IAssembler<IExceptionHandler,ExceptionHandlerData> Members
  12:  
  13:         public IExceptionHandler Assemble(IBuilderContext context, ExceptionHandlerData objectConfiguration, 
  14:             IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
  15:         {
  16:             return new FilterableExceptionHandler(objectConfiguration as FilterableExceptionHandlerData);
  17:         }
  18:  
  19:         #endregion
  20:     }
  21: }
  22:  

五、使用、验证FilterableExceptionHandler

现在我们通过一个简单的Console Application来验证FilterableExceptionHandler是否能够按照我们希望的方式进行工作。我们使用在第三节列出的配置。为次我们我需要创建3个Exception:FooException 、BarException 和BazException。

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using System.Runtime.Serialization; 
   4: namespace Artech.CustomExceptionHandlers
   5: {
   6:     [global::System.Serializable]
   7:     public class FooException : Exception
   8:     {
   9:         public FooException() { }
  10:         public FooException(string message) : base(message) { }
  11:         public FooException(string message, Exception inner) : base(message, inner) { }
  12:         protected FooException(SerializationInfo info,StreamingContext context)
  13:             : base(info, context) { }
  14:     } 
  15:     [global::System.Serializable]
  16:     public class BarException : Exception
  17:     {
  18:         public BarException() { }
  19:         public BarException(string message) : base(message) { }
  20:         public BarException(string message, Exception inner) : base(message, inner) { }
  21:         protected BarException(SerializationInfo info,StreamingContext context)
  22:             : base(info, context) { }
  23:     } 
  24:     [global::System.Serializable]
  25:     public class BazException : Exception
  26:     {
  27:         public BazException() { }
  28:         public BazException(string message) : base(message) { }
  29:         public BazException(string message, Exception inner) : base(message, inner) { }
  30:         protected BazException(SerializationInfo info,StreamingContext context)
  31:             : base(info, context) { }
  32:     }
  33: }  

在通过配置我们可以看到,我们希望的是对FooException 进行异常的处理,并通过Message的属性,通过ReplaceHandler将其替换成BarException 和BazException;为此我们写一个HandleException方法。如下所以,我们人为地抛出一个FooException,Message通过参数指定。在try/catch中,通过ExceptionPolicy.HandleException方法通过 ELAB进行异常的处理。在最外层的catch中,输出最终的Exception的类型和Message。在Main方法中,两次调用HandleException方法,在参数中指定FooException的Message(“xxx”和“yyy”)。

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using System.Runtime.Serialization; 
   4: namespace Artech.CustomExceptionHandlers
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:        {
  10:             HandleException("xxx");
  11:             HandleException("yyy");            
  12:         } 
  13:         private static void HandleException(string message)
  14:         {
  15:             try
  16:             {
  17:                 try
  18:                 {
  19:                     throw new FooException(message);
  20:                 }
  21:                 catch (Exception ex)
  22:                 {                 
  23:                     if (ExceptionPolicy.HandleException(ex, "Exception Policy"))
  24:                     {
  25:                         throw;
  26:                     } 
  27:                 }
  28:             }
  29:             catch (Exception ex)
  30:             {
  31:                 Console.WriteLine("Exception Type: {0}; Message: {1}", ex.GetType().Name, ex.Message);
  32:             }
  33:         }
  34:     } 
  35: }

运行该程序,你将会看到如下的输出结果。可见对应抛出的同一种类型的Exception(FooException),通过我们的FilterableExceptionHandler,根据Message属性值的不同,最终被分别替换成了BarException 和BazException。


作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

Enterprise Library深入解析与灵活应用(4):创建一个自定义Exception Handler改变ELAB的异常处理机制...相关推荐

  1. [原创]Enterprise Library深入解析与灵活应用(4):创建一个自定义Exception Handler改变ELAB的异常处理机制

    一.背景与动机 微软Enterprise Library ELAB(Exception Handling Application Block)提供了一种基于策略(Policy)的异常处理方式,在不同的 ...

  2. 《Enterprise Library深入解析与灵活应用》博文系列汇总

    Enterprise Library是微软P&P部门开发的众多Open source框架中的一个,最新的版本已经出到了4.1.由于接触Enterprise Library已经有很长的一段时间, ...

  3. Enterprise Library深入解析与灵活应用(3):倘若将Unity、PIAB、Exception Handling引入MVP模式.. .. .....

    最近在做一个Smart Client Software Factory的项目.熟悉SCSF或者CAB的都应该很清楚MVP这种设计模式.MVP是MVC的一种变体,View和Mode分别关注于UI的呈现和 ...

  4. Enterprise Library 5.0 开发向导- 简介(1)

    Enterprise Library 5.0 开发向导- 简介(1) 微软企业库 Enterprise Library 5.0 正式发布!!! 在基于微软.NET 框架开发的应用程序中,无论是企业级的 ...

  5. Enterprise Library 4.1 Caching Block 图文笔记

    一,下载并安装好Enterprise Library 4.1 二,新建一个Web应用程序 三,右键点击Web.Config 文件 使用 Edit Enterprise Library Configur ...

  6. Enterprise Library Step By Step系列(十二):异常处理应用程序块——进阶篇

    一.把异常信息Logging到数据库 在日志和监测应用程序块中,有朋友提意见说希望能够把异常信息Logging到数据库中,在这里介绍一下具体的实现方法. 1.创建相关的数据库环境: 我们可以用日志和监 ...

  7. Enterprise Library 2.0 -- Caching Application Block

    开始写Enterprise Library 2.0的学习体会,准备先把每个部分的入门部分写好,然后再继续深入的研究每一部分,希望能得到高手的指点和建议.今天写的是Enterprise Library ...

  8. Enterprise Library学习所得(一):总体概述

    每一个设计模式都是针对于某一个问题点的最佳的解决方法.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office ...

  9. Enterprise Library 4.0

    微软发布了支持Visual Studio 2008的新版本Enterprise Library 4.0,同时也发布了他们的依赖注入容器Unity应用程序块的1.1版本. 模式与实践团队的产品经理Gri ...

最新文章

  1. 程序员笔试面试后上机_2021年国考笔试成绩查询后,面试准备阶段需要做好四方面...
  2. fineUI表格控件各属性说明
  3. 网络订票当心三类陷阱 最好当场识别真伪
  4. 硬件创新需要去理解的点(精炼总结)
  5. [HAOI2007]反素数
  6. 一文搞定Redis五大数据类型及使用场景
  7. 很多时候的心情,是需要一个无人的角落
  8. springboot冲突导致的发版失败
  9. 如果删除了DOM元素,是否还将其侦听器也从内存中删除了?
  10. 10074 启用开发者模式 for vs2015rc
  11. 三种射频通信接收机原理框图及优缺点
  12. 【Linux】POSIX信号量
  13. 阿里云无影云桌面分配用户是什么?
  14. star面试法则面试案例_案例面试技巧
  15. 华为面试状态码解密手把手教学
  16. uni-app 报错:代码使用了scss/sass语言,但未安装相应的编译器插件,请前往插件市场安装该插件
  17. 《那些年啊,那些事——一个程序员的奋斗史》——123
  18. 800套绝美PPT模板免费下载
  19. android 背光控制,Android P背光策略分析(一)
  20. 阿里云·数加,阿里下一代数据集成实践

热门文章

  1. 木业智能装备应用技术类毕业论文文献有哪些?
  2. 关于使用MyBatis逆向工程生成器把其他数据库的同名表生成下来的问题(Table Configuration xxx matched more than one table (xxx,xxx))
  3. OpenFlow入门资料汇总(OpenFlow、SDN、NOX等,多为网络文章)
  4. 证大中学10月份历史学科研讨
  5. 串口流控 UART 中 CTS RTS RX TX (串口模组和MCU直接的通信)
  6. 一个编程小白的Java SE学习日志 Ⅷ——接口、异常【极客BOY-米奇】
  7. Interger和int的使用区别
  8. no non-test Go files
  9. 2004-12-12
  10. MediaBox HD v2.4.6.1 [Lite Mod] [最新]