初探面向方面的开发(AOP)
现在我先假想出这样的一个应用情境:一个工作流的系统起初包括了最核心功能,在不同的业务领域中,还需要为这些核心的功能加载其它的约束或者自定义功能。定义如下:
/// 假设的工作流引擎
/// </summary>
public class WorkflowEngine
{
/// <summary>
/// 登录到工作流系统。
/// 这个函数只有对登录用户的判断规则,其它变动的规则等待注入
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public virtual bool Logon(string user)
{
if (user == "wzcheng" || user == "xyz" || user == "*")
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 得到工作文档
/// </summary>
/// <param name="user"></param>
/// <param name="docID"></param>
/// <returns></returns>
public virtual string GetWorkDocument(string user,int docID)
{
if (this.Logon(user) && docID>0)
return "this is a document!";
else
return string.Empty;
}
}
很容易想到的办法就是在具体的业务领域里的工作流引擎从WorkflowEngine派生并覆写它的某些方法。值得一提的是,无论是面向过程、面向对象还是面向方面的解决方案都可以达到预期的目标,不同的是对问题进行剖析的角度和实施的复杂度不同,所以下面的提到的解决方法请读者不要和OP或者OO比出一个谁优谁劣来,就如同米饭和食盐没有任何优劣可比性一样。你看我,又罗嗦了。言归正传, 首先提出问题: 如果某领域的业务规则是过了中午12点就不能登录工作流系统,并且任何用户只要取文档都要被日志记录下来。 从AOP的角度考虑,核心问题方面是:登录和取文档;外围问题方面是:时间约束和日志记录。核心问题是相对稳固的,外围问题是会随着用户需求的变化了变化的。 接着就是解决问题,我们可以形象的成这种办法为间谍技术、伪装技术、特使技术等等,别看我忽悠的挺夸张,不知道还以为我是国家安全局的。不过的确有点相似之处,比如某人在BBS上发帖妄言时政,他一提交就被告知:您的言论涉及敏感关键字,系统怀疑你灌水等等。你说这是什么机制,难道不是AOP吗?从BBS的角度触发,谈论面广,人气旺那是求之不得的事情,不过别忘了,大家都是生活在构造和谐社会这个大的设计框架之下,不该咱说的就别乱说嘛,就如同过了12点工作流引擎不提供服务一样,做人要厚道,不要不自觉嘛。可有些人就是不自觉,那么对这种不自觉的行为采取必要的措施不得不说是合乎天理人情的,所以AOP的应用还是有相当大的前景吧?
根据上文提到的AOP机制,我采取的做法就是采用运行时生成动态程序集的技术,放弃了平常常用new实例化对象的方式,改用类厂机制。什么是类厂呀?看官,如果这个问题还没明白的话,你得赶紧补充一下,劳驾上网查,关键字是:类厂、设计模式。我们继续,通过类厂动态实现原型类的代理类,代理类从原型类派生,按照一个需求清单覆写原型类的方法,在这些方法中添加了对其它方法的调用,我们把这种添加成为代码注入行为。对于之前提出的需求,我做了如下的实现:
/// 工作流的约束对象
/// </summary>
public class WorkFlowEngineBuilder : ObjectProxy
{
static WorkFlowEngineBuilder builder = new WorkFlowEngineBuilder();
public override Type Target
{
get { return typeof(WorkflowEngine); }
}
public override TargetType TargetType
{
get { return TargetType.Special; }
}
public override InjectCase[] OverridableList
{
get
{
InjectCase[] caseColl = new InjectCase[2];
caseColl[0] = new InjectCase();
/*向匹配 Logon(*) 原型的函数注入此类的Constrain函数*/
caseColl[0].SourceInfo = this.GetType().GetMethod("Constrain");
caseColl[0].TargetInfo = this.GetMatchMethodInfo("Logon(*)", true);
caseColl[1] = new InjectCase();
/*向匹配 Get(*) 原型的函数注入此类的WriteLog函数*/
caseColl[1].SourceInfo = this.GetType().GetMethod("WriteLog");
caseColl[1].TargetInfo = this.GetMatchMethodInfo("Get*(*)", true);
/*注意:当多个条件命中同一函数时,系统会出错,这里等待进一步的改进*/
return caseColl;
}
}
/// <summary>
/// 过了中午12点,系统就被锁定,除非以wzcheng登录的用户
/// </summary>
public void Constrain()
{
string currentUser = WorkFlowEngineBuilder.GetParameter<string>(0);
if (currentUser != "wzcheng")
{
if (DateTime.Now > DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd 12:00")))
{
throw new Exception("下班时间不能访问工作流引擎");
}
}
}
public void WriteLog()
{
string currentUser = WorkFlowEngineBuilder.GetParameter<string>(0);
int docID= WorkFlowEngineBuilder.GetParameter<int>(1);
Console.WriteLine("{0} 在 {1} 开始取ID为 {2} 文档", currentUser, DateTime.Now, docID);
}
/// <summary>
/// 创建一个新的代理对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parameters"></param>
/// <returns></returns>
public static T BuildObject<T>(params object[] parameters)
where T : class
{
return builder.NewObject<T>(parameters);
}
}
客户端调用的代码如下:
try
{
bool logon = workEng.Logon(this.comboBox1.Text);
MessageBox.Show(logon ? "登录成功" : "登录失败");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
从调用代码可以发现,除了用 WorkFlowEngineBuilder.BuildObject<WorkflowEngine>() 代替了new WorkflowEngine() 之外没有其它的变化,但运行时可以发现,变量 workEng 其实保存的是 WorkflowEngine的派生类型。这些都是表象,能说明的问题只有一个:这样的AOP实现方式在应用层还是很平滑的,不至于让程序员一开始就觉得接受不了,但这不是主要问题或者说不是主要的矛盾。是什么原因导致了AOP概念浮出水面,让G#或者aspectj取得了生存空间呢? 回头我们再分析WorkFlowEngineBuilder 类的如下代码:
{
get { return typeof(WorkflowEngine); }
}
上面的代码是指WorkFlowEngineBuilder 所针对的要注入代码的目标类型,如果把 typeof(WorkflowEngine) 该成别的,WorkFlowEngineBuilder 是不是就成为其它类型的注入者呢?是的。再看:
{
get
{
InjectCase[] caseColl = new InjectCase[2];
caseColl[0] = new InjectCase();
/*向匹配 Logon(*) 原型的函数注入此类的Constrain函数*/
caseColl[0].SourceInfo = this.GetType().GetMethod("Constrain");
caseColl[0].TargetInfo = this.GetMatchMethodInfo("Logon(*)", true);
caseColl[1] = new InjectCase();
/*向匹配 Get(*) 原型的函数注入此类的WriteLog函数*/
caseColl[1].SourceInfo = this.GetType().GetMethod("WriteLog");
caseColl[1].TargetInfo = this.GetMatchMethodInfo("Get*(*)", true);
/*注意:当多个条件命中同一函数时,系统会出错,这里等待进一步的改进*/
return caseColl;
}
}
如果改写这个提供覆写清单的方法中的匹配规则,那么结合 Tagert 的改变,WorkFlowEngineBuilder是不是就完全服务于一个全新的应用场景了?那么我们平时提到的重构、复用、责任链模式等等是不是都可以在AOP的驱动下完成呢?需要提出的是:我的例子只是为了肤浅的说明一下AOP,让不了解的人有一点点了解,代码部分写的比较烂,应该没有什么借鉴之处。download source and demo / for .net 2.0.