使用 ContextBoundModel 构建 AOP.NET 可扩展业务组件






3.14/5 (7投票s)
2004年6月8日
4分钟阅读

78052

962
面向方面编程和 DotNet 实现 -
引言
面向方面编程 是一种拦截技术,它可以在方法调用之前/之后运行某些代码,或者直接替换方法调用。基于这种有用的能力,您可以做很多事情来帮助方法更好地运行,或者您只想像事件一样监听方法。当然,您可以为方法创建一个插件,它将扩展目标类的功能!ContextBoundModel (CBM) 是 AOP 的 DotNet 实现。本文展示的代码将让您了解 CBM 的简单和清晰。
基础知识
现在,您可能知道,可以拦截 AOP 对象的某个方法调用,但代码的语法是什么?
ContextBoundModel 定义了一个接口 IMessageHandlerBase
,如果您想拦截一个方法,应该实现这个接口。
using System.Runtime.Remoting.Messaging;
public interface IMessageHandlerBase
{
IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
,AspectObjectProxy proxy
,MessageHandlerQueue queue);
}
接口 IMessageHandlerBase
只定义了一个方法 - ProcessMessage
。参数 IMethodCallMessage 包含有关方法调用的信息:方法名称和参数。参数 AspectObjectProxy
是一个管理目标 AOP 对象的RealProxy。您可以使用 proxy.Items
来存储目标 AOP 对象的自定义数据。参数 MessageHandlerQueue
包含下一个 IMessageHandlerBase
。您可以使用 queue.InvokeNext(mcm,proxy)
来调用实际方法并获取一个 IMethodReturnMessage,其中包含有关返回值或异常的信息。第一部分 - AspectObject 和自拦截
实现 AOP 对象的基础是继承 AspectObject
,它定义在 Lostinet.ContextBoundModel
命名空间中。CBM 框架将为每个方法构建一个 MessageHandlerQueue
。所以 CBM 中的 AOP 对象应该是
using Lostinet.ContextBoundModel;
public class MyDataAccess : AspectObject
AOP 类可以通过实现 IAspectServerMessageHandler
来成为自拦截类。
public class MyDataAccess : AspectObject , IAspectServerMessageHandler
{
IMethodReturnMessage IMessageHandlerBase.ProcessMessage(
IMethodCallMessage mcm
,AspectObjectProxy proxy
,MessageHandlerQueue queue)
{
....
}
}
这意味着,当该类的其他方法在“外部”被调用时,将调用 ProcessMessage
。MyDataAccess 的示例代码是
using System;
using System.Runtime.Remoting.Messaging;
//using namespaces in Lostinet.ContextBoundModel.dll
using Lostinet.ContextBoundModel;
using Lostinet.ContextBoundModel.Configuration;
public class MyDataAccess : AspectObject , IAspectServerMessageHandler
{
private string theconnection;
//implementation for IAspectServerMessageHandler
//this method will invoke before other member invoked!
IMethodReturnMessage IMessageHandlerBase.ProcessMessage(
IMethodCallMessage mcm
, AspectObjectProxy proxy, MessageHandlerQueue queue)
{
if(theconnection!=null)
return queue.InvokeNext(mcm,proxy);
Console.WriteLine(" open connection ");
theconnection="newconnection";//open a database connection :-)
//do the default action - Invoke the method .
IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy);
theconnection=null;//release the connection
Console.WriteLine(" close connection ");
return retmsg;
}
public int InsertAccount(string username,string password)
{
Console.WriteLine("the connection is :"+theconnection);
Console.WriteLine("insert account:"+username+","+password);
return 101;//return new identity
}
}
MyDataAccess
类提供了一个名为 'InsertAccount
' 的方法。通常,此方法会打开连接,执行 SQL 查询,然后关闭连接。但现在,将调用 ProcessMessage
而不是 InsertAccount
方法。ProcessMessage
打开一个连接,使用 'queue.InvokeNext(mcm,proxy);
' 调用 InsertAccount
的实际代码并获取 IMethodReturnMessage
,然后关闭连接并返回结果。
这里我展示了调用 InsertAccount
的代码。
public class TestMyDataAccess
{
static public void Run()
{
MyDataAccess da=new MyDataAccess();
da.InsertAccount("myusername","mypassword");
}
}
测试代码的调用图是
TestMyDataClass.Run
-->Access.InsertAccount (intercepted call)
-->[The CBM interception helper code]
-->MyDataAccess.IMessageHandlerBase.ProcessMessage
-->MessageHandlerQueue.InvokeNext
-->[The CBM interception helper code]
-->MyDataAccess.InsertAccount (real call)
第二部分 - IMessageHandler
AOP 对象可以被其他 IMessageHandler 拦截。所以,如果您设计了一个实现 IMessageHandler
的类,如何让该类拦截 AOP 对象?CBM 提供了 4 种方法来实现这一点。
在第二个示例类中,代码使用运行时配置方法为 AOP 类型注册一个 IMessageHandler
类型。
这是一个 AOP 类 - MyBizObject
,以及一个 IMessageHandler
实现 - CreateAccountSendMailHandler
。在 TestMyBizObject.Run
中,代码使用 AspectConfigurationSettings.MessageHandlers.Add
为 MyBizObject
注册 CreateAccountSendMailHandler
。
public class MyBizObject : AspectObject { MyDataAccess myda=new MyDataAccess(); public int CreateAccount(string username,string password) { if(username==null)throw(new ArgumentNullException("username")); if(password==null)throw(new ArgumentNullException("password")); if(string.Compare("admin",username,true)==0) throw(new Exception("failed to create account")); //INSERT ACCOUNT TO DATABASE HERE Console.WriteLine("Creating Account :"+username); return myda.InsertAccount(username,password); } } public class CreateAccountSendMailHandler : IMessageHandler { public string HandlerName { get { return "SendMailAfterCreateAccount"; } } public IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm, AspectObjectProxy proxy , MessageHandlerQueue queue) { Console.WriteLine("Amazing!! I know "+mcm.MethodName+" is calling !"); //do the default action - Invoke the method . IMethodReturnMessage retmsg=queue.InvokeNext(mcm,proxy); //if the method failed if(retmsg.Exception!=null) return retmsg; if(mcm.MethodName=="CreateAccount") { string username=mcm.Args[0].ToString(); string password=mcm.Args[1].ToString(); int userid=(int)retmsg.ReturnValue; //Send an email to the admin : userid/username/password Console.WriteLine("Hi,Admin , account created : " +userid+","+username+":"+password); } return retmsg; } } public class TestMyBizObject { static public void Run() { //register CreateAccountSendMailHandler for MyBizObject //this could be configed in app/web.config file //or use AspectConfigurationSettings.Config to use a xml file AspectConfigurationSettings.MessageHandlers.Add( "SendMailAfterCreateAccount" ,typeof(MyBizObject) ,typeof(CreateAccountSendMailHandler) ,true,true,true ); MyBizObject mybizobj=new MyBizObject(); mybizobj.CreateAccount("lostinet","password"); } }
测试代码的调用图是
TestMyBizObject.Run -- -->MyBizObject.CreateAccount (intercepted call) -->[The CBM interception helper code] -->CreateAccountSendMailHandler.ProcessModel -->MessageHandlerQueue.InvokeNext -->[The CBM interception helper code] -->MyBizObject.CreateAccount (real call)
神奇之处在于,MyBizObject
并不知道 CreateAccountSendMailHandler
,但当调用 mybizobj.CreateAccount("lostinet","password")
时,CreateAccountSendMailHandler.ProcessMessage
将被调用!!!
第三部分 - IMessageHandlerAttribute
Attribute 是一种元数据。您可以将其视为附加到类或方法的配置。如果您不知道什么是 attribute,请参阅:MSDN - 使用 Attributes 扩展元数据。
ContextBoundModel 使用 Attribute 为类或方法配置 IMessageHandler
。这是重用 IMessageHandler
最快的方式!使用 attribute 重用 IMessageHandler
的代码如下:
//define an attribute class that implement IMessageHandlerAttribute
public class TheAttributeThatAttachYourMessageHandlerAttribute
: Attribute , IMessageHandlerAttribute
{
}
public class YouCanAttachToMethodToottribute
: Attribute , IMessageHandlerAttribute
{
}
//and then , you can reuse it as :
[TheAttributeThatAttachYourMessageHandler]
public class AnAopObject : AspectObject
{
[YouCanAttachToMethodToo]
public void MethodThatCanBeIntercepted(){}
}
这是第三个简单的类。
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Parameter)]
public class CheckArgumentsNotNullAttribute : Attribute
,IMessageHandlerAttribute
,IMessageHandler
{
public string HandlerName
{
get
{
return "CheckArgumentsNotNull";
}
}
//implement IMessageHandlerAttribute
public IMessageHandler CreateMessageHandler(bool bserver)
{
//return this as IMessageHandler
//of cause you can create an new Handler and return it.
return this;
}
//implement IMessageHandler
public IMethodReturnMessage ProcessMessage(IMethodCallMessage mcm
, AspectObjectProxy proxy, MessageHandlerQueue queue)
{
//check the arguments !
object[] inargs=mcm.InArgs;
for(int i=0;i < inargs.Length;i++)
{
//if the argument is null , throw an exception !
if(inargs[i]==null)
throw(new ArgumentNullException(mcm.GetInArgName(i)));
}
//do the default action - invoke target method.
return queue.InvokeNext(mcm,proxy);
}
}
public class AccountService : AspectObject
{
MyBizObject mybo=new MyBizObject();
[CheckArgumentsNotNull]
public void CreateAccount(string username,string password)
{
mybo.CreateAccount(username,password);
}
}
public class TestAccountService
{
static public void Run()
{
AccountService service=new AccountService();
try
{
string password=null;
service.CreateAccount("yourname",password);
}
catch(Exception x)
{
Console.WriteLine(x.GetType().FullName);
}
}
}
请看 AccountService.CreateAccount
方法,它被标记了 [CheckArgumentsNotNull]
attribute。当调用 AccountService.CreateAccount
时,将调用 CheckArgumentsNotNullAttribute.CreateMessageHandler
来返回一个 IMessageHandler
来处理消息,然后调用 CheckArgumentsNotNullAttribute.ProcessMessage
!!
TestAccountService.Run
-->AccountService.CreateAccount (intercepted call)
-->[The CBM interception helper code]
//here the CreateMessageHandler being invoked
-->CreateAccountSendMailHandler.ProcessModel
-->CheckArgumentsNotNullAttribute.InvokeNext
-->[The CBM interception helper code]
-->AccountService.CreateAccount (real call)
第四部分 - 在控制台应用程序中测试代码
class EntryPoint
{
[STAThread] static void Main()
{
TestMyDataAccess.Run();
Console.WriteLine(" -------- ");
TestMyBizObject.Run();
Console.WriteLine(" -------- ");
TestAccountService.Run();
}
}
此应用程序输出
open connection
the connection is :newconnection
insert account:myusername,mypassword
close connection
--------
Amazing!! I know .ctor is calling !
Amazing!! I know CreateAccount is calling !
Creating Account :lostinet
open connection
the connection is :newconnection
insert account:lostinet,password
close connection
Hi,Admin , account created : 101,lostinet:password
--------
Amazing!! I know .ctor is calling !
System.ArgumentNullException
Press any key to continue
更多想法?
您可以构建自己的 IMessageHandler
/IMessageHandlerAttribute
对,并像这样重用它们:
[AutoStartSqlConnection]
[AutoStartSqlTransactionAndCommitIfNoException]
[LogIntoDataBase]
[WriteWindowsEventLogWhileException]
[EmailAdministratorsWhileException]
public void Login([NotNull] string username,[NotNull] string password)
{
........
thow(new Exception("wrong password"));
}
您可以通过本文前面的链接下载示例代码。readme.txt 文件中包含一个提供 20 多个示例代码的链接!
资源
- CBM 主页 - http://www.contextboundmodel.net/
文章
- CodeProject - 面向方面编程 / 面向方面软件设计
- MSDN - 面向方面编程实现更好的代码封装和重用
- MSDN - 通过将自定义服务注入对象的拦截链来解耦组件
.NET 实现
- LOOM.NET - http://www.rapier-loom.net/
- Spring.Net - http://www.springframework.net/
Java 实现
- AspectJ - http://www.aspectj.org/
- Spring - http://www.springframework.org/